diff --git a/tests/bluetooth/bsim_bt/bsim_test_adv_resume/CMakeLists.txt b/tests/bluetooth/bsim_bt/bsim_test_adv_resume/CMakeLists.txt new file mode 100644 index 00000000000..56a2fc8eac4 --- /dev/null +++ b/tests/bluetooth/bsim_bt/bsim_test_adv_resume/CMakeLists.txt @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +if (NOT DEFINED ENV{BSIM_COMPONENTS_PATH}) + message(FATAL_ERROR "This test requires the BabbleSim simulator. Please set\ + the environment variable BSIM_COMPONENTS_PATH to point to its components \ + folder. More information can be found in\ + https://babblesim.github.io/folder_structure_and_env.html") +endif() + +find_package(Zephyr HINTS $ENV{ZEPHYR_BASE}) +project(bsim_test_adv_resume) + +target_sources(app PRIVATE + src/bs_bt_utils.c + src/dut.c + src/main.c + src/tester.c +) + +zephyr_include_directories( + $ENV{BSIM_COMPONENTS_PATH}/libUtilv1/src/ + $ENV{BSIM_COMPONENTS_PATH}/libPhyComv1/src/ + ) diff --git a/tests/bluetooth/bsim_bt/bsim_test_adv_resume/prj.conf b/tests/bluetooth/bsim_bt/bsim_test_adv_resume/prj.conf new file mode 100644 index 00000000000..ec138585fd7 --- /dev/null +++ b/tests/bluetooth/bsim_bt/bsim_test_adv_resume/prj.conf @@ -0,0 +1,20 @@ +CONFIG_BT=y +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_CENTRAL=y + +CONFIG_ASSERT=y +CONFIG_BT_TESTING=y +CONFIG_BT_DEBUG_LOG=y + +CONFIG_BT_EXT_ADV=n +CONFIG_BT_PRIVACY=n +CONFIG_BT_SCAN_WITH_IDENTITY=n +CONFIG_BT_LOG_SNIFFER_INFO=y + +CONFIG_BT_AUTO_PHY_UPDATE=n +CONFIG_BT_AUTO_DATA_LEN_UPDATE=n +CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS=n + +CONFIG_BT_MAX_CONN=2 +CONFIG_BT_MAX_PAIRED=2 +CONFIG_BT_ID_MAX=2 diff --git a/tests/bluetooth/bsim_bt/bsim_test_adv_resume/src/bs_bt_utils.c b/tests/bluetooth/bsim_bt/bsim_test_adv_resume/src/bs_bt_utils.c new file mode 100644 index 00000000000..b0451b66eca --- /dev/null +++ b/tests/bluetooth/bsim_bt/bsim_test_adv_resume/src/bs_bt_utils.c @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "bs_bt_utils.h" +#include "argparse.h" +#include "bs_pc_backchannel.h" + +BUILD_ASSERT(CONFIG_BT_MAX_PAIRED >= 2, "CONFIG_BT_MAX_PAIRED is too small."); +BUILD_ASSERT(CONFIG_BT_ID_MAX == 2, "CONFIG_BT_ID_MAX should be 2."); + +#define BS_SECONDS(dur_sec) ((bs_time_t)dur_sec * USEC_PER_SEC) +#define TEST_TIMEOUT_SIMULATED BS_SECONDS(60) + +void test_tick(bs_time_t HW_device_time) +{ + bs_trace_debug_time(0, "Simulation ends now.\n"); + if (bst_result != Passed) { + bst_result = Failed; + bs_trace_error("Test did not pass before simulation ended.\n"); + } +} + +void test_init(void) +{ + bst_ticker_set_next_tick_absolute(TEST_TIMEOUT_SIMULATED); + bst_result = In_progress; +} + +DEFINE_FLAG(flag_is_connected); + +void wait_connected(void) +{ + UNSET_FLAG(flag_is_connected); + WAIT_FOR_FLAG(flag_is_connected); + printk("connected\n"); +} + +void wait_disconnected(void) +{ + SET_FLAG(flag_is_connected); + WAIT_FOR_FLAG_UNSET(flag_is_connected); + printk("disconnected\n"); +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + UNSET_FLAG(flag_is_connected); +} + +static void connected(struct bt_conn *conn, uint8_t err) +{ + if (err != 0) { + return; + } + + SET_FLAG(flag_is_connected); +} + +BT_CONN_CB_DEFINE(conn_callbacks) = { + .connected = connected, + .disconnected = disconnected, +}; + +void bs_bt_utils_setup(void) +{ + int err; + + err = bt_enable(NULL); + ASSERT(!err, "bt_enable failed.\n"); +} + +static bt_addr_le_t last_scanned_addr; + +static void scan_connect_to_first_result_device_found(const bt_addr_le_t *addr, int8_t rssi, + uint8_t type, struct net_buf_simple *ad) +{ + struct bt_conn *conn; + char addr_str[BT_ADDR_LE_STR_LEN]; + int err; + + /* We're only interested in connectable events */ + if (type != BT_HCI_ADV_IND && type != BT_HCI_ADV_DIRECT_IND) { + FAIL("Unexpected advertisement type."); + } + + bt_addr_le_to_str(addr, addr_str, sizeof(addr_str)); + printk("Got scan result, connecting.. dst %s, RSSI %d\n", + addr_str, rssi); + + err = bt_le_scan_stop(); + ASSERT(!err, "Err bt_le_scan_stop %d", err); + + err = bt_conn_le_create(addr, + BT_CONN_LE_CREATE_CONN, BT_LE_CONN_PARAM_DEFAULT, + &conn); + ASSERT(!err, "Err bt_conn_le_create %d", err); + + /* Save address for later comparison */ + memcpy(&last_scanned_addr, addr, sizeof(last_scanned_addr)); +} + +void scan_connect_to_first_result(void) +{ + int err; + + printk("start scanner\n"); + err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, + scan_connect_to_first_result_device_found); + ASSERT(!err, "Err bt_le_scan_start %d", err); +} + +static void scan_expect_same_address_device_found(const bt_addr_le_t *addr, int8_t rssi, + uint8_t type, struct net_buf_simple *ad) +{ + char addr_str[BT_ADDR_LE_STR_LEN]; + char expected_addr_str[BT_ADDR_LE_STR_LEN]; + + /* We're only interested in connectable events */ + if (type != BT_HCI_ADV_IND && type != BT_HCI_ADV_DIRECT_IND) { + FAIL("Unexpected advertisement type."); + } + + if (!bt_addr_le_eq(&last_scanned_addr, addr)) { + bt_addr_le_to_str(&last_scanned_addr, + expected_addr_str, + sizeof(expected_addr_str)); + bt_addr_le_to_str(addr, addr_str, sizeof(addr_str)); + + FAIL("Expected advertiser with addr %s, got %s\n", + expected_addr_str, addr_str); + } + + PASS("Advertiser used correct address on resume\n"); +} + +void scan_expect_same_address(void) +{ + int err; + + printk("start scanner\n"); + err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, + scan_expect_same_address_device_found); + ASSERT(!err, "Err bt_le_scan_start %d", err); +} + +static void disconnect_device(struct bt_conn *conn, void *data) +{ + int err; + + /* We only use a single flag to indicate connections. Since this + * function will be called multiple times in a row, we have to set it + * back after it has been unset (in the `disconnected` callback). + */ + SET_FLAG(flag_is_connected); + + err = bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + ASSERT(!err, "Failed to initate disconnect (err %d)", err); + + printk("Waiting for disconnection...\n"); + WAIT_FOR_FLAG_UNSET(flag_is_connected); +} + +void disconnect(void) +{ + bt_conn_foreach(BT_CONN_TYPE_LE, disconnect_device, NULL); +} + +void advertise_connectable(int id, bool persist) +{ + printk("start advertiser\n"); + int err; + struct bt_le_adv_param param = {}; + + param.id = id; + param.interval_min = 0x0020; + param.interval_max = 0x4000; + param.options |= persist ? 0 : BT_LE_ADV_OPT_ONE_TIME; + param.options |= BT_LE_ADV_OPT_CONNECTABLE; + + err = bt_le_adv_start(¶m, NULL, 0, NULL, 0); + ASSERT(err == 0, "Advertising failed to start (err %d)\n", err); +} + +#define CHANNEL_ID 0 +#define MSG_SIZE 1 + +void backchannel_init(uint peer) +{ + uint device_number = get_device_nbr(); + uint device_numbers[] = { peer }; + uint channel_numbers[] = { CHANNEL_ID }; + uint *ch; + + ch = bs_open_back_channel(device_number, device_numbers, + channel_numbers, ARRAY_SIZE(channel_numbers)); + if (!ch) { + FAIL("Unable to open backchannel\n"); + } +} + +void backchannel_sync_send(void) +{ + uint8_t sync_msg[MSG_SIZE] = { get_device_nbr() }; + + printk("Sending sync\n"); + bs_bc_send_msg(CHANNEL_ID, sync_msg, ARRAY_SIZE(sync_msg)); +} + +void backchannel_sync_wait(void) +{ + uint8_t sync_msg[MSG_SIZE]; + + while (true) { + if (bs_bc_is_msg_received(CHANNEL_ID) > 0) { + bs_bc_receive_msg(CHANNEL_ID, sync_msg, + ARRAY_SIZE(sync_msg)); + if (sync_msg[0] != get_device_nbr()) { + /* Received a message from another device, exit */ + break; + } + } + + k_sleep(K_MSEC(1)); + } + + printk("Sync received\n"); +} diff --git a/tests/bluetooth/bsim_bt/bsim_test_adv_resume/src/bs_bt_utils.h b/tests/bluetooth/bsim_bt/bsim_test_adv_resume/src/bs_bt_utils.h new file mode 100644 index 00000000000..8440fda287d --- /dev/null +++ b/tests/bluetooth/bsim_bt/bsim_test_adv_resume/src/bs_bt_utils.h @@ -0,0 +1,74 @@ +/** + * Common functions and helpers for BSIM ADV tests + * + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "bs_tracing.h" +#include "bs_types.h" +#include "bstests.h" +#include "time_machine.h" +#include "zephyr/sys/__assert.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +extern enum bst_result_t bst_result; + +#define DEFINE_FLAG(flag) atomic_t flag = (atomic_t) false +#define SET_FLAG(flag) (void)atomic_set(&flag, (atomic_t) true) +#define UNSET_FLAG(flag) (void)atomic_set(&flag, (atomic_t) false) +#define WAIT_FOR_FLAG(flag) \ + while (!(bool)atomic_get(&flag)) { \ + (void)k_sleep(K_MSEC(1)); \ + } +#define WAIT_FOR_FLAG_UNSET(flag) \ + while ((bool)atomic_get(&flag)) { \ + (void)k_sleep(K_MSEC(1)); \ + } +#define TAKE_FLAG(flag) \ + while (!(bool)atomic_cas(&flag, true, false)) { \ + (void)k_sleep(K_MSEC(1)); \ + } + +#define ASSERT(expr, ...) \ + do { \ + if (!(expr)) { \ + FAIL(__VA_ARGS__); \ + } \ + } while (0) + +#define FAIL(...) \ + do { \ + bst_result = Failed; \ + bs_trace_error_time_line(__VA_ARGS__); \ + } while (0) + +#define PASS(...) \ + do { \ + bst_result = Passed; \ + bs_trace_info_time(1, __VA_ARGS__); \ + } while (0) + +void test_tick(bs_time_t HW_device_time); +void test_init(void); +void backchannel_init(uint peer); +void backchannel_sync_send(void); +void backchannel_sync_wait(void); + +void bs_bt_utils_setup(void); +void wait_connected(void); +void wait_disconnected(void); +void scan_connect_to_first_result(void); +void disconnect(void); +void advertise_connectable(int id, bool persist); +void scan_expect_same_address(void); diff --git a/tests/bluetooth/bsim_bt/bsim_test_adv_resume/src/dut.c b/tests/bluetooth/bsim_bt/bsim_test_adv_resume/src/dut.c new file mode 100644 index 00000000000..072753064bc --- /dev/null +++ b/tests/bluetooth/bsim_bt/bsim_test_adv_resume/src/dut.c @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "bs_bt_utils.h" +#include "zephyr/bluetooth/addr.h" +#include "zephyr/bluetooth/bluetooth.h" +#include "zephyr/bluetooth/conn.h" +#include "zephyr/toolchain/gcc.h" + +#include +#include + +void dut_procedure(void) +{ + bs_bt_utils_setup(); + + printk("DUT start\n"); + + /* start scanning (using NRPA) */ + scan_connect_to_first_result(); + + advertise_connectable(0, 0); + wait_connected(); + printk("DUT is peripheral\n"); + + /* tester advertises using a new identity + * -> will get detected and connected to by DUT + */ + wait_connected(); + printk("DUT is central & peripheral\n"); + + /* restart advertiser: it will fail because we have run out of contexts. + * But since we pass the `persist` flag, it will start up as soon as a + * peripheral role is disconnected. + * + * We can't start it with the `persist` flag the first time, because adv + * will resume straight after the peripheral's connection completes, + * 'stealing' the last conn context and preventing the scanner from + * establishing a connection. + */ + advertise_connectable(0, 1); + + wait_disconnected(); + printk("DUT is central\n"); + scan_connect_to_first_result(); + + wait_disconnected(); + printk("DUT has no connections\n"); + + PASS("PASS\n"); +} diff --git a/tests/bluetooth/bsim_bt/bsim_test_adv_resume/src/main.c b/tests/bluetooth/bsim_bt/bsim_test_adv_resume/src/main.c new file mode 100644 index 00000000000..27d1dd1eddb --- /dev/null +++ b/tests/bluetooth/bsim_bt/bsim_test_adv_resume/src/main.c @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "bs_bt_utils.h" +#include "bstests.h" + +void dut_procedure(void); +void tester_peripheral_procedure(void); +void tester_central_procedure(void); + +static const struct bst_test_instance test_to_add[] = { + { + .test_id = "dut", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = dut_procedure, + }, + { + .test_id = "tester_peripheral", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = tester_peripheral_procedure, + }, + { + .test_id = "tester_central", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = tester_central_procedure, + }, + BSTEST_END_MARKER, +}; + +static struct bst_test_list *install(struct bst_test_list *tests) +{ + return bst_add_tests(tests, test_to_add); +}; + +bst_test_install_t test_installers[] = { install, NULL }; + +void main(void) +{ + bst_main(); +} diff --git a/tests/bluetooth/bsim_bt/bsim_test_adv_resume/src/tester.c b/tests/bluetooth/bsim_bt/bsim_test_adv_resume/src/tester.c new file mode 100644 index 00000000000..fc385f68891 --- /dev/null +++ b/tests/bluetooth/bsim_bt/bsim_test_adv_resume/src/tester.c @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "bs_bt_utils.h" +#include "zephyr/bluetooth/addr.h" +#include "zephyr/bluetooth/conn.h" + +#include + +#include + +#define TESTER_CENTRAL_ID 1 +#define TESTER_PERIPHERAL_ID 2 + +void tester_central_procedure(void) +{ + bs_bt_utils_setup(); + backchannel_init(TESTER_PERIPHERAL_ID); + printk("central tester start\n"); + + /* connect to DUT as central */ + scan_connect_to_first_result(); + wait_connected(); + backchannel_sync_send(); + /* DUT is peripheral */ + + /* wait until DUT connects to peripheral, and that it has disconnected + * from it afterwards. + */ + backchannel_sync_wait(); + + printk("disconnect central\n"); + disconnect(); + + /* DUT starts advertising again (with scanner's NRPA) + * should give wrong address + */ + scan_expect_same_address(); + + /* PASS/FAIL is called in the `device_found` callback. */ +} + +void tester_peripheral_procedure(void) +{ + bs_bt_utils_setup(); + backchannel_init(TESTER_CENTRAL_ID); + printk("peripheral tester start\n"); + + /* wait for central to connect to DUT */ + backchannel_sync_wait(); + + /* connect to DUT as peripheral */ + advertise_connectable(0, 0); + wait_connected(); + /* DUT is central & peripheral */ + + printk("disconnect peripheral\n"); + disconnect(); + /* DUT starts scanning again (using NRPA) */ + backchannel_sync_send(); + + PASS("PASS\n"); +} diff --git a/tests/bluetooth/bsim_bt/bsim_test_adv_resume/test_scripts/_compile.sh b/tests/bluetooth/bsim_bt/bsim_test_adv_resume/test_scripts/_compile.sh new file mode 100755 index 00000000000..584b5626e70 --- /dev/null +++ b/tests/bluetooth/bsim_bt/bsim_test_adv_resume/test_scripts/_compile.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Copyright 2022 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +set -eu +bash_source_dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" + +# Read variable definitions output by _env.sh +source <("${bash_source_dir}/_env.sh") + +# Place yourself in the test's root (i.e. ./../) +west build -b nrf52_bsim && \ + cp build/zephyr/zephyr.exe "${peripheral_exe}" diff --git a/tests/bluetooth/bsim_bt/bsim_test_adv_resume/test_scripts/_env.sh b/tests/bluetooth/bsim_bt/bsim_test_adv_resume/test_scripts/_env.sh new file mode 100755 index 00000000000..3b0628d6545 --- /dev/null +++ b/tests/bluetooth/bsim_bt/bsim_test_adv_resume/test_scripts/_env.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Copyright 2022 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 +set -eu +bash_source_dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" + +: "${BSIM_OUT_PATH:?BSIM_OUT_PATH must be defined}" + +test_name="$(basename "$(realpath "$bash_source_dir/..")")" +bsim_bin="${BSIM_OUT_PATH}/bin" +verbosity_level=2 +BOARD="${BOARD:-nrf52_bsim}" +simulation_id="$test_name" +central_exe="${bsim_bin}/bs_${BOARD}_tests_bluetooth_bsim_bt_${test_name}_prj_conf" +peripheral_exe="${bsim_bin}/bs_${BOARD}_tests_bluetooth_bsim_bt_${test_name}_prj_conf" + +function print_var { + # Print a shell-sourceable variable definition. + local var_name="$1" + local var_repr="${!var_name@Q}" + echo "$var_name=$var_repr" +} + +print_var test_name +print_var bsim_bin +print_var verbosity_level +print_var BOARD +print_var simulation_id +print_var central_exe +print_var peripheral_exe diff --git a/tests/bluetooth/bsim_bt/bsim_test_adv_resume/test_scripts/run_adv_resume.sh b/tests/bluetooth/bsim_bt/bsim_test_adv_resume/test_scripts/run_adv_resume.sh new file mode 100755 index 00000000000..5fce3fc3ac4 --- /dev/null +++ b/tests/bluetooth/bsim_bt/bsim_test_adv_resume/test_scripts/run_adv_resume.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Copyright 2022 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +set -eu +bash_source_dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" + +# Read variable definitions output by _env.sh +source <("${bash_source_dir}/_env.sh") + +process_ids="" +exit_code=0 + +function Execute() { + if [ ! -f $1 ]; then + echo -e " \e[91m$(pwd)/$(basename $1) cannot be found (did you forget to\ + compile it?)\e[39m" + exit 1 + fi + timeout 30 $@ & + process_ids="$process_ids $!" +} + +: "${BSIM_OUT_PATH:?BSIM_OUT_PATH must be defined}" + +cd ${BSIM_OUT_PATH}/bin + +Execute "$central_exe" \ + -v=${verbosity_level} -s=${simulation_id} -d=0 -testid=dut -RealEncryption=1 + +Execute "$peripheral_exe" \ + -v=${verbosity_level} -s=${simulation_id} -d=1 -testid=tester_central -RealEncryption=1 + +Execute "$peripheral_exe" \ + -v=${verbosity_level} -s=${simulation_id} -d=2 -testid=tester_peripheral -RealEncryption=1 + +Execute ./bs_2G4_phy_v1 -v=${verbosity_level} -s=${simulation_id} \ + -D=3 -sim_length=10e6 $@ + +# Uncomment (and comment the other peripheral line) to run DUT under a debugger +# gdb --args "$peripheral_exe" \ +# -v=${verbosity_level} -s=${simulation_id} -d=1 -testid=peripheral -RealEncryption=1 + +for process_id in $process_ids; do + wait $process_id || let "exit_code=$?" +done +exit $exit_code #the last exit code != 0 diff --git a/tests/bluetooth/bsim_bt/compile.sh b/tests/bluetooth/bsim_bt/compile.sh index 6e99d9ead19..74d7cba73c0 100755 --- a/tests/bluetooth/bsim_bt/compile.sh +++ b/tests/bluetooth/bsim_bt/compile.sh @@ -20,6 +20,7 @@ mkdir -p ${WORK_DIR} source ${ZEPHYR_BASE}/tests/bluetooth/bsim_bt/compile.source +app=tests/bluetooth/bsim_bt/bsim_test_adv_resume compile & app=tests/bluetooth/bsim_bt/bsim_test_bond_overwrite_allowed compile & app=tests/bluetooth/bsim_bt/bsim_test_bond_overwrite_denied compile & app=tests/bluetooth/bsim_bt/bsim_test_notify compile &