From 3581a1534f307dc47116af69315f6142390aaa55 Mon Sep 17 00:00:00 2001 From: Aleksander Wasaznik Date: Mon, 18 Sep 2023 17:26:25 +0200 Subject: [PATCH] Bluetooth: Host: Fix GATT Long Read for EATT When EATT is established, the value returned from `bt_att_get_mtu` is not useful to determine the ATT_MTU that applies to a ATT response. This is because `bt_att_get_mtu` may return the value for a different bearer than the request is serviced on. To fix this, the params struct for the GATT read operation is given a new field that will record the ATT_MTU that applies to this ATT operation. This value is then used to determine if the GATT long read operation is concluded. Fixes: https://github.com/zephyrproject-rtos/zephyr/issues/61741 Signed-off-by: Aleksander Wasaznik --- include/zephyr/bluetooth/gatt.h | 20 +- subsys/bluetooth/host/att.c | 7 + subsys/bluetooth/host/att_internal.h | 2 + subsys/bluetooth/host/gatt.c | 25 +- .../host/att/long_read/CMakeLists.txt | 24 ++ .../bluetooth/host/att/long_read/_build.sh | 14 + .../bsim/bluetooth/host/att/long_read/main.c | 229 ++++++++++ .../bluetooth/host/att/long_read/prj.conf | 26 ++ .../bsim/bluetooth/host/att/long_read/run.sh | 26 ++ .../host/att/long_read/testlib/adv.c | 83 ++++ .../host/att/long_read/testlib/adv.h | 7 + .../host/att/long_read/testlib/att_read.c | 390 ++++++++++++++++++ .../host/att/long_read/testlib/att_read.h | 39 ++ .../host/att/long_read/testlib/att_write.c | 74 ++++ .../host/att/long_read/testlib/att_write.h | 9 + .../host/att/long_read/testlib/bs_macro.h | 23 ++ .../host/att/long_read/testlib/bs_main.c | 31 ++ .../host/att/long_read/testlib/bs_sync.c | 107 +++++ .../host/att/long_read/testlib/bs_sync.h | 5 + .../host/att/long_read/testlib/conn_ref.h | 37 ++ .../host/att/long_read/testlib/conn_wait.c | 66 +++ .../host/att/long_read/testlib/conn_wait.h | 4 + .../host/att/long_read/testlib/connect.c | 85 ++++ .../host/att/long_read/testlib/connect.h | 7 + .../host/att/long_read/testlib/log_utils.h | 36 ++ .../host/att/long_read/testlib/scan.c | 98 +++++ .../host/att/long_read/testlib/scan.h | 7 + .../host/att/long_read/testlib/security.c | 108 +++++ .../host/att/long_read/testlib/security.h | 7 + tests/bsim/bluetooth/host/compile.sh | 1 + 30 files changed, 1593 insertions(+), 4 deletions(-) create mode 100644 tests/bsim/bluetooth/host/att/long_read/CMakeLists.txt create mode 100755 tests/bsim/bluetooth/host/att/long_read/_build.sh create mode 100644 tests/bsim/bluetooth/host/att/long_read/main.c create mode 100644 tests/bsim/bluetooth/host/att/long_read/prj.conf create mode 100755 tests/bsim/bluetooth/host/att/long_read/run.sh create mode 100644 tests/bsim/bluetooth/host/att/long_read/testlib/adv.c create mode 100644 tests/bsim/bluetooth/host/att/long_read/testlib/adv.h create mode 100644 tests/bsim/bluetooth/host/att/long_read/testlib/att_read.c create mode 100644 tests/bsim/bluetooth/host/att/long_read/testlib/att_read.h create mode 100644 tests/bsim/bluetooth/host/att/long_read/testlib/att_write.c create mode 100644 tests/bsim/bluetooth/host/att/long_read/testlib/att_write.h create mode 100644 tests/bsim/bluetooth/host/att/long_read/testlib/bs_macro.h create mode 100644 tests/bsim/bluetooth/host/att/long_read/testlib/bs_main.c create mode 100644 tests/bsim/bluetooth/host/att/long_read/testlib/bs_sync.c create mode 100644 tests/bsim/bluetooth/host/att/long_read/testlib/bs_sync.h create mode 100644 tests/bsim/bluetooth/host/att/long_read/testlib/conn_ref.h create mode 100644 tests/bsim/bluetooth/host/att/long_read/testlib/conn_wait.c create mode 100644 tests/bsim/bluetooth/host/att/long_read/testlib/conn_wait.h create mode 100644 tests/bsim/bluetooth/host/att/long_read/testlib/connect.c create mode 100644 tests/bsim/bluetooth/host/att/long_read/testlib/connect.h create mode 100644 tests/bsim/bluetooth/host/att/long_read/testlib/log_utils.h create mode 100644 tests/bsim/bluetooth/host/att/long_read/testlib/scan.c create mode 100644 tests/bsim/bluetooth/host/att/long_read/testlib/scan.h create mode 100644 tests/bsim/bluetooth/host/att/long_read/testlib/security.c create mode 100644 tests/bsim/bluetooth/host/att/long_read/testlib/security.h diff --git a/include/zephyr/bluetooth/gatt.h b/include/zephyr/bluetooth/gatt.h index 7d4ac72495b0824..6b31e5dc92baced 100644 --- a/include/zephyr/bluetooth/gatt.h +++ b/include/zephyr/bluetooth/gatt.h @@ -1572,6 +1572,8 @@ struct bt_gatt_read_params { #if defined(CONFIG_BT_EATT) enum bt_att_chan_opt chan_opt; #endif /* CONFIG_BT_EATT */ + /** Internal */ + uint16_t _att_mtu; }; /** @brief Read Attribute Value by handle @@ -1582,9 +1584,21 @@ struct bt_gatt_read_params { * depending on how many instances of given the UUID exists with the * start_handle being updated for each instance. * - * If an instance does contain a long value which cannot be read entirely the - * caller will need to read the remaining data separately using the handle and - * offset. + * To perform a GATT Long Read procedure, start with a Characteristic Value + * Read (by setting @c offset @c 0 and @c handle_count @c 1) and then return + * @ref BT_GATT_ITER_CONTINUE from the callback. This is equivalent to calling + * @ref bt_gatt_read again, but with the correct offset to continue the read. + * This may be repeated until the procedure is complete, which is signaled by + * the callback being called with @p data set to @c NULL. + * + * Note that returning @ref BT_GATT_ITER_CONTINUE is really starting a new ATT + * operation, so this can fail to allocate resources. However, all API errors + * are reported as if the server returned @ref BT_ATT_ERR_UNLIKELY. There is no + * way to distinguish between this condition and a @ref BT_ATT_ERR_UNLIKELY + * response from the server itself. + * + * Note that the effect of returning @ref BT_GATT_ITER_CONTINUE from the + * callback varies depending on the type of read operation. * * The Response comes in callback @p params->func. The callback is run from * the context specified by 'config BT_RECV_CONTEXT'. diff --git a/subsys/bluetooth/host/att.c b/subsys/bluetooth/host/att.c index 640c3acef90ab64..a27ec3119d0701d 100644 --- a/subsys/bluetooth/host/att.c +++ b/subsys/bluetooth/host/att.c @@ -447,12 +447,19 @@ static int chan_req_send(struct bt_att_chan *chan, struct bt_att_req *req) buf = req->buf; req->buf = NULL; + /* This lock makes sure the value of `bt_att_mtu(chan)` does not + * change. + */ + k_sched_lock(); err = bt_att_chan_send(chan, buf); if (err) { /* We still have the ownership of the buffer */ req->buf = buf; chan->req = NULL; + } else { + bt_gatt_req_set_mtu(req, bt_att_mtu(chan)); } + k_sched_unlock(); return err; } diff --git a/subsys/bluetooth/host/att_internal.h b/subsys/bluetooth/host/att_internal.h index 2910c018e851ad6..a45184a59c76cc7 100644 --- a/subsys/bluetooth/host/att_internal.h +++ b/subsys/bluetooth/host/att_internal.h @@ -347,3 +347,5 @@ bool bt_att_tx_meta_data_match(const struct net_buf *buf, bt_gatt_complete_func_ #endif /* CONFIG_BT_EATT */ bool bt_att_chan_opt_valid(struct bt_conn *conn, enum bt_att_chan_opt chan_opt); + +void bt_gatt_req_set_mtu(struct bt_att_req *req, uint16_t mtu); diff --git a/subsys/bluetooth/host/gatt.c b/subsys/bluetooth/host/gatt.c index 069082ec67b0959..8266fe36daee1af 100644 --- a/subsys/bluetooth/host/gatt.c +++ b/subsys/bluetooth/host/gatt.c @@ -2,6 +2,7 @@ /* * Copyright (c) 2015-2016 Intel Corporation + * Copyright (c) 2023 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ @@ -4621,8 +4622,11 @@ static void gatt_read_rsp(struct bt_conn *conn, uint8_t err, const void *pdu, * If the Characteristic Value is greater than (ATT_MTU - 1) octets * in length, the Read Long Characteristic Value procedure may be used * if the rest of the Characteristic Value is required. + * + * Note: Both BT_ATT_OP_READ_RSP and BT_ATT_OP_READ_BLOB_RSP + * have an overhead of one octet. */ - if (length < (bt_att_get_mtu(conn) - 1)) { + if (length < (params->_att_mtu - 1)) { params->func(conn, 0, params, NULL, 0); return; } @@ -6277,3 +6281,22 @@ void bt_gatt_disconnected(struct bt_conn *conn) remove_cf_cfg(conn); #endif } + +void bt_gatt_req_set_mtu(struct bt_att_req *req, uint16_t mtu) +{ + IF_ENABLED(CONFIG_BT_GATT_CLIENT, ({ + if (req->func == gatt_read_rsp) { + struct bt_gatt_read_params *params = req->user_data; + + __ASSERT_NO_MSG(params); + params->_att_mtu = mtu; + return; + } + })); + + /* Otherwise: This request type does not have an `_att_mtu` + * params field or any other method to get this value, so we can + * just drop it here. Feel free to add this capability to other + * request types if needed. + */ +} diff --git a/tests/bsim/bluetooth/host/att/long_read/CMakeLists.txt b/tests/bsim/bluetooth/host/att/long_read/CMakeLists.txt new file mode 100644 index 000000000000000..04bd66411533acb --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/CMakeLists.txt @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr HINTS $ENV{ZEPHYR_BASE}) +project(app) + +target_sources(app PRIVATE + testlib/bs_main.c + testlib/adv.c + testlib/connect.c + testlib/scan.c + testlib/security.c + testlib/att_read.c + testlib/att_write.c + testlib/bs_sync.c + testlib/conn_wait.c + main.c +) + +zephyr_include_directories( + ${BSIM_COMPONENTS_PATH}/libPhyComv1/src/ + ${BSIM_COMPONENTS_PATH}/libUtilv1/src/ +) diff --git a/tests/bsim/bluetooth/host/att/long_read/_build.sh b/tests/bsim/bluetooth/host/att/long_read/_build.sh new file mode 100755 index 000000000000000..637512b3826822c --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/_build.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -eu +dotslash="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" +bin_dir="${BSIM_OUT_PATH}/bin" +BOARD="${BOARD:-nrf52_bsim}" + +cd "${dotslash}" + +compile_path="${bin_dir}/bs_${BOARD}_" +compile_path+="$(realpath --relative-to "$(west topdir)"/zephyr prj.conf | tr /. _)" + +west build -b nrf52_bsim +cp -v build/zephyr/zephyr.exe "${compile_path}" diff --git a/tests/bsim/bluetooth/host/att/long_read/main.c b/tests/bsim/bluetooth/host/att/long_read/main.c new file mode 100644 index 000000000000000..64d75ce0be025fe --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/main.c @@ -0,0 +1,229 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "testlib/adv.h" +#include "testlib/att_read.h" +#include "testlib/att_write.h" +#include "testlib/bs_macro.h" +#include "testlib/bs_sync.h" +#include "testlib/conn_ref.h" +#include "testlib/conn_wait.h" +#include "testlib/connect.h" +#include "testlib/log_utils.h" +#include "testlib/scan.h" +#include "testlib/security.h" + +#define CENTRAL_DEVICE_NBR 0 +#define PERIPHERAL_DEVICE_NBR 1 + +LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG); + +#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 ssize_t read_mtu_validation_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("Server side buf_len %u", buf_len); + + /* Note: We assume `buf_len` is equal to the usable payload + * capacity of the response PDU. I.e. `(ATT_MTU - 1)` for + * BT_ATT_OP_READ_RSP and BT_ATT_OP_READ_BLOB_RSP. + */ + + /* Send back a full PDU on the first read (on offset 0). Then an + * not full one for the second read to conlude the long read.. + */ + read_len = buf_len; + if (offset > 0) { + __ASSERT_NO_MSG(read_len > 0); + /* The second PDU is one-less-than-full to test for off + * by one errors. + */ + read_len -= 1; + } + + /* If the ATT_MTU is too large, sending a one-less-than-full + * response would exeed the max attribute length limit. + */ + __ASSERT(buf_len < (BT_ATT_MAX_ATTRIBUTE_LEN / 2), + "The EATT buffer is too large for this test."); + + /* Ensure the padding bytes (that are not overwritten later in + * this function) are initialized. + */ + memset(buf, 0, read_len); + + /* Echo back the requested read size in the first two bytes of + * each read. + */ + __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_mtu_validation_chrc, NULL, NULL), +}; + +static struct bt_gatt_service svc = { + .attrs = attrs, + .attr_count = ARRAY_SIZE(attrs), +}; + +static void find_the_chrc(struct bt_conn *conn, uint16_t *chrc_value_handle) +{ + uint16_t svc_handle; + uint16_t svc_end_handle; + uint16_t chrc_end_handle; + + EXPECT_ZERO(bt_testlib_gatt_discover_primary(&svc_handle, &svc_end_handle, conn, UUID_1, 1, + 0xffff)); + + LOG_INF("svc_handle: %u, svc_end_handle: %u", svc_handle, svc_end_handle); + + EXPECT_ZERO(bt_testlib_gatt_discover_characteristic(chrc_value_handle, &chrc_end_handle, + NULL, conn, UUID_2, (svc_handle + 1), + svc_end_handle)); + + LOG_INF("chrc_value_handle: %u, chrc_end_handle: %u", *chrc_value_handle, chrc_end_handle); +} + +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 void test_long_read(enum bt_att_chan_opt bearer, uint16_t chrc_value_handle, + struct bt_conn *conn) +{ + bool central = (get_device_nbr() == CENTRAL_DEVICE_NBR); + + if (central) { + size_t read_count; + + NET_BUF_SIMPLE_DEFINE(attr_value_buf, BT_ATT_MAX_ATTRIBUTE_LEN); + + /* Perform the whole long read operation. */ + EXPECT_ZERO(btt_gatt_long_read(&attr_value_buf, NULL, conn, bearer, + chrc_value_handle, 0)); + + /* Parse the read attribute value to verify the + * integrity of the transfer. + * + * Each response starts with the length of the whole + * response and the rest is zero-padded. + */ + for (read_count = 0; attr_value_buf.len; read_count++) { + uint16_t encoded_len; + uint16_t padding_size; + + LOG_INF("Verifying read %u", read_count); + + __ASSERT(attr_value_buf.len >= sizeof(encoded_len), + "Incomplete encoded length"); + encoded_len = net_buf_simple_pull_le16(&attr_value_buf); + + padding_size = (encoded_len - sizeof(uint16_t)); + LOG_INF("Padding size %u", padding_size); + + /* Check and discard padding. */ + for (uint16_t i = 0; i < padding_size; i++) { + __ASSERT(attr_value_buf.len, "Unexpected end of buffer"); + __ASSERT(net_buf_simple_pull_u8(&attr_value_buf) == 0, + "Expected a padding byte at %u", i); + } + } + LOG_INF("Verified %u reads", read_count); + __ASSERT(read_count > 1, "Expected at least two reads"); + } +} + +void the_test(void) +{ + bool central = (get_device_nbr() == CENTRAL_DEVICE_NBR); + bool peripheral = (get_device_nbr() == PERIPHERAL_DEVICE_NBR); + bt_addr_le_t adva; + struct bt_conn *conn = NULL; + uint16_t chrc_value_handle = 0; + + if (peripheral) { + EXPECT_ZERO(bt_gatt_service_register(&svc)); + } + + bt_enable_quiet(); + + if (peripheral) { + EXPECT_ZERO(bt_set_name("peripheral")); + EXPECT_ZERO(bt_testlib_adv_conn( + &conn, BT_ID_DEFAULT, + (BT_LE_ADV_OPT_USE_NAME | BT_LE_ADV_OPT_FORCE_NAME_IN_AD))); + } + + if (central) { + 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); + }; + } + + bs_sync_all_log("Connected"); + + /* Perform discovery. */ + if (central) { + find_the_chrc(conn, &chrc_value_handle); + } + + bs_sync_all_log("Testing UATT"); + test_long_read(BT_ATT_CHAN_OPT_UNENHANCED_ONLY, chrc_value_handle, conn); + + bs_sync_all_log("Testing EATT"); + test_long_read(BT_ATT_CHAN_OPT_ENHANCED_ONLY, chrc_value_handle, conn); + + bs_sync_all_log("Test Complete"); + + PASS("Test complete\n"); +} diff --git a/tests/bsim/bluetooth/host/att/long_read/prj.conf b/tests/bsim/bluetooth/host/att/long_read/prj.conf new file mode 100644 index 000000000000000..1e5e8dc8b418099 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/prj.conf @@ -0,0 +1,26 @@ +CONFIG_ASSERT=y +CONFIG_BOOT_BANNER=n +CONFIG_BT_BUF_ACL_RX_SIZE=204 +CONFIG_BT_CENTRAL=y +CONFIG_BT_DEVICE_NAME_DYNAMIC=y +CONFIG_BT_EATT=y +CONFIG_BT_EXT_ADV=y +CONFIG_BT_GATT_CLIENT=y +CONFIG_BT_GATT_DYNAMIC_DB=y +CONFIG_BT_L2CAP_DYNAMIC_CHANNEL=y +CONFIG_BT_L2CAP_ECRED=y +CONFIG_BT_L2CAP_TX_MTU=200 +CONFIG_BT_MAX_CONN=3 +CONFIG_BT_MAX_PAIRED=2 +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_PRIVACY=n +CONFIG_BT_SMP=y +CONFIG_BT_TESTING=y +CONFIG_BT=y +CONFIG_FLASH_MAP=y +CONFIG_FLASH_PAGE_LAYOUT=y +CONFIG_FLASH=y +CONFIG_LOG_BACKEND_FORMAT_TIMESTAMP=n +CONFIG_LOG_RUNTIME_FILTERING=y +CONFIG_LOG_TAG_MAX_LEN=20 +CONFIG_LOG=y diff --git a/tests/bsim/bluetooth/host/att/long_read/run.sh b/tests/bsim/bluetooth/host/att/long_read/run.sh new file mode 100755 index 000000000000000..b1b7a5c6eb03fc1 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/run.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -eu +dotslash="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" +bin_dir="${BSIM_OUT_PATH}/bin" +BOARD="${BOARD:-nrf52_bsim}" + +cd "${dotslash}" + +compile_path="${bin_dir}/bs_${BOARD}_" +compile_path+="$(realpath --relative-to "$(west topdir)"/zephyr prj.conf | tr /. _)" + +args_all=(-s=long_read -D=2) +args_dev=(-v=2 -RealEncryption=1 -testid=the_test) +sim_seconds=60 + +echo "Simulation time: $sim_seconds seconds" + +# bs_2G4_phy_v1 requires pwd to at its location +cd "${BSIM_OUT_PATH}/bin" + +("${compile_path}" "${args_all[@]}" "${args_dev[@]}" -d=0 || echo d0 $?) & +("${compile_path}" "${args_all[@]}" "${args_dev[@]}" -d=1 || echo d1 $?) & +(./bs_2G4_phy_v1 "${args_all[@]}" -v=6 -sim_length=$((sim_seconds * 10 ** 6)) || echo phy $?) & + +wait diff --git a/tests/bsim/bluetooth/host/att/long_read/testlib/adv.c b/tests/bsim/bluetooth/host/att/long_read/testlib/adv.c new file mode 100644 index 000000000000000..e67d3bb6fe8ecce --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/adv.c @@ -0,0 +1,83 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct bt_testlib_adv_ctx { + struct bt_conn **result; + struct k_condvar done; +}; + +/* Context pool (with capacity of one). */ +static K_SEM_DEFINE(g_ctx_free, 1, 1); +static K_MUTEX_DEFINE(g_ctx_lock); +static struct bt_testlib_adv_ctx *g_ctx; + +static void connected_cb(struct bt_le_ext_adv *adv, struct bt_le_ext_adv_connected_info *info) +{ + k_mutex_lock(&g_ctx_lock, K_FOREVER); + + if (g_ctx->result) { + *g_ctx->result = bt_conn_ref(info->conn); + } + k_condvar_signal(&g_ctx->done); + + k_mutex_unlock(&g_ctx_lock); +} + +int bt_testlib_adv_conn(struct bt_conn **conn, int id, uint32_t adv_options) +{ + int api_err; + struct bt_le_ext_adv *adv = NULL; + struct bt_le_adv_param param = {}; + struct bt_testlib_adv_ctx ctx = { + .result = conn, + }; + static const struct bt_le_ext_adv_cb cb = { + .connected = connected_cb, + }; + + param.id = id; + param.interval_min = BT_GAP_ADV_FAST_INT_MIN_1; + param.interval_max = BT_GAP_ADV_FAST_INT_MAX_1; + param.options |= BT_LE_ADV_OPT_CONNECTABLE; + param.options |= adv_options; + + k_condvar_init(&ctx.done); + + k_sem_take(&g_ctx_free, K_FOREVER); + k_mutex_lock(&g_ctx_lock, K_FOREVER); + g_ctx = &ctx; + + api_err = bt_le_ext_adv_create(¶m, &cb, &adv); + if (!api_err) { + api_err = bt_le_ext_adv_start(adv, BT_LE_EXT_ADV_START_DEFAULT); + } + + if (!api_err) { + k_condvar_wait(&ctx.done, &g_ctx_lock, K_FOREVER); + } + + /* Delete adv before giving semaphore so that it's potentially available + * for the next taker of the semaphore. + */ + if (adv) { + bt_le_ext_adv_delete(adv); + } + + g_ctx = NULL; + k_mutex_unlock(&g_ctx_lock); + k_sem_give(&g_ctx_free); + + return api_err; +} diff --git a/tests/bsim/bluetooth/host/att/long_read/testlib/adv.h b/tests/bsim/bluetooth/host/att/long_read/testlib/adv.h new file mode 100644 index 000000000000000..a9d429d5ad29a01 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/adv.h @@ -0,0 +1,7 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +int bt_testlib_adv_conn(struct bt_conn **conn, int id, uint32_t adv_options); diff --git a/tests/bsim/bluetooth/host/att/long_read/testlib/att_read.c b/tests/bsim/bluetooth/host/att/long_read/testlib/att_read.c new file mode 100644 index 000000000000000..d2fa5cb5ab51a64 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/att_read.c @@ -0,0 +1,390 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "att_read.h" +#include + +LOG_MODULE_REGISTER(att_read, LOG_LEVEL_DBG); + +struct bt_testlib_att_read_closure { + uint8_t att_err; + struct bt_conn *conn; + struct bt_gatt_read_params params; + uint16_t *result_size; + uint16_t *result_handle; + struct net_buf_simple *result_data; + struct k_mutex lock; + struct k_condvar done; + uint16_t *att_mtu; + bool long_read; +}; + +static uint8_t att_read_cb(struct bt_conn *conn, uint8_t att_err, + struct bt_gatt_read_params *params, const void *read_data, + uint16_t read_len) +{ + struct bt_testlib_att_read_closure *ctx = + CONTAINER_OF(params, struct bt_testlib_att_read_closure, params); + + k_mutex_lock(&ctx->lock, K_FOREVER); + + if (read_data == NULL) { + __ASSERT_NO_MSG(ctx->long_read); + k_condvar_signal(&ctx->done); + k_mutex_unlock(&ctx->lock); + return BT_GATT_ITER_STOP; + } + + ctx->att_err = att_err; + + if (!att_err && ctx->result_handle) { + *ctx->result_handle = params->by_uuid.start_handle; + } + + if (!att_err && ctx->result_size) { + LOG_DBG("Adding %u bytes to result", read_len); + *ctx->result_size += read_len; + if (*ctx->result_size > 512) { + LOG_ERR("result_size > 512"); + } + } + + if (!att_err && ctx->result_data) { + uint16_t result_data_size = + MIN(read_len, net_buf_simple_tailroom(ctx->result_data)); + + net_buf_simple_add_mem(ctx->result_data, read_data, result_data_size); + } + + if (!att_err && ctx->att_mtu) { + *ctx->att_mtu = params->_att_mtu; + } + + if (ctx->long_read) { + /* Don't signal `&ctx->done` */ + k_mutex_unlock(&ctx->lock); + return BT_GATT_ITER_CONTINUE; + } + + k_condvar_signal(&ctx->done); + k_mutex_unlock(&ctx->lock); + return BT_GATT_ITER_STOP; +} + +static int bt_testlib_sync_bt_gatt_read(struct bt_testlib_att_read_closure *ctx) +{ + int api_err; + + ctx->params.func = att_read_cb; + + k_mutex_init(&ctx->lock); + k_condvar_init(&ctx->done); + + k_mutex_lock(&ctx->lock, K_FOREVER); + + api_err = bt_gatt_read(ctx->conn, &ctx->params); + + if (!api_err) { + k_condvar_wait(&ctx->done, &ctx->lock, K_FOREVER); + } + + k_mutex_unlock(&ctx->lock); + + if (api_err) { + __ASSERT_NO_MSG(api_err < 0); + return api_err; + } + + __ASSERT_NO_MSG(ctx->att_err >= 0); + return ctx->att_err; +} + +int bt_testlib_att_read_by_type_sync(struct net_buf_simple *result_data, uint16_t *result_size, + uint16_t *result_handle, uint16_t *result_att_mtu, + struct bt_conn *conn, enum bt_att_chan_opt bearer, + const struct bt_uuid *type, uint16_t start_handle, + uint16_t end_handle) +{ + struct bt_testlib_att_read_closure ctx = {.result_handle = result_handle, + .result_size = result_size, + .conn = conn, + .result_data = result_data, + .att_mtu = result_att_mtu, + .params = { + .by_uuid = {.uuid = type, + .start_handle = start_handle, + .end_handle = end_handle}, + }}; + + IF_ENABLED(CONFIG_BT_EATT, ({ ctx.params.chan_opt = bearer; })) + + if (bearer == BT_ATT_CHAN_OPT_ENHANCED_ONLY) { + __ASSERT(IS_ENABLED(CONFIG_BT_EATT), "EATT not complied in"); + } + + return bt_testlib_sync_bt_gatt_read(&ctx); +} + +int bt_testlib_att_read_by_handle_sync(struct net_buf_simple *result_data, uint16_t *result_size, + uint16_t *result_att_mtu, struct bt_conn *conn, + enum bt_att_chan_opt bearer, uint16_t handle, + uint16_t offset) +{ + struct bt_testlib_att_read_closure ctx = { + .result_size = result_size, + .conn = conn, + .att_mtu = result_att_mtu, + .result_data = result_data, + .params = { + .handle_count = 1, + .single = {.handle = handle, .offset = offset}, + IF_ENABLED(CONFIG_BT_EATT, (.chan_opt = bearer)), + }}; + + if (bearer == BT_ATT_CHAN_OPT_ENHANCED_ONLY) { + __ASSERT(IS_ENABLED(CONFIG_BT_EATT), "EATT not complied in"); + } + + *result_size = 0; + + return bt_testlib_sync_bt_gatt_read(&ctx); +} + +int btt_gatt_long_read(struct net_buf_simple *result_data, uint16_t *result_size, + struct bt_conn *conn, enum bt_att_chan_opt bearer, uint16_t handle, + uint16_t offset) +{ + int err; + uint16_t _result_data_size = 0; + + struct bt_testlib_att_read_closure ctx = { + .long_read = true, + .result_size = &_result_data_size, + .conn = conn, + .result_data = result_data, + .params = { + .handle_count = 1, + .single = {.handle = handle, .offset = offset}, + IF_ENABLED(CONFIG_BT_EATT, (.chan_opt = bearer)), + }}; + + if (bearer == BT_ATT_CHAN_OPT_ENHANCED_ONLY) { + __ASSERT(IS_ENABLED(CONFIG_BT_EATT), "EATT not complied in"); + } + + err = bt_testlib_sync_bt_gatt_read(&ctx); + + if (result_size) { + *result_size = _result_data_size; + } + + return err; +} + +struct bt_testlib_gatt_discover_service_closure { + struct bt_gatt_discover_params params; + uint8_t att_err; + uint16_t *const result_handle; + uint16_t *const result_end_handle; + struct k_mutex lock; + struct k_condvar done; +}; + +static uint8_t gatt_discover_service_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_testlib_gatt_discover_service_closure *ctx = + CONTAINER_OF(params, struct bt_testlib_gatt_discover_service_closure, params); + + k_mutex_lock(&ctx->lock, K_FOREVER); + + ctx->att_err = attr ? BT_ATT_ERR_SUCCESS : BT_ATT_ERR_ATTRIBUTE_NOT_FOUND; + + if (!ctx->att_err) { + if (ctx->result_handle) { + *ctx->result_handle = attr->handle; + } + + if (ctx->result_end_handle) { + *ctx->result_end_handle = 0; + /* Output 'group end handle'. */ + if (params->type == BT_GATT_DISCOVER_PRIMARY || + params->type == BT_GATT_DISCOVER_SECONDARY) { + *ctx->result_end_handle = + ((struct bt_gatt_service_val *)attr->user_data)->end_handle; + } + } + } + + k_condvar_signal(&ctx->done); + k_mutex_unlock(&ctx->lock); + return BT_GATT_ITER_STOP; +} + +/** AKA Service discovery by UUID. + */ +int bt_testlib_gatt_discover_primary(uint16_t *result_handle, uint16_t *result_end_handle, + struct bt_conn *conn, const struct bt_uuid *uuid, + uint16_t start_handle, uint16_t end_handle) +{ + int api_err; + + struct bt_testlib_gatt_discover_service_closure _ctx = { + .result_handle = result_handle, + .result_end_handle = result_end_handle, + .params = { + .type = BT_GATT_DISCOVER_PRIMARY, + .start_handle = start_handle, + .end_handle = end_handle, + .func = gatt_discover_service_cb, + .uuid = uuid, + }}; + struct bt_testlib_gatt_discover_service_closure *const ctx = &_ctx; + + k_mutex_init(&ctx->lock); + k_condvar_init(&ctx->done); + + __ASSERT_NO_MSG(conn); + __ASSERT_NO_MSG(IN_RANGE(start_handle, BT_ATT_FIRST_ATTRIBUTE_HANDLE, + BT_ATT_LAST_ATTRIBUTE_HANDLE)); + __ASSERT_NO_MSG( + IN_RANGE(end_handle, BT_ATT_FIRST_ATTRIBUTE_HANDLE, BT_ATT_LAST_ATTRIBUTE_HANDLE)); + + k_mutex_lock(&ctx->lock, K_FOREVER); + + api_err = bt_gatt_discover(conn, &ctx->params); + + if (!api_err) { + k_condvar_wait(&ctx->done, &ctx->lock, K_FOREVER); + } + + k_mutex_unlock(&ctx->lock); + + if (api_err) { + __ASSERT_NO_MSG(api_err < 0); + return api_err; + } + __ASSERT_NO_MSG(ctx->att_err >= 0); + return ctx->att_err; +} + +struct bt_testlib_gatt_discover_char_closure { + struct bt_gatt_discover_params params; + uint8_t att_err; + uint16_t *const result_def_handle; + uint16_t *const result_value_handle; + uint16_t *const result_end_handle; + uint16_t svc_end_handle; + struct k_mutex lock; + struct k_condvar done; +}; + +static uint8_t gatt_discover_char_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + LOG_DBG("gatt_discover_char_cb conn %u attr %p params %p", bt_conn_index(conn), attr, + params); + + struct bt_testlib_gatt_discover_char_closure *ctx = + CONTAINER_OF(params, struct bt_testlib_gatt_discover_char_closure, params); + bool read_more = false; + + k_mutex_lock(&ctx->lock, K_FOREVER); + + if (ctx->att_err == BT_ATT_ERR_ATTRIBUTE_NOT_FOUND) { + /* The start of the charachteristic was not found yet. + * This is the start of the characteristic. + */ + if (attr) { + ctx->att_err = BT_ATT_ERR_SUCCESS; + if (ctx->result_def_handle) { + *ctx->result_def_handle = attr->handle; + } + if (ctx->result_value_handle) { + *ctx->result_value_handle = + ((struct bt_gatt_chrc *)attr->user_data)->value_handle; + } + if (ctx->result_end_handle) { + read_more = true; + } + } + } else { + /* This is the end of the characteristic. + */ + if (attr) { + __ASSERT_NO_MSG(ctx->result_end_handle); + *ctx->result_end_handle = (attr->handle - 1); + } + }; + + if (!read_more) { + k_condvar_signal(&ctx->done); + } + k_mutex_unlock(&ctx->lock); + return read_more ? BT_GATT_ITER_CONTINUE : BT_GATT_ITER_STOP; +} + +int bt_testlib_gatt_discover_characteristic(uint16_t *const result_value_handle, + uint16_t *const result_end_handle, + uint16_t *const result_def_handle, struct bt_conn *conn, + const struct bt_uuid *uuid, uint16_t start_handle, + uint16_t svc_end_handle) +{ + int api_err; + + if (result_end_handle) { + /* If there is no second result, the end_handle is the svc_end. */ + *result_end_handle = svc_end_handle; + } + + struct bt_testlib_gatt_discover_char_closure _ctx = { + .att_err = BT_ATT_ERR_ATTRIBUTE_NOT_FOUND, + .result_value_handle = result_value_handle, + .result_def_handle = result_def_handle, + .result_end_handle = result_end_handle, + .params = { + .type = BT_GATT_DISCOVER_CHARACTERISTIC, + .start_handle = start_handle, + .end_handle = svc_end_handle, + .func = gatt_discover_char_cb, + .uuid = uuid, + }}; + struct bt_testlib_gatt_discover_char_closure *const ctx = &_ctx; + + k_mutex_init(&ctx->lock); + k_condvar_init(&ctx->done); + + __ASSERT_NO_MSG(conn); + __ASSERT_NO_MSG(IN_RANGE(start_handle, BT_ATT_FIRST_ATTRIBUTE_HANDLE, + BT_ATT_LAST_ATTRIBUTE_HANDLE)); + __ASSERT_NO_MSG(IN_RANGE(svc_end_handle, BT_ATT_FIRST_ATTRIBUTE_HANDLE, + BT_ATT_LAST_ATTRIBUTE_HANDLE)); + + k_mutex_lock(&ctx->lock, K_FOREVER); + + api_err = bt_gatt_discover(conn, &ctx->params); + + if (!api_err) { + k_condvar_wait(&ctx->done, &ctx->lock, K_FOREVER); + } + + k_mutex_unlock(&ctx->lock); + + if (api_err) { + __ASSERT_NO_MSG(api_err < 0); + return api_err; + } + __ASSERT_NO_MSG(ctx->att_err >= 0); + return ctx->att_err; +} diff --git a/tests/bsim/bluetooth/host/att/long_read/testlib/att_read.h b/tests/bsim/bluetooth/host/att/long_read/testlib/att_read.h new file mode 100644 index 000000000000000..ced1265289dafbe --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/att_read.h @@ -0,0 +1,39 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +/** Perform a single ATT_READ_BY_TYPE_REQ. */ +int bt_testlib_att_read_by_type_sync(struct net_buf_simple *result_data, uint16_t *result_size, + uint16_t *result_handle, uint16_t *result_att_mtu, + struct bt_conn *conn, enum bt_att_chan_opt bearer, + const struct bt_uuid *type, uint16_t start_handle, + uint16_t end_handle); + +/** If offset == 0, perform a single ATT_READ_REQ. + * If offset > 0, perform a signle ATT_READ_BLOB_REQ. + */ +int bt_testlib_att_read_by_handle_sync(struct net_buf_simple *result_data, uint16_t *result_size, + uint16_t *result_att_mtu, struct bt_conn *conn, + enum bt_att_chan_opt bearer, uint16_t handle, + uint16_t offset); + +int btt_gatt_long_read(struct net_buf_simple *result_data, uint16_t *result_size, + struct bt_conn *conn, enum bt_att_chan_opt bearer, uint16_t handle, + uint16_t offset); + +int bt_testlib_gatt_discover_primary(uint16_t *result_handle, uint16_t *result_end_handle, + struct bt_conn *conn, const struct bt_uuid *uuid, + uint16_t start_handle, uint16_t end_handle); + +/* Note: svc_end_handle must be the service end handle. (The discovery + * algorithm requires it to recognize the last characteristic in a + * service and deduce its end handle.) + */ +int bt_testlib_gatt_discover_characteristic(uint16_t *const result_value_handle, + uint16_t *const result_end_handle, + uint16_t *const result_def_handle, struct bt_conn *conn, + const struct bt_uuid *uuid, uint16_t start_handle, + uint16_t svc_end_handle); diff --git a/tests/bsim/bluetooth/host/att/long_read/testlib/att_write.c b/tests/bsim/bluetooth/host/att/long_read/testlib/att_write.c new file mode 100644 index 000000000000000..9642dad83844f19 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/att_write.c @@ -0,0 +1,74 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(att_write, LOG_LEVEL_DBG); + +struct bt_testlib_att_write_closure { + uint8_t att_err; + struct bt_gatt_write_params params; + struct k_mutex lock; + struct k_condvar done; +}; + +static void att_write_cb(struct bt_conn *conn, uint8_t att_err, struct bt_gatt_write_params *params) +{ + struct bt_testlib_att_write_closure *ctx = + CONTAINER_OF(params, struct bt_testlib_att_write_closure, params); + + k_mutex_lock(&ctx->lock, K_FOREVER); + + ctx->att_err = att_err; + + k_condvar_signal(&ctx->done); + k_mutex_unlock(&ctx->lock); +} + +int bt_testlib_att_write(struct bt_conn *conn, enum bt_att_chan_opt bearer, uint16_t handle, + uint8_t *data, uint16_t size) +{ + int api_err; + struct bt_testlib_att_write_closure _ctx = {.params = { + .handle = handle, + .offset = 0, + .func = att_write_cb, + .data = data, + .length = size, + }}; + struct bt_testlib_att_write_closure *const ctx = &_ctx; + + k_mutex_init(&ctx->lock); + k_condvar_init(&ctx->done); + + __ASSERT_NO_MSG(conn); + __ASSERT_NO_MSG( + IN_RANGE(handle, BT_ATT_FIRST_ATTRIBUTE_HANDLE, BT_ATT_LAST_ATTRIBUTE_HANDLE)); + + k_mutex_lock(&ctx->lock, K_FOREVER); + + api_err = bt_gatt_write(conn, &ctx->params); + + if (!api_err) { + k_condvar_wait(&ctx->done, &ctx->lock, K_FOREVER); + } + + k_mutex_unlock(&ctx->lock); + + if (api_err) { + __ASSERT_NO_MSG(api_err < 0); + return api_err; + } + __ASSERT_NO_MSG(ctx->att_err >= 0); + return ctx->att_err; +} diff --git a/tests/bsim/bluetooth/host/att/long_read/testlib/att_write.h b/tests/bsim/bluetooth/host/att/long_read/testlib/att_write.h new file mode 100644 index 000000000000000..9bf063d3bd484f0 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/att_write.h @@ -0,0 +1,9 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +int bt_testlib_att_write(struct bt_conn *conn, enum bt_att_chan_opt bearer, uint16_t handle, + const uint8_t *data, uint16_t size); diff --git a/tests/bsim/bluetooth/host/att/long_read/testlib/bs_macro.h b/tests/bsim/bluetooth/host/att/long_read/testlib/bs_macro.h new file mode 100644 index 000000000000000..b9e52beea6c5631 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/bs_macro.h @@ -0,0 +1,23 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define PASS(...) \ + do { \ + extern enum bst_result_t bst_result; \ + bst_result = Passed; \ + bs_trace_info_time(1, __VA_ARGS__); \ + } while (0) + +static inline void bt_testlib_expect_zero(int err, char *where_file, int where_line) +{ + if (err) { + bs_trace_print(BS_TRACE_ERROR, where_file, where_line, 0, BS_TRACE_AUTOTIME, 0, + "err %d\n", err); + } +} + +#define EXPECT_ZERO(expr) bt_testlib_expect_zero((expr), __FILE__, __LINE__) diff --git a/tests/bsim/bluetooth/host/att/long_read/testlib/bs_main.c b/tests/bsim/bluetooth/host/att/long_read/testlib/bs_main.c new file mode 100644 index 000000000000000..26985a8d415d399 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/bs_main.c @@ -0,0 +1,31 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +void the_test(void); + +static const struct bst_test_instance test_to_add[] = { + { + .test_id = "the_test", + .test_main_f = the_test, + }, + 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}; + +int main(void) +{ + bst_main(); + + return 0; +} diff --git a/tests/bsim/bluetooth/host/att/long_read/testlib/bs_sync.c b/tests/bsim/bluetooth/host/att/long_read/testlib/bs_sync.c new file mode 100644 index 000000000000000..c7c432c74b7a4ee --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/bs_sync.c @@ -0,0 +1,107 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +LOG_MODULE_REGISTER(bs_sync, LOG_LEVEL_INF); + +static int n_devs; + +static void register_more_cmd_args(void) +{ + static bs_args_struct_t args_struct_toadd[] = { + { + .option = "D", + .name = "number_devices", + .type = 'i', + .dest = (void *)&n_devs, + .descript = "Number of devices which will connect in this phy", + .is_mandatory = true, + }, + ARG_TABLE_ENDMARKER, + }; + + bs_add_extra_dynargs(args_struct_toadd); +} +NATIVE_TASK(register_more_cmd_args, PRE_BOOT_1, 100); + +static uint *backchannels; +static void setup_backchannels(void) +{ + __ASSERT_NO_MSG(n_devs > 0); + uint self = get_device_nbr(); + uint device_nbrs[n_devs]; + uint channel_numbers[n_devs]; + + for (int i = 0; i < n_devs; i++) { + device_nbrs[i] = i; + channel_numbers[i] = 0; + } + + backchannels = + bs_open_back_channel(self, device_nbrs, channel_numbers, ARRAY_SIZE(device_nbrs)); + __ASSERT_NO_MSG(backchannels != NULL); +} +NATIVE_TASK(setup_backchannels, PRE_BOOT_3, 100); + +void bs_bc_receive_msg_sync(uint ch, size_t size, uint8_t *data) +{ + while (bs_bc_is_msg_received(ch) < size) { + k_msleep(1); + } + bs_bc_receive_msg(ch, data, size); +} + +void bs_bc_send_uint(uint ch, uint64_t data) +{ + uint8_t data_bytes[sizeof(data)]; + + sys_put_le64(data, data_bytes); + bs_bc_send_msg(ch, data_bytes, sizeof(data_bytes)); +} + +uint64_t bs_bc_recv_uint(uint ch) +{ + uint8_t data[sizeof(uint64_t)]; + + bs_bc_receive_msg_sync(ch, sizeof(data), data); + return sys_get_le64(data); +} + +void bt_testlib_bs_sync_all(void) +{ + static uint64_t counter; + + LOG_DBG("%llu d%u enter", counter, get_device_nbr()); + + /* Device 0 acts as hub. */ + if (get_device_nbr() == 0) { + for (int i = 1; i < n_devs; i++) { + uint64_t counter_cfm; + + counter_cfm = bs_bc_recv_uint(backchannels[i]); + __ASSERT(counter_cfm == counter, "%luu %luu", counter_cfm, counter); + } + for (int i = 1; i < n_devs; i++) { + bs_bc_send_uint(backchannels[i], counter); + } + } else { + uint64_t counter_cfm; + + bs_bc_send_uint(backchannels[0], counter); + counter_cfm = bs_bc_recv_uint(backchannels[0]); + __ASSERT(counter_cfm == counter, "%luu %luu", counter_cfm, counter); + } + + LOG_DBG("%llu d%u exit", counter, get_device_nbr()); + + counter++; +} diff --git a/tests/bsim/bluetooth/host/att/long_read/testlib/bs_sync.h b/tests/bsim/bluetooth/host/att/long_read/testlib/bs_sync.h new file mode 100644 index 000000000000000..f69336cb1c26630 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/bs_sync.h @@ -0,0 +1,5 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +void bt_testlib_bs_sync_all(void); diff --git a/tests/bsim/bluetooth/host/att/long_read/testlib/conn_ref.h b/tests/bsim/bluetooth/host/att/long_read/testlib/conn_ref.h new file mode 100644 index 000000000000000..c9978b635dcea60 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/conn_ref.h @@ -0,0 +1,37 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +/** + * @file + * @brief Reified connection reference counting. + * @ingroup testlib conn ref + * + * This file provides functions to reify the moving and cloning of @ref + * bt_conn references for increased safety. + * + * Reifying means that the existence of a reference is always tied + * one-to-one with a non-NULL value in a owning pointer variable. + * + * The functions in this file will trigger an assert if they attempt to + * overwrite a non-NULL value in a owning pointer variable. This is to + * prevent leaking the reference that presumable is tied the value that + * would be overwritten. + * + * The functions in this file are intended to guard against undefined + * behavor due to NULL pointer dereferencing. They will assert on any + * relevant pointers. + */ + +void bt_testlib_conn_unref(struct bt_conn **connp) +{ + struct bt_conn *conn; + + __ASSERT_NO_MSG(connp); + conn = atomic_ptr_set((void **)connp, NULL); + __ASSERT_NO_MSG(conn); + bt_conn_unref(conn); +} diff --git a/tests/bsim/bluetooth/host/att/long_read/testlib/conn_wait.c b/tests/bsim/bluetooth/host/att/long_read/testlib/conn_wait.c new file mode 100644 index 000000000000000..7edd12a699b2516 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/conn_wait.c @@ -0,0 +1,66 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(conn_wait, LOG_LEVEL_DBG); + +static K_MUTEX_DEFINE(conn_wait_mutex); +static K_CONDVAR_DEFINE(something_changed); + +void on_change(struct bt_conn *conn, uint8_t err) +{ + k_mutex_lock(&conn_wait_mutex, K_FOREVER); + k_condvar_broadcast(&something_changed); + k_mutex_unlock(&conn_wait_mutex); +} + +BT_CONN_CB_DEFINE(conn_callbacks) = { + .connected = on_change, + .disconnected = on_change, +}; + +enum bt_conn_state bt_conn_state(struct bt_conn *conn) +{ + int err; + struct bt_conn_info info; + + __ASSERT(conn != NULL, "Invalid connection"); + err = bt_conn_get_info(conn, &info); + __ASSERT(err == 0, "Failed to get connection info"); + + return info.state; +} + +int bt_testlib_wait_connected(struct bt_conn *conn) +{ + __ASSERT_NO_MSG(conn != NULL); + k_mutex_lock(&conn_wait_mutex, K_FOREVER); + while (bt_conn_state(conn) != BT_CONN_STATE_CONNECTED) { + k_condvar_wait(&something_changed, &conn_wait_mutex, K_FOREVER); + } + k_mutex_unlock(&conn_wait_mutex); + return 0; +} + +int bt_testlib_wait_disconnected(struct bt_conn *conn) +{ + __ASSERT_NO_MSG(conn != NULL); + k_mutex_lock(&conn_wait_mutex, K_FOREVER); + while (bt_conn_state(conn) != BT_CONN_STATE_DISCONNECTED) { + k_condvar_wait(&something_changed, &conn_wait_mutex, K_FOREVER); + } + k_mutex_unlock(&conn_wait_mutex); + return 0; +} diff --git a/tests/bsim/bluetooth/host/att/long_read/testlib/conn_wait.h b/tests/bsim/bluetooth/host/att/long_read/testlib/conn_wait.h new file mode 100644 index 000000000000000..d28b150aebcffcd --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/conn_wait.h @@ -0,0 +1,4 @@ +#include + +int bt_testlib_wait_connected(struct bt_conn *conn); +int bt_testlib_wait_disconnected(struct bt_conn *conn); diff --git a/tests/bsim/bluetooth/host/att/long_read/testlib/connect.c b/tests/bsim/bluetooth/host/att/long_read/testlib/connect.c new file mode 100644 index 000000000000000..ddfa81de9ce4477 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/connect.c @@ -0,0 +1,85 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include "connect.h" + +LOG_MODULE_REGISTER(testlib_connect, LOG_LEVEL_INF); + +struct bt_testlib_connect_closure { + uint8_t conn_err; + struct bt_conn **conn; + struct k_mutex lock; + struct k_condvar done; +}; + +/* Context pool (with capacity of one). */ +static K_SEM_DEFINE(g_ctx_free, 1, 1); +static K_MUTEX_DEFINE(g_ctx_lock); +static struct bt_testlib_connect_closure *g_ctx; + +static void connected_cb(struct bt_conn *conn, uint8_t conn_err) +{ + /* Loop over each (allocated) item in pool. */ + + k_mutex_lock(&g_ctx_lock, K_FOREVER); + + if (g_ctx && conn == *g_ctx->conn) { + g_ctx->conn_err = conn_err; + k_condvar_signal(&g_ctx->done); + } + + k_mutex_unlock(&g_ctx_lock); +} + +BT_CONN_CB_DEFINE(conn_callbacks) = { + .connected = connected_cb, +}; + +int bt_testlib_connect(const bt_addr_le_t *peer, struct bt_conn **conn) +{ + int api_err; + struct bt_testlib_connect_closure ctx = { + .conn = conn, + }; + + __ASSERT_NO_MSG(conn); + __ASSERT_NO_MSG(*conn == NULL); + + k_condvar_init(&ctx.done); + + k_sem_take(&g_ctx_free, K_FOREVER); + k_mutex_lock(&g_ctx_lock, K_FOREVER); + g_ctx = &ctx; + + api_err = bt_conn_le_create(peer, BT_CONN_LE_CREATE_CONN, BT_LE_CONN_PARAM_DEFAULT, conn); + + if (!api_err) { + LOG_INF("Connecting.. conn %u", bt_conn_index(*conn)); + k_condvar_wait(&ctx.done, &g_ctx_lock, K_FOREVER); + LOG_INF("Connect complete"); + } + + g_ctx = NULL; + k_mutex_unlock(&g_ctx_lock); + k_sem_give(&g_ctx_free); + + if (api_err) { + LOG_ERR("bt_conn_le_create err %d", api_err); + __ASSERT_NO_MSG(api_err < 0); + return api_err; + } + + if (ctx.conn_err) { + LOG_ERR("Connect HCI err %d", ctx.conn_err); + __ASSERT_NO_MSG(ctx.conn_err >= 0); + return ctx.conn_err; + } + + return 0; +} diff --git a/tests/bsim/bluetooth/host/att/long_read/testlib/connect.h b/tests/bsim/bluetooth/host/att/long_read/testlib/connect.h new file mode 100644 index 000000000000000..e8da223849a7da5 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/connect.h @@ -0,0 +1,7 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +int bt_testlib_connect(const bt_addr_le_t *peer, struct bt_conn **conn); diff --git a/tests/bsim/bluetooth/host/att/long_read/testlib/log_utils.h b/tests/bsim/bluetooth/host/att/long_read/testlib/log_utils.h new file mode 100644 index 000000000000000..c3825246bb95162 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/log_utils.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +static inline void bt_testlib_log_level_set(char *module, uint32_t new_level) +{ + int source_id; + uint32_t result_level; + + __ASSERT_NO_MSG(IS_ENABLED(CONFIG_LOG_RUNTIME_FILTERING)); + + source_id = log_source_id_get(module); + __ASSERT(source_id >= 0, "%d", source_id); + + result_level = log_filter_set(NULL, Z_LOG_LOCAL_DOMAIN_ID, source_id, new_level); + __ASSERT(result_level == new_level, "%u %u", result_level, new_level); +} + +static inline void bt_testlib_log_level_set_all(uint32_t new_level) +{ + uint32_t source_count; + + __ASSERT_NO_MSG(IS_ENABLED(CONFIG_LOG_RUNTIME_FILTERING)); + + source_count = log_src_cnt_get(Z_LOG_LOCAL_DOMAIN_ID); + + for (uint32_t source_id = 0; source_id < source_count; source_id++) { + uint32_t result_level; + + result_level = log_filter_set(NULL, Z_LOG_LOCAL_DOMAIN_ID, source_id, new_level); + __ASSERT(result_level == new_level, "%u %u", result_level, new_level); + } +} diff --git a/tests/bsim/bluetooth/host/att/long_read/testlib/scan.c b/tests/bsim/bluetooth/host/att/long_read/testlib/scan.c new file mode 100644 index 000000000000000..337dfef15caa8b8 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/scan.c @@ -0,0 +1,98 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#include "scan.h" + +LOG_MODULE_REGISTER(testlib_scan, LOG_LEVEL_INF); + +struct bt_scan_find_name_closure { + char *wanted_name; + bt_addr_le_t *result; + struct k_condvar done; +}; + +/* Context pool (with capacity of one). */ +static K_SEM_DEFINE(g_ctx_free, 1, 1); +static K_MUTEX_DEFINE(g_ctx_lock); +static struct bt_scan_find_name_closure *g_ctx; + +static bool bt_scan_find_name_cb_data_cb(struct bt_data *data, void *user_data) +{ + char **wanted = user_data; + + if (data->type == BT_DATA_NAME_COMPLETE) { + if (data->data_len == strlen(*wanted) && + !memcmp(*wanted, data->data, data->data_len)) { + *wanted = NULL; + /* Stop bt_data_parse. */ + return false; + } + } + + /* Continue with next ad data. */ + return true; +} + +static void bt_scan_find_name_cb(const bt_addr_le_t *addr, int8_t rssi, uint8_t adv_type, + struct net_buf_simple *buf) +{ + char *wanted; + + k_mutex_lock(&g_ctx_lock, K_FOREVER); + + __ASSERT_NO_MSG(g_ctx); + __ASSERT_NO_MSG(g_ctx->wanted_name); + + wanted = g_ctx->wanted_name; + + bt_data_parse(buf, bt_scan_find_name_cb_data_cb, &wanted); + + if (!wanted) { + (void)bt_le_scan_stop(); + *g_ctx->result = *addr; + k_condvar_signal(&g_ctx->done); + } + + k_mutex_unlock(&g_ctx_lock); +} + +int bt_testlib_scan_find_name(bt_addr_le_t *result, char name[]) +{ + int api_err; + struct bt_scan_find_name_closure ctx = { + .wanted_name = name, + .result = result, + }; + + k_condvar_init(&ctx.done); + + k_sem_take(&g_ctx_free, K_FOREVER); + k_mutex_lock(&g_ctx_lock, K_FOREVER); + g_ctx = &ctx; + + api_err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, bt_scan_find_name_cb); + if (!api_err) { + k_condvar_wait(&ctx.done, &g_ctx_lock, K_FOREVER); + } + + g_ctx = NULL; + k_mutex_unlock(&g_ctx_lock); + k_sem_give(&g_ctx_free); + + if (!api_err) { + char str[BT_ADDR_LE_STR_LEN]; + (void)bt_addr_le_to_str(result, str, ARRAY_SIZE(str)); + LOG_INF("Scan match: %s", str); + } else { + LOG_ERR("Scan error: %d", api_err); + } + + return api_err; +} diff --git a/tests/bsim/bluetooth/host/att/long_read/testlib/scan.h b/tests/bsim/bluetooth/host/att/long_read/testlib/scan.h new file mode 100644 index 000000000000000..58c887c21c960ba --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/scan.h @@ -0,0 +1,7 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +int bt_testlib_scan_find_name(bt_addr_le_t *result, char name[]); diff --git a/tests/bsim/bluetooth/host/att/long_read/testlib/security.c b/tests/bsim/bluetooth/host/att/long_read/testlib/security.c new file mode 100644 index 000000000000000..e7de704acee6f29 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/security.c @@ -0,0 +1,108 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(testlib_security, LOG_LEVEL_INF); + +struct testlib_security_ctx { + enum bt_security_err result; + struct bt_conn *conn; + bt_security_t new_minimum; + struct k_condvar done; +}; + +/* Context pool (with capacity of one). */ +static K_SEM_DEFINE(g_ctx_free, 1, 1); +static K_MUTEX_DEFINE(g_ctx_lock); +static struct testlib_security_ctx *g_ctx; + +static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err) +{ + LOG_INF("conn %u level %d err %d", bt_conn_index(conn), level, err); + + /* Mutex operations establish a happens-before relationship. This + * ensures variables have the expected values despite non-atomic + * accesses. + */ + k_mutex_lock(&g_ctx_lock, K_FOREVER); + + if (g_ctx && (g_ctx->conn == conn)) { + g_ctx->result = err; + /* Assumption: A security error means there will be further + * security changes for this connection. + */ + if (err || level >= g_ctx->new_minimum) { + k_condvar_signal(&g_ctx->done); + } + } + + k_mutex_unlock(&g_ctx_lock); +} + +BT_CONN_CB_DEFINE(conn_callbacks) = { + .security_changed = security_changed, +}; + +int bt_testlib_secure(struct bt_conn *conn, bt_security_t new_minimum) +{ + int api_err = 0; + struct testlib_security_ctx ctx = { + .conn = conn, + .new_minimum = new_minimum, + }; + + k_condvar_init(&ctx.done); + + /* The semaphore allocates `g_ctx` to this invocation of + * `bt_testlib_secure`, in case this function is called from multiple + * threads in parallel. + */ + k_sem_take(&g_ctx_free, K_FOREVER); + /* The mutex synchronizes this function with `security_changed()`. */ + k_mutex_lock(&g_ctx_lock, K_FOREVER); + + /* Do the thing. */ + api_err = bt_conn_set_security(conn, new_minimum); + + /* Holding the mutex will pause any thread entering + * `security_changed_cb`, delaying it until `k_condvar_wait`. This + * ensures that the condition variable is signaled while this thread is + * in `k_condvar_wait`, even if the event happens before, e.g. between + * `bt_conn_get_security` and `k_condvar_wait`. + * + * If the security level is already satisfied, there is no point in + * waiting, and it would deadlock if security was already satisfied + * before the mutex was taken, `bt_conn_set_security` will result in no + * operation. + */ + if (!api_err && bt_conn_get_security(conn) < new_minimum) { + /* Waiting on a condvar releases the mutex and waits for a + * signal on the condvar, atomically, without a gap between the + * release and wait. The mutex is locked again before returning. + */ + g_ctx = &ctx; + k_condvar_wait(&ctx.done, &g_ctx_lock, K_FOREVER); + g_ctx = NULL; + } + + k_mutex_unlock(&g_ctx_lock); + k_sem_give(&g_ctx_free); + + if (api_err) { + __ASSERT_NO_MSG(api_err < 0); + return api_err; + } + + __ASSERT_NO_MSG(ctx.result >= 0); + return ctx.result; +} diff --git a/tests/bsim/bluetooth/host/att/long_read/testlib/security.h b/tests/bsim/bluetooth/host/att/long_read/testlib/security.h new file mode 100644 index 000000000000000..8b7dd46423c64c0 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/security.h @@ -0,0 +1,7 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +int bt_testlib_secure(struct bt_conn *conn, bt_security_t new_minimum); diff --git a/tests/bsim/bluetooth/host/compile.sh b/tests/bsim/bluetooth/host/compile.sh index 4f9cd0409b8fbda..ba88b149ab3973c 100755 --- a/tests/bsim/bluetooth/host/compile.sh +++ b/tests/bsim/bluetooth/host/compile.sh @@ -37,6 +37,7 @@ app=tests/bsim/bluetooth/host/att/read_fill_buf/client compile app=tests/bsim/bluetooth/host/att/read_fill_buf/server compile app=tests/bsim/bluetooth/host/att/sequential/dut compile app=tests/bsim/bluetooth/host/att/sequential/tester compile +app=tests/bsim/bluetooth/host/att/long_read compile app=tests/bsim/bluetooth/host/gatt/caching compile app=tests/bsim/bluetooth/host/gatt/general compile