zephyr/tests/bsim/bluetooth/host/att/timeout/main.c
Pavel Vasilyev f7e8a8717b tests: bsim: bluetooth: host: att: Add ATT timeout test
This commit tests that the host correctly disconnects from peer when ATT
timeout happens.

Signed-off-by: Pavel Vasilyev <pavel.vasilyev@nordicsemi.no>
2024-10-10 14:59:02 -04:00

293 lines
7.6 KiB
C

/* Copyright (c) 2024 Nordic Semiconductor ASA
* SPDX-License-Identifier: Apache-2.0
*/
#include <argparse.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/settings/settings.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/bluetooth/bluetooth.h>
#include "host/att_internal.h"
#include "testlib/adv.h"
#include "testlib/att_read.h"
#include "testlib/att_write.h"
#include "bs_macro.h"
#include "bs_sync.h"
#include <testlib/conn.h>
#include "testlib/log_utils.h"
#include "testlib/scan.h"
#include "testlib/security.h"
/* This test uses system asserts to fail tests. */
BUILD_ASSERT(__ASSERT_ON);
LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG);
#define CENTRAL_DEVICE_NBR 0
#define PERIPHERAL_DEVICE_NBR 1
#define UUID_1 \
BT_UUID_DECLARE_128(0xdb, 0x1f, 0xe2, 0x52, 0xf3, 0xc6, 0x43, 0x66, 0xb3, 0x92, 0x5d, \
0xc6, 0xe7, 0xc9, 0x59, 0x9d)
#define UUID_2 \
BT_UUID_DECLARE_128(0x3f, 0xa4, 0x7f, 0x44, 0x2e, 0x2a, 0x43, 0x05, 0xab, 0x38, 0x07, \
0x8d, 0x16, 0xbf, 0x99, 0xf1)
static bool trigger_att_timeout;
static K_SEM_DEFINE(disconnected_sem, 0, 1);
static ssize_t read_chrc(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
uint16_t buf_len, uint16_t offset)
{
ssize_t read_len;
LOG_INF("ATT timeout will %sbe triggered", trigger_att_timeout ? "" : "not ");
if (trigger_att_timeout) {
/* Sleep longer than ATT Timeout (section 3.3.3). */
k_sleep(K_SECONDS(BT_ATT_TIMEOUT_SEC + 1));
}
__ASSERT_NO_MSG(offset == 0);
read_len = buf_len;
__ASSERT_NO_MSG(read_len >= 2);
sys_put_le16(read_len, buf);
return read_len;
}
static struct bt_gatt_attr attrs[] = {
BT_GATT_PRIMARY_SERVICE(UUID_1),
BT_GATT_CHARACTERISTIC(UUID_2, BT_GATT_CHRC_READ, BT_GATT_PERM_READ, read_chrc, NULL, NULL),
};
static struct bt_gatt_service svc = {
.attrs = attrs,
.attr_count = ARRAY_SIZE(attrs),
};
static void bs_sync_all_log(char *log_msg)
{
/* Everyone meets here. */
bt_testlib_bs_sync_all();
if (get_device_nbr() == 0) {
LOG_WRN("Sync point: %s", log_msg);
}
/* Everyone waits for d0 to finish logging. */
bt_testlib_bs_sync_all();
}
static inline void bt_enable_quiet(void)
{
bt_testlib_log_level_set("bt_hci_core", LOG_LEVEL_ERR);
bt_testlib_log_level_set("bt_id", LOG_LEVEL_ERR);
EXPECT_ZERO(bt_enable(NULL));
bt_testlib_log_level_set("bt_hci_core", LOG_LEVEL_INF);
bt_testlib_log_level_set("bt_id", LOG_LEVEL_INF);
}
static struct bt_conn *peripheral_setup(enum bt_att_chan_opt bearer, bool timeout)
{
struct bt_conn *conn = NULL;
EXPECT_ZERO(bt_testlib_adv_conn(&conn, BT_ID_DEFAULT, bt_get_name()));
trigger_att_timeout = timeout;
return conn;
}
static struct bt_conn *central_setup(enum bt_att_chan_opt bearer, bool timeout)
{
bt_addr_le_t adva;
struct bt_conn *conn = NULL;
EXPECT_ZERO(bt_testlib_scan_find_name(&adva, "peripheral"));
EXPECT_ZERO(bt_testlib_connect(&adva, &conn));
/* Establish EATT bearers. */
EXPECT_ZERO(bt_testlib_secure(conn, BT_SECURITY_L2));
while (bt_eatt_count(conn) == 0) {
k_msleep(100);
};
return conn;
}
static void central_read(struct bt_conn *conn, enum bt_att_chan_opt bearer, bool timeout)
{
uint16_t actual_read_len;
uint16_t remote_read_send_len;
uint16_t handle = 0;
int err;
NET_BUF_SIMPLE_DEFINE(attr_value, sizeof(remote_read_send_len));
err = bt_testlib_att_read_by_type_sync(&attr_value, &actual_read_len, &handle, NULL, conn,
bearer, UUID_2, BT_ATT_FIRST_ATTRIBUTE_HANDLE,
BT_ATT_LAST_ATTRIBUTE_HANDLE);
if (timeout) {
__ASSERT(err == BT_ATT_ERR_UNLIKELY, "Unexpected error %d", err);
} else {
__ASSERT(!err, "Unexpected error %d", err);
__ASSERT(attr_value.len >= sizeof(remote_read_send_len),
"Remote sent too little data.");
remote_read_send_len = net_buf_simple_pull_le16(&attr_value);
__ASSERT(remote_read_send_len == actual_read_len, "Length mismatch. %u %u",
remote_read_send_len, actual_read_len);
}
}
/**
* Test procedure:
*
* Central:
* 1. Connect to the peripheral.
* 2. Try to read a characteristic value.
* 3. Expect BT_ATT_ERR_UNLIKELY error.
* 4. Expect the peripheral to disconnect.
* 5. Reconnect to the peripheral.
* 6. Try to read a characteristic value.
* 7. Expect the peripheral to respond with the characteristic value.
* 8. Ensure that connection stays alive after a delay equal to ATT timeout.
* 9. Disconnect from the peripheral.
*
* Peripheral:
* 1. Start advertising.
* 2. Make the read callback sleep for more than ATT Timeout when the central tries to read.
* 3. Expect the disconnected callback to be called.
* 4. Start advertising again.
* 5. Make the read callback respond with the characteristic value when the central tries to read.
* 6. Expect the connection stay alive after a delay equal to ATT timeout.
* 7. Expect the central to disconnect.
*/
static void test_timeout(enum bt_att_chan_opt bearer)
{
bool central = (get_device_nbr() == CENTRAL_DEVICE_NBR);
bool peripheral = (get_device_nbr() == PERIPHERAL_DEVICE_NBR);
struct bt_conn *conn;
int err;
/* Test ATT timeout. */
if (peripheral) {
conn = peripheral_setup(bearer, true);
}
if (central) {
conn = central_setup(bearer, true);
}
bs_sync_all_log("Ready to test ATT timeout");
if (central) {
central_read(conn, bearer, true);
}
err = k_sem_take(&disconnected_sem, K_SECONDS(BT_ATT_TIMEOUT_SEC + 2));
/* Here disconnect is triggered by the Central host due to ATT timeout. */
__ASSERT(!err, "Unexpected error %d", err);
bt_testlib_conn_unref(&conn);
/* Test successful read. */
if (peripheral) {
conn = peripheral_setup(bearer, false);
}
if (central) {
conn = central_setup(bearer, false);
}
bs_sync_all_log("Ready to test successful read");
if (central) {
central_read(conn, bearer, false);
}
err = k_sem_take(&disconnected_sem, K_SECONDS(BT_ATT_TIMEOUT_SEC + 2));
/* Check that disconnect doesn't happen during time > ATT timeout. */
__ASSERT(err == -EAGAIN, "Unexpected error %d", err);
if (central) {
/* This time disconnect from the peripheral. */
EXPECT_ZERO(bt_testlib_disconnect(&conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN));
}
if (peripheral) {
/* Wait for the central to disconnect. */
bt_testlib_wait_disconnected(conn);
bt_testlib_conn_unref(&conn);
}
/* Clear the semaphore. */
err = k_sem_take(&disconnected_sem, K_SECONDS(1));
__ASSERT_NO_MSG(!err);
}
static void connected(struct bt_conn *conn, uint8_t err)
{
LOG_INF("Connected");
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
bool central = (get_device_nbr() == CENTRAL_DEVICE_NBR);
bool peripheral = (get_device_nbr() == PERIPHERAL_DEVICE_NBR);
uint8_t expected_reason;
LOG_INF("Disconnected: %u", reason);
if (central) {
expected_reason = BT_HCI_ERR_LOCALHOST_TERM_CONN;
}
if (peripheral) {
expected_reason = BT_HCI_ERR_REMOTE_USER_TERM_CONN;
}
__ASSERT(expected_reason == reason, "Unexpected reason %u", reason);
k_sem_give(&disconnected_sem);
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
};
void the_test(void)
{
bool peripheral = (get_device_nbr() == PERIPHERAL_DEVICE_NBR);
if (peripheral) {
EXPECT_ZERO(bt_gatt_service_register(&svc));
}
bt_enable_quiet();
if (peripheral) {
EXPECT_ZERO(bt_set_name("peripheral"));
}
bs_sync_all_log("Testing UATT");
test_timeout(BT_ATT_CHAN_OPT_UNENHANCED_ONLY);
bs_sync_all_log("Testing EATT");
test_timeout(BT_ATT_CHAN_OPT_ENHANCED_ONLY);
bs_sync_all_log("Test Complete");
PASS("Test complete\n");
}