From f55cd0b266824f53cf834cc321c3960b2924205d Mon Sep 17 00:00:00 2001 From: Emil Gydesen Date: Fri, 21 Feb 2025 14:41:39 +0100 Subject: [PATCH] tests: Bluetooth: Add BT Tester GAP smoke test Add a babblesim test of the BT Tester doing a GAP smoke test connecting 2 BT testers using BTP. The purpose of this is to further increase the test coverage of the BT Tester in CI, as it is only being built, and runtime errors are typically not caught. Signed-off-by: Emil Gydesen --- tests/bluetooth/tester/CMakeLists.txt | 7 + tests/bluetooth/tester/boards/nrf52_bsim.conf | 13 ++ .../boards/nrf5340bsim_nrf5340_cpuapp.conf | 13 ++ tests/bluetooth/tester/src/btp.c | 13 ++ tests/bluetooth/tester/src/btp/btp.h | 2 + tests/bluetooth/tester/src/btp_gap.c | 12 +- tests/bluetooth/tester/src/main.c | 13 ++ tests/bsim/bluetooth/tester/CMakeLists.txt | 29 +++ tests/bsim/bluetooth/tester/Kconfig | 4 + tests/bsim/bluetooth/tester/Kconfig.sysbuild | 10 + tests/bsim/bluetooth/tester/prj.conf | 2 + tests/bsim/bluetooth/tester/src/btp.c | 176 ++++++++++++++++++ tests/bsim/bluetooth/tester/src/btp.h | 156 ++++++++++++++++ tests/bsim/bluetooth/tester/src/gap_central.c | 65 +++++++ .../bluetooth/tester/src/gap_peripheral.c | 44 +++++ tests/bsim/bluetooth/tester/src/test_main.c | 19 ++ tests/bsim/bluetooth/tester/sysbuild.cmake | 6 + tests/bsim/bluetooth/tester/testcase.yaml | 22 +++ .../bluetooth/tester/tests_scripts/gap.sh | 24 +++ 19 files changed, 627 insertions(+), 3 deletions(-) create mode 100644 tests/bluetooth/tester/boards/nrf52_bsim.conf create mode 100644 tests/bluetooth/tester/boards/nrf5340bsim_nrf5340_cpuapp.conf create mode 100644 tests/bsim/bluetooth/tester/CMakeLists.txt create mode 100644 tests/bsim/bluetooth/tester/Kconfig create mode 100644 tests/bsim/bluetooth/tester/Kconfig.sysbuild create mode 100644 tests/bsim/bluetooth/tester/prj.conf create mode 100644 tests/bsim/bluetooth/tester/src/btp.c create mode 100644 tests/bsim/bluetooth/tester/src/btp.h create mode 100644 tests/bsim/bluetooth/tester/src/gap_central.c create mode 100644 tests/bsim/bluetooth/tester/src/gap_peripheral.c create mode 100644 tests/bsim/bluetooth/tester/src/test_main.c create mode 100644 tests/bsim/bluetooth/tester/sysbuild.cmake create mode 100644 tests/bsim/bluetooth/tester/testcase.yaml create mode 100755 tests/bsim/bluetooth/tester/tests_scripts/gap.sh diff --git a/tests/bluetooth/tester/CMakeLists.txt b/tests/bluetooth/tester/CMakeLists.txt index 2493fd11f999..de2abff5f62f 100644 --- a/tests/bluetooth/tester/CMakeLists.txt +++ b/tests/bluetooth/tester/CMakeLists.txt @@ -7,6 +7,13 @@ LIST(APPEND QEMU_EXTRA_FLAGS -serial unix:/tmp/bt-stack-tester) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(tester) +if(CONFIG_SOC_SERIES_BSIM_NRFXX) +zephyr_include_directories( + ${BSIM_COMPONENTS_PATH}/libUtilv1/src/ + ${BSIM_COMPONENTS_PATH}/libPhyComv1/src/ + ) +endif() # CONFIG_SOC_SERIES_BSIM_NRFXX + zephyr_library_include_directories(${ZEPHYR_BASE}/samples/bluetooth) zephyr_library_include_directories(${ZEPHYR_BASE}/subsys/bluetooth/mesh) zephyr_library_include_directories(${ZEPHYR_BASE}/subsys/bluetooth/host) diff --git a/tests/bluetooth/tester/boards/nrf52_bsim.conf b/tests/bluetooth/tester/boards/nrf52_bsim.conf new file mode 100644 index 000000000000..1a38679ce066 --- /dev/null +++ b/tests/bluetooth/tester/boards/nrf52_bsim.conf @@ -0,0 +1,13 @@ +# CONFIG_TEST enforces minimal logging, which we don't want +CONFIG_TEST=n + +CONFIG_ASSERT=y +CONFIG_THREAD_NAME=y + +CONFIG_LOG=y +CONFIG_LOG_BACKEND_SHOW_COLOR=y + +CONFIG_LOG_DEFAULT_LEVEL=3 +CONFIG_BTTESTER_LOG_LEVEL_DBG=y + +CONFIG_UART_PIPE=n diff --git a/tests/bluetooth/tester/boards/nrf5340bsim_nrf5340_cpuapp.conf b/tests/bluetooth/tester/boards/nrf5340bsim_nrf5340_cpuapp.conf new file mode 100644 index 000000000000..1a38679ce066 --- /dev/null +++ b/tests/bluetooth/tester/boards/nrf5340bsim_nrf5340_cpuapp.conf @@ -0,0 +1,13 @@ +# CONFIG_TEST enforces minimal logging, which we don't want +CONFIG_TEST=n + +CONFIG_ASSERT=y +CONFIG_THREAD_NAME=y + +CONFIG_LOG=y +CONFIG_LOG_BACKEND_SHOW_COLOR=y + +CONFIG_LOG_DEFAULT_LEVEL=3 +CONFIG_BTTESTER_LOG_LEVEL_DBG=y + +CONFIG_UART_PIPE=n diff --git a/tests/bluetooth/tester/src/btp.c b/tests/bluetooth/tester/src/btp.c index 3951ea0aee1b..59d53e2acc64 100644 --- a/tests/bluetooth/tester/src/btp.c +++ b/tests/bluetooth/tester/src/btp.c @@ -185,6 +185,19 @@ static void uart_send(const uint8_t *data, size_t len) { uart_pipe_send(data, len); } +#elif defined(CONFIG_SOC_SERIES_BSIM_NRFXX) +extern void btp_register_tester(uint8_t *buffer, size_t len, uart_pipe_recv_cb cb); +extern void btp_send_to_bsim(const uint8_t *data, size_t len); + +static void uart_init(uint8_t *data) +{ + btp_register_tester(data, BTP_MTU, recv_cb); +} + +static void uart_send(const uint8_t *data, size_t len) +{ + btp_send_to_bsim(data, len); +} #else /* !CONFIG_UART_PIPE */ static uint8_t *recv_buf; static size_t recv_off; diff --git a/tests/bluetooth/tester/src/btp/btp.h b/tests/bluetooth/tester/src/btp/btp.h index 06dc07883989..2a8190a42f16 100644 --- a/tests/bluetooth/tester/src/btp/btp.h +++ b/tests/bluetooth/tester/src/btp/btp.h @@ -87,6 +87,8 @@ #define BTP_STATUS_VAL(err) (err) ? BTP_STATUS_FAILED : BTP_STATUS_SUCCESS +#define BTP_EVENT_OPCODE 0x80 + /* TODO indicate delay response, should be removed when all commands are * converted to cmd+status+ev pattern */ diff --git a/tests/bluetooth/tester/src/btp_gap.c b/tests/bluetooth/tester/src/btp_gap.c index 350988fc4dbb..a4d630df5c74 100644 --- a/tests/bluetooth/tester/src/btp_gap.c +++ b/tests/bluetooth/tester/src/btp_gap.c @@ -723,8 +723,9 @@ static uint8_t start_advertising(const void *cmd, uint16_t cmd_len, * type. */ if ((cmd_len < sizeof(*cp)) || - (cmd_len != sizeof(*cp) + cp->adv_data_len + cp->scan_rsp_len + - sizeof(duration) + sizeof(own_addr_type))) { + (cmd_len != sizeof(*cp) + cp->adv_data_len + cp->scan_rsp_len + sizeof(duration) + + sizeof(own_addr_type))) { + LOG_DBG("Invalid command length: %u", cmd_len); return BTP_STATUS_FAILED; } @@ -759,6 +760,7 @@ static uint8_t start_advertising(const void *cmd, uint16_t cmd_len, err = tester_gap_create_adv_instance(¶m, own_addr_type, ad, adv_len, sd, sd_len, NULL); if (err != 0) { + LOG_DBG("Failed to create adv instance: %d", err); return BTP_STATUS_FAILED; } @@ -771,7 +773,7 @@ static uint8_t start_advertising(const void *cmd, uint16_t cmd_len, /* BTP API don't allow to set empty scan response data. */ if (err < 0) { - LOG_ERR("Failed to start advertising"); + LOG_ERR("Failed to start advertising: %d"); return BTP_STATUS_FAILED; } @@ -892,6 +894,10 @@ static void store_adv(const bt_addr_le_t *addr, int8_t rssi, static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t evtype, struct net_buf_simple *buf_ad) { + char addr_str[BT_ADDR_LE_STR_LEN]; + bt_addr_le_to_str(addr, addr_str, sizeof(addr_str)); + LOG_ERR("Found remote device %s", addr_str); + /* if General/Limited Discovery - parse Advertising data to get flags */ if (!(discovery_flags & BTP_GAP_DISCOVERY_FLAG_LE_OBSERVE) && (evtype != BT_GAP_ADV_TYPE_SCAN_RSP)) { diff --git a/tests/bluetooth/tester/src/main.c b/tests/bluetooth/tester/src/main.c index 6951feab3ce1..954ded26fbfe 100644 --- a/tests/bluetooth/tester/src/main.c +++ b/tests/bluetooth/tester/src/main.c @@ -2,14 +2,22 @@ /* * Copyright (c) 2015-2016 Intel Corporation + * Copyright (c) 2025 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ +#include + #include +#include #include #include +#if defined(CONFIG_SOC_SERIES_BSIM_NRFXX) +#include "bstests.h" +#endif /* CONFIG_SOC_SERIES_BSIM_NRFXX */ + #include #define LOG_MODULE_NAME bttester_main LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_BTTESTER_LOG_LEVEL); @@ -19,5 +27,10 @@ LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_BTTESTER_LOG_LEVEL); int main(void) { tester_init(); + +#if defined(CONFIG_SOC_SERIES_BSIM_NRFXX) + bst_main(); +#endif /* CONFIG_SOC_SERIES_BSIM_NRFXX */ + return 0; } diff --git a/tests/bsim/bluetooth/tester/CMakeLists.txt b/tests/bsim/bluetooth/tester/CMakeLists.txt new file mode 100644 index 000000000000..1e04178e16e5 --- /dev/null +++ b/tests/bsim/bluetooth/tester/CMakeLists.txt @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(tester_bsim) + +# Add the BSIM tester +add_subdirectory(${ZEPHYR_BASE}/tests/bluetooth/tester ${CMAKE_BINARY_DIR}/tester_build) + +# This contains babblesim-specific helpers, e.g. device synchronization. +add_subdirectory(${ZEPHYR_BASE}/tests/bsim/babblekit babblekit) +target_link_libraries(app PRIVATE babblekit) + +# Add the testlib +add_subdirectory(${ZEPHYR_BASE}/tests/bluetooth/common/testlib testlib) +target_link_libraries(app PRIVATE testlib) + +zephyr_include_directories( + ${BSIM_COMPONENTS_PATH}/libUtilv1/src/ + ${BSIM_COMPONENTS_PATH}/libPhyComv1/src/ + ) + +target_sources(app PRIVATE + src/btp.c + src/gap_central.c + src/gap_peripheral.c + src/test_main.c +) diff --git a/tests/bsim/bluetooth/tester/Kconfig b/tests/bsim/bluetooth/tester/Kconfig new file mode 100644 index 000000000000..658bec444152 --- /dev/null +++ b/tests/bsim/bluetooth/tester/Kconfig @@ -0,0 +1,4 @@ +# Copyright (c) 2024-2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +source "${ZEPHYR_BASE}/tests/bluetooth/tester/Kconfig" diff --git a/tests/bsim/bluetooth/tester/Kconfig.sysbuild b/tests/bsim/bluetooth/tester/Kconfig.sysbuild new file mode 100644 index 000000000000..46737c8da6e9 --- /dev/null +++ b/tests/bsim/bluetooth/tester/Kconfig.sysbuild @@ -0,0 +1,10 @@ +# Copyright (c) 2023-2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +source "${ZEPHYR_BASE}/tests/bluetooth/tester/Kconfig.sysbuild" + +config NATIVE_SIMULATOR_PRIMARY_MCU_INDEX + int + # Let's pass the test arguments to the application MCU test + # otherwise by default they would have gone to the net core. + default 0 if $(BOARD_TARGET_STRING) = "NRF5340BSIM_NRF5340_CPUAPP" diff --git a/tests/bsim/bluetooth/tester/prj.conf b/tests/bsim/bluetooth/tester/prj.conf new file mode 100644 index 000000000000..a51c9cbb7aed --- /dev/null +++ b/tests/bsim/bluetooth/tester/prj.conf @@ -0,0 +1,2 @@ +# Please build using the sample configuration file: +# ${ZEPHYR_BASE}/tests/bluetooth/tester/prj.conf diff --git a/tests/bsim/bluetooth/tester/src/btp.c b/tests/bsim/bluetooth/tester/src/btp.c new file mode 100644 index 000000000000..daa0f329f128 --- /dev/null +++ b/tests/bsim/bluetooth/tester/src/btp.c @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "babblekit/testcase.h" + +#include "btp/btp.h" + +LOG_MODULE_REGISTER(bsim_btp, CONFIG_BTTESTER_LOG_LEVEL); + +static uint8_t *btp_buffer; +static size_t btp_buffer_len; +static uart_pipe_recv_cb btp_cb; + +K_FIFO_DEFINE(btp_rsp_fifo); +NET_BUF_POOL_FIXED_DEFINE(btp_rsp_pool, 1, BTP_MTU, 0, NULL); +K_FIFO_DEFINE(btp_evt_fifo); +NET_BUF_POOL_FIXED_DEFINE(btp_evt_pool, 100, BTP_MTU, 0, NULL); + +static void wait_for_response(const struct btp_hdr *cmd_hdr) +{ + const struct btp_hdr *rsp_hdr; + struct net_buf *buf; + + buf = k_fifo_get(&btp_rsp_fifo, K_SECONDS(1)); + TEST_ASSERT(buf != NULL); + + rsp_hdr = (struct btp_hdr *)buf->data; + TEST_ASSERT(rsp_hdr->len <= BTP_MTU, "len %u > %d", rsp_hdr->len, BTP_MTU); + + LOG_DBG("rsp service %u and opcode %u len %u", cmd_hdr->service, cmd_hdr->opcode, + rsp_hdr->len); + + TEST_ASSERT(rsp_hdr->service == cmd_hdr->service && rsp_hdr->opcode == cmd_hdr->opcode); + + net_buf_unref(buf); +} + +/* BTP communication is inspired from `uart_pipe_rx` to achieve a similar API */ +void btp_send_to_tester(const uint8_t *data, size_t len) +{ + const struct btp_hdr *cmd_hdr; + size_t offset = len; + + TEST_ASSERT(data != NULL); + TEST_ASSERT(btp_buffer != NULL); + TEST_ASSERT(len <= btp_buffer_len, "len %zu > %zu", len, btp_buffer_len); + TEST_ASSERT(len >= sizeof(*cmd_hdr), "len %zu <= %zu", len, sizeof(*cmd_hdr)); + + memcpy(btp_buffer, data, len); + + cmd_hdr = (const struct btp_hdr *)data; + LOG_DBG("cmd service %u and opcode %u", cmd_hdr->service, cmd_hdr->opcode); + btp_buffer = btp_cb(btp_buffer, &offset); + TEST_ASSERT(offset == 0U); + + wait_for_response(cmd_hdr); +} + +void btp_register_tester(uint8_t *buffer, size_t len, uart_pipe_recv_cb cb) +{ + TEST_ASSERT(cb != NULL); + TEST_ASSERT(buffer != NULL); + TEST_ASSERT(len > sizeof(struct btp_hdr), "len %zu", len); + TEST_ASSERT(len <= BTP_MTU, "len %zu > %d", len, BTP_MTU); + + btp_buffer = buffer; + btp_buffer_len = len; + btp_cb = cb; + + LOG_INF("btp registered with %p %zu %p", buffer, len, cb); +} + +void btp_send_to_bsim(const uint8_t *data, size_t len) +{ + static struct net_buf *buf; + bool full_evt; + + TEST_ASSERT(len >= 0); + TEST_ASSERT(data != NULL); + TEST_ASSERT(len <= BTP_MTU - sizeof(struct btp_hdr), "len %zu < %d", len, BTP_MTU); + TEST_ASSERT(buf != NULL || (buf == NULL && len == sizeof(struct btp_hdr)), "len %zu buf %p", + len, buf); + + /* tester_send_with_index always sends the header first, and then any additional information + * afterwards. We thus need to reassemble the full event. + */ + if (buf == NULL) { + const struct btp_hdr *hdr = (const struct btp_hdr *)data; + + LOG_DBG("hdr service %u and opcode %u", hdr->service, hdr->opcode); + TEST_ASSERT(hdr->opcode != BTP_STATUS, "hdr service %u", hdr->service); + + if (hdr->opcode < BTP_EVENT_OPCODE) { + buf = net_buf_alloc(&btp_rsp_pool, K_NO_WAIT); + TEST_ASSERT(buf != NULL); + } else { + buf = net_buf_alloc(&btp_evt_pool, K_NO_WAIT); + if (buf == NULL) { + /* Discard the oldest event */ + buf = k_fifo_get(&btp_evt_fifo, K_NO_WAIT); + TEST_ASSERT(buf != NULL); + net_buf_unref(buf); + + buf = net_buf_alloc(&btp_evt_pool, K_NO_WAIT); + TEST_ASSERT(buf != NULL); + } + } + + full_evt = hdr->len == 0; /* we are not awaiting additional data if hdr->len == 0*/ + } else { + /* If buf != NULL then we have received the hdr and this is the additional data for + * the event/response. We always assume that we receive the rest here + */ + const struct btp_hdr *hdr = (const struct btp_hdr *)buf->data; + + TEST_ASSERT(buf != NULL && len == hdr->len, "len %zu hdr len %u", len, hdr->len); + full_evt = true; + } + + net_buf_add_mem(buf, data, len); + + if (full_evt) { + const struct btp_hdr *hdr = (const struct btp_hdr *)buf->data; + + if (hdr->opcode < BTP_EVENT_OPCODE) { + k_fifo_put(&btp_rsp_fifo, buf); + } else { + k_fifo_put(&btp_evt_fifo, buf); + } + + buf = NULL; + } +} + +void btp_wait_for_evt(uint8_t service, uint8_t opcode, struct net_buf **out_buf) +{ + LOG_DBG("Waiting for evt with service %u and opcode %u", service, opcode); + + while (true) { + const struct btp_hdr *hdr; + struct net_buf *buf; + + buf = k_fifo_get(&btp_evt_fifo, K_FOREVER); + TEST_ASSERT(buf != NULL); + + LOG_HEXDUMP_DBG(buf->data, buf->len, "evt"); + hdr = net_buf_pull_mem(buf, sizeof(struct btp_hdr)); + + /* TODO: Verify length of event based on the service and opcode */ + if (hdr->service == service && hdr->opcode == opcode) { + if (out_buf != NULL) { + /* If the caller provides an out_buf, they are responsible for + * unref'ing it*/ + *out_buf = net_buf_ref(buf); + } + + net_buf_unref(buf); + return; + } + + net_buf_unref(buf); + } +} diff --git a/tests/bsim/bluetooth/tester/src/btp.h b/tests/bsim/bluetooth/tester/src/btp.h new file mode 100644 index 000000000000..f26ccec2f86e --- /dev/null +++ b/tests/bsim/bluetooth/tester/src/btp.h @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include + +#include "btp/btp.h" + +void btp_send_to_tester(const uint8_t *data, size_t len); +void btp_wait_for_evt(uint8_t service, uint8_t opcode, struct net_buf **out_buf); + +static inline void btp_core_register(uint8_t id) +{ + + NET_BUF_SIMPLE_DEFINE(cmd_buffer, BTP_MTU); + + struct btp_hdr *cmd_hdr; + struct btp_core_register_service_cmd *cmd; + + cmd_hdr = net_buf_simple_add(&cmd_buffer, sizeof(*cmd_hdr)); + cmd_hdr->service = BTP_SERVICE_ID_CORE; + cmd_hdr->opcode = BTP_CORE_REGISTER_SERVICE; + cmd_hdr->index = BTP_INDEX_NONE; + cmd = net_buf_simple_add(&cmd_buffer, sizeof(*cmd)); + cmd->id = id; + + cmd_hdr->len = cmd_buffer.len - sizeof(*cmd_hdr); + + btp_send_to_tester(cmd_buffer.data, cmd_buffer.len); +} + +static inline void btp_gap_set_discoverable(uint8_t discoverable) +{ + NET_BUF_SIMPLE_DEFINE(cmd_buffer, BTP_MTU); + + struct btp_hdr *cmd_hdr; + struct btp_gap_set_discoverable_cmd *cmd; + + cmd_hdr = net_buf_simple_add(&cmd_buffer, sizeof(*cmd_hdr)); + cmd_hdr->service = BTP_SERVICE_ID_GAP; + cmd_hdr->opcode = BTP_GAP_SET_DISCOVERABLE; + cmd_hdr->index = BTP_INDEX; + cmd = net_buf_simple_add(&cmd_buffer, sizeof(*cmd)); + cmd->discoverable = discoverable; + + cmd_hdr->len = cmd_buffer.len - sizeof(*cmd_hdr); + + btp_send_to_tester(cmd_buffer.data, cmd_buffer.len); +} + +static inline void btp_gap_start_advertising(uint8_t adv_data_len, uint8_t scan_rsp_len, + const uint8_t adv_sr_data[], uint32_t duration, + uint8_t own_addr_type) +{ + NET_BUF_SIMPLE_DEFINE(cmd_buffer, BTP_MTU); + + struct btp_hdr *cmd_hdr; + struct btp_gap_start_advertising_cmd *cmd; + + cmd_hdr = net_buf_simple_add(&cmd_buffer, sizeof(*cmd_hdr)); + cmd_hdr->service = BTP_SERVICE_ID_GAP; + cmd_hdr->opcode = BTP_GAP_START_ADVERTISING; + cmd_hdr->index = BTP_INDEX; + cmd = net_buf_simple_add(&cmd_buffer, sizeof(*cmd)); + cmd->adv_data_len = adv_data_len; + cmd->scan_rsp_len = scan_rsp_len; + if (adv_data_len > 0U) { + net_buf_simple_add_mem(&cmd_buffer, adv_sr_data, adv_data_len); + } + if (scan_rsp_len > 0U) { + net_buf_simple_add_mem(&cmd_buffer, adv_sr_data, scan_rsp_len); + } + net_buf_simple_add_le32(&cmd_buffer, duration); + net_buf_simple_add_u8(&cmd_buffer, own_addr_type); + + cmd_hdr->len = cmd_buffer.len - sizeof(*cmd_hdr); + + btp_send_to_tester(cmd_buffer.data, cmd_buffer.len); +} + +static inline void btp_gap_start_discovery(uint8_t flags) +{ + NET_BUF_SIMPLE_DEFINE(cmd_buffer, BTP_MTU); + + struct btp_hdr *cmd_hdr; + struct btp_gap_start_discovery_cmd *cmd; + + cmd_hdr = net_buf_simple_add(&cmd_buffer, sizeof(*cmd_hdr)); + cmd_hdr->service = BTP_SERVICE_ID_GAP; + cmd_hdr->opcode = BTP_GAP_START_DISCOVERY; + cmd_hdr->index = BTP_INDEX; + cmd = net_buf_simple_add(&cmd_buffer, sizeof(*cmd)); + cmd->flags = flags; + + cmd_hdr->len = cmd_buffer.len - sizeof(*cmd_hdr); + + btp_send_to_tester(cmd_buffer.data, cmd_buffer.len); +} + +static inline void btp_gap_stop_discovery(void) +{ + NET_BUF_SIMPLE_DEFINE(cmd_buffer, BTP_MTU); + + struct btp_hdr *cmd_hdr; + + cmd_hdr = net_buf_simple_add(&cmd_buffer, sizeof(*cmd_hdr)); + cmd_hdr->service = BTP_SERVICE_ID_GAP; + cmd_hdr->opcode = BTP_GAP_STOP_DISCOVERY; + cmd_hdr->index = BTP_INDEX; + cmd_hdr->len = cmd_buffer.len - sizeof(*cmd_hdr); + + btp_send_to_tester(cmd_buffer.data, cmd_buffer.len); +} + +static inline void btp_gap_connect(const bt_addr_le_t *address, uint8_t own_addr_type) +{ + NET_BUF_SIMPLE_DEFINE(cmd_buffer, BTP_MTU); + + struct btp_hdr *cmd_hdr; + struct btp_gap_connect_cmd *cmd; + + cmd_hdr = net_buf_simple_add(&cmd_buffer, sizeof(*cmd_hdr)); + cmd_hdr->service = BTP_SERVICE_ID_GAP; + cmd_hdr->opcode = BTP_GAP_CONNECT; + cmd_hdr->index = BTP_INDEX; + cmd = net_buf_simple_add(&cmd_buffer, sizeof(*cmd)); + bt_addr_le_copy(&cmd->address, address); + cmd->own_addr_type = own_addr_type; + + cmd_hdr->len = cmd_buffer.len - sizeof(*cmd_hdr); + + btp_send_to_tester(cmd_buffer.data, cmd_buffer.len); +} + +static inline void btp_gap_disconnect(const bt_addr_le_t *address) +{ + NET_BUF_SIMPLE_DEFINE(cmd_buffer, BTP_MTU); + + struct btp_hdr *cmd_hdr; + struct btp_gap_disconnect_cmd *cmd; + + cmd_hdr = net_buf_simple_add(&cmd_buffer, sizeof(*cmd_hdr)); + cmd_hdr->service = BTP_SERVICE_ID_GAP; + cmd_hdr->opcode = BTP_GAP_DISCONNECT; + cmd_hdr->index = BTP_INDEX; + cmd = net_buf_simple_add(&cmd_buffer, sizeof(*cmd)); + bt_addr_le_copy(&cmd->address, address); + + cmd_hdr->len = cmd_buffer.len - sizeof(*cmd_hdr); + + btp_send_to_tester(cmd_buffer.data, cmd_buffer.len); +} diff --git a/tests/bsim/bluetooth/tester/src/gap_central.c b/tests/bsim/bluetooth/tester/src/gap_central.c new file mode 100644 index 000000000000..afc4f9856fe7 --- /dev/null +++ b/tests/bsim/bluetooth/tester/src/gap_central.c @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#include +#include +#include +#include +#include + +#include "babblekit/testcase.h" +#include "bstests.h" + +#include "btp.h" + +LOG_MODULE_REGISTER(bsim_gap_central, CONFIG_BTTESTER_LOG_LEVEL); + +static void test_gap_central(void) +{ + struct btp_gap_device_found_ev *device_found_ev; + char addr_str[BT_ADDR_LE_STR_LEN]; + bt_addr_le_t remote_addr; + struct net_buf *evt; + btp_wait_for_evt(BTP_SERVICE_ID_CORE, BTP_CORE_EV_IUT_READY, NULL); + + btp_core_register(BTP_SERVICE_ID_GAP); + btp_gap_start_discovery(BTP_GAP_DISCOVERY_FLAG_LE); + btp_wait_for_evt(BTP_SERVICE_ID_GAP, BTP_GAP_EV_DEVICE_FOUND, &evt); + + device_found_ev = net_buf_pull_mem(evt, sizeof(*device_found_ev)); + bt_addr_le_copy(&remote_addr, &device_found_ev->address); + net_buf_unref(evt); + bt_addr_le_to_str(&remote_addr, addr_str, sizeof(addr_str)); + LOG_INF("Found remote device %s", addr_str); + + btp_gap_stop_discovery(); + btp_gap_connect(&remote_addr, BTP_GAP_ADDR_TYPE_IDENTITY); + btp_wait_for_evt(BTP_SERVICE_ID_GAP, BTP_GAP_EV_DEVICE_CONNECTED, NULL); + + /* Keep alive for a little while */ + k_sleep(K_SECONDS(10)); + + btp_gap_disconnect(&remote_addr); + btp_wait_for_evt(BTP_SERVICE_ID_GAP, BTP_GAP_EV_DEVICE_DISCONNECTED, NULL); + + TEST_PASS("PASSED\n"); +} + +static const struct bst_test_instance test_sample[] = { + { + .test_id = "gap_central", + .test_descr = "Smoketest for the GAP central BT Tester behavior", + .test_main_f = test_gap_central, + }, + BSTEST_END_MARKER, +}; + +struct bst_test_list *test_gap_central_install(struct bst_test_list *tests) +{ + return bst_add_tests(tests, test_sample); +} diff --git a/tests/bsim/bluetooth/tester/src/gap_peripheral.c b/tests/bsim/bluetooth/tester/src/gap_peripheral.c new file mode 100644 index 000000000000..736224778328 --- /dev/null +++ b/tests/bsim/bluetooth/tester/src/gap_peripheral.c @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#include +#include +#include + +#include "babblekit/testcase.h" +#include "bstests.h" + +#include "btp.h" + +static void test_gap_peripheral(void) +{ + btp_wait_for_evt(BTP_SERVICE_ID_CORE, BTP_CORE_EV_IUT_READY, NULL); + + btp_core_register(BTP_SERVICE_ID_GAP); + btp_gap_set_discoverable(BTP_GAP_GENERAL_DISCOVERABLE); + btp_gap_start_advertising(0U, 0U, NULL, 0xFFFFU /* unused in Zephyr */, + BT_HCI_OWN_ADDR_PUBLIC); + btp_wait_for_evt(BTP_SERVICE_ID_GAP, BTP_GAP_EV_DEVICE_CONNECTED, NULL); + btp_wait_for_evt(BTP_SERVICE_ID_GAP, BTP_GAP_EV_DEVICE_DISCONNECTED, NULL); + + TEST_PASS("PASSED\n"); +} + +static const struct bst_test_instance test_sample[] = { + { + .test_id = "gap_peripheral", + .test_descr = "Smoketest for the GAP central BT Tester behavior", + .test_main_f = test_gap_peripheral, + }, + BSTEST_END_MARKER, +}; + +struct bst_test_list *test_gap_peripheral_install(struct bst_test_list *tests) +{ + return bst_add_tests(tests, test_sample); +} diff --git a/tests/bsim/bluetooth/tester/src/test_main.c b/tests/bsim/bluetooth/tester/src/test_main.c new file mode 100644 index 000000000000..1da2bcc9cd5c --- /dev/null +++ b/tests/bsim/bluetooth/tester/src/test_main.c @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include + +#include "bstests.h" + +extern struct bst_test_list *test_gap_central_install(struct bst_test_list *tests); +extern struct bst_test_list *test_gap_peripheral_install(struct bst_test_list *tests); + +bst_test_install_t test_installers[] = { + test_gap_central_install, + test_gap_peripheral_install, + NULL, +}; + +/* bst_main will be called by the BT tester's `main` function */ diff --git a/tests/bsim/bluetooth/tester/sysbuild.cmake b/tests/bsim/bluetooth/tester/sysbuild.cmake new file mode 100644 index 000000000000..5ef3f3da88b4 --- /dev/null +++ b/tests/bsim/bluetooth/tester/sysbuild.cmake @@ -0,0 +1,6 @@ +# Copyright (c) 2023-2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +include(${ZEPHYR_BASE}/tests/bluetooth/tester/sysbuild.cmake) + +native_simulator_set_primary_mcu_index(${DEFAULT_IMAGE} ${NET_APP}) diff --git a/tests/bsim/bluetooth/tester/testcase.yaml b/tests/bsim/bluetooth/tester/testcase.yaml new file mode 100644 index 000000000000..4cbffb011b8a --- /dev/null +++ b/tests/bsim/bluetooth/tester/testcase.yaml @@ -0,0 +1,22 @@ +common: + build_only: true + tags: + - bluetooth + harness: bsim + harness_config: + bsim_exe_name: tests_bsim_bluetooth_tester_prj_conf + extra_args: + - CONF_FILE=${ZEPHYR_BASE}/tests/bluetooth/tester/prj.conf + +tests: + bluetooth.tester_nrf52_bsim: + platform_allow: + - nrf52_bsim/native + extra_args: + - EXTRA_CONF_FILE=${ZEPHYR_BASE}/tests/bluetooth/tester/boards/nrf52_bsim.conf + bluetooth.tester_nrf5340bsim: + sysbuild: true + platform_allow: + - nrf5340bsim/nrf5340/cpuapp + extra_args: + - EXTRA_CONF_FILE=${ZEPHYR_BASE}/tests/bluetooth/tester/boards/nrf5340bsim_nrf5340_cpuapp.conf diff --git a/tests/bsim/bluetooth/tester/tests_scripts/gap.sh b/tests/bsim/bluetooth/tester/tests_scripts/gap.sh new file mode 100755 index 000000000000..bf6f643e337d --- /dev/null +++ b/tests/bsim/bluetooth/tester/tests_scripts/gap.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Copyright 2023-2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +# Smoketest for GAP BTP commands with the BT tester + +simulation_id="tester_gap" +verbosity_level=2 +EXECUTE_TIMEOUT=20 + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +cd ${BSIM_OUT_PATH}/bin + +Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_tester_prj_conf \ + -v=${verbosity_level} -s=${simulation_id} -d=0 -RealEncryption=1 -testid=gap_central + +Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_tester_prj_conf \ + -v=${verbosity_level} -s=${simulation_id} -d=1 -RealEncryption=1 -testid=gap_peripheral + +Execute ./bs_2G4_phy_v1 -v=${verbosity_level} -s=${simulation_id} \ + -D=2 -sim_length=20e6 $@ -argschannel -at=40 + +wait_for_background_jobs #Wait for all programs in background and return != 0 if any fails