Commit f7e8a871 authored by Pavel Vasilyev's avatar Pavel Vasilyev Committed by Anas Nashif
Browse files

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: default avatarPavel Vasilyev <pavel.vasilyev@nordicsemi.no>
parent f712bde1
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -10,7 +10,8 @@

#define BT_EATT_PSM		0x27
#define BT_ATT_DEFAULT_LE_MTU	23
#define BT_ATT_TIMEOUT		K_SECONDS(30)
#define BT_ATT_TIMEOUT_SEC	30
#define BT_ATT_TIMEOUT		K_SECONDS(BT_ATT_TIMEOUT_SEC)

/* Local ATT Rx MTU
 *
+1 −0
Original line number Diff line number Diff line
@@ -22,5 +22,6 @@ run_in_background ${ZEPHYR_BASE}/tests/bsim/bluetooth/host/att/sequential/compil
run_in_background ${ZEPHYR_BASE}/tests/bsim/bluetooth/host/att/pipeline/compile.sh
run_in_background ${ZEPHYR_BASE}/tests/bsim/bluetooth/host/att/long_read/compile.sh
run_in_background ${ZEPHYR_BASE}/tests/bsim/bluetooth/host/att/open_close/compile.sh
run_in_background ${ZEPHYR_BASE}/tests/bsim/bluetooth/host/att/timeout/compile.sh

wait_for_background_jobs
+24 −0
Original line number Diff line number Diff line
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr HINTS $ENV{ZEPHYR_BASE})
project(app)

add_subdirectory(${ZEPHYR_BASE}/tests/bluetooth/common/testlib testlib)

target_sources(app PRIVATE
  ../long_read/bs_main.c
  ../long_read/bs_sync.c
  main.c
)

zephyr_include_directories(
  ${BSIM_COMPONENTS_PATH}/libPhyComv1/src/
  ${BSIM_COMPONENTS_PATH}/libUtilv1/src/
  ../long_read/
)

target_link_libraries(app PRIVATE
  testlib
)
+14 −0
Original line number Diff line number Diff line
#!/usr/bin/env bash
# Copyright 2024 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0

set -eu
: "${ZEPHYR_BASE:?ZEPHYR_BASE must be defined}"

INCR_BUILD=1

source ${ZEPHYR_BASE}/tests/bsim/compile.source

app="$(guess_test_relpath)" compile

wait_for_background_jobs
+292 −0
Original line number Diff line number Diff line
/* 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");
}
Loading