From aad13987f48279979a9e124bd5eab43b5a13f1b5 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 | 22 +- .../host/att/long_read/CMakeLists.txt | 24 ++ .../bluetooth/host/att/long_read/_build.sh | 3 + .../bsim/bluetooth/host/att/long_read/main.c | 186 +++++++++ .../bluetooth/host/att/long_read/prj.conf | 26 ++ .../bsim/bluetooth/host/att/long_read/run.sh | 19 + .../host/att/long_read/testlib/adv.c | 83 ++++ .../host/att/long_read/testlib/adv.h | 8 + .../host/att/long_read/testlib/att_read.c | 392 ++++++++++++++++++ .../host/att/long_read/testlib/att_read.h | 39 ++ .../host/att/long_read/testlib/att_write.c | 77 ++++ .../host/att/long_read/testlib/att_write.h | 9 + .../host/att/long_read/testlib/bs_macro.h | 26 ++ .../host/att/long_read/testlib/bs_main.c | 31 ++ .../host/att/long_read/testlib/bs_sync.c | 97 +++++ .../host/att/long_read/testlib/bs_sync.h | 1 + .../host/att/long_read/testlib/conn_ref.h | 11 + .../host/att/long_read/testlib/conn_wait.c | 62 +++ .../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/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 + 29 files changed, 1458 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/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 e4aae4e4d41ae66..f0ab0dc04fbb3ac 100644 --- a/include/zephyr/bluetooth/gatt.h +++ b/include/zephyr/bluetooth/gatt.h @@ -1577,6 +1577,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 @@ -1587,9 +1589,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 bea0fffaa8d7088..6cd2c08c6b503e5 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 39f01bc45b40250..c0cc0bde5b03e53 100644 --- a/subsys/bluetooth/host/gatt.c +++ b/subsys/bluetooth/host/gatt.c @@ -4649,8 +4649,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; } @@ -6303,3 +6306,20 @@ 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; + 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..d9d9bbdbd958ea3 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/_build.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +exec west build -b nrf52_bsim 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..3af51084691ee34 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/main.c @@ -0,0 +1,186 @@ +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include "testlib/bs_macro.h" +#include "testlib/bs_sync.h" +#include "testlib/adv.h" +#include "testlib/connect.h" +#include "testlib/scan.h" +#include "testlib/security.h" +#include "testlib/conn_ref.h" +#include "testlib/att_read.h" +#include "testlib/att_write.h" +#include "testlib/conn_wait.h" + +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 = buf_len; + + __ASSERT(buf_len < 256, "The EATT buffer is too powerful for this test."); + + /* Note that `bt_gatt_get_mtu` is not useful when EATT is + * established because it may be returning the value for a + * different bearer than this request is serviced on. + */ + LOG_INF("Server side buf_len %u", buf_len); + LOG_INF("Server size bt_gatt_get_mtu %u", bt_gatt_get_mtu(conn)); + + /* Send back the whole buffer worth of data the first time. Then + * send all-but-full one to conlude the long read. The + * assumption here is that `buf_len` is `(ATT_MTU - 1)`, which + * is read length that triggers subsequent reads in a long read + * procedure. + */ + if (offset > 0) { + read_len -= 1; + } + + /* Fill the read buffer with zero. */ + memset(buf, 0, read_len); + + /* Echo back the requested read size in the first two bytes of + * each read. */ + 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), +}; + +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; + + EZ(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); + + EZ(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); +} + +#include +#include +static inline void log_level_set(char *module, uint32_t new_level) +{ + __ASSERT_NO_MSG(IS_ENABLED(CONFIG_LOG_RUNTIME_FILTERING)); + int source_id = log_source_id_get(module); + __ASSERT(source_id >= 0, "%d", source_id); + uint32_t result_level = log_filter_set(NULL, Z_LOG_LOCAL_DOMAIN_ID, source_id, new_level); + __ASSERT(result_level == new_level, "%u", result_level); +} + +static inline void bt_enable_quiet() +{ + log_level_set("bt_hci_core", LOG_LEVEL_ERR); + log_level_set("bt_id", LOG_LEVEL_ERR); + EZ(bt_enable(NULL)); + log_level_set("bt_hci_core", LOG_LEVEL_INF); + log_level_set("bt_id", LOG_LEVEL_INF); +} + +void sync(char *log) +{ + bt_testlib_bs_sync(); + if (get_device_nbr() == 0) { + LOG_WRN("Sync: %s", log); + } + bt_testlib_bs_sync(); +} + +void the_test(void) +{ + bool central = get_device_nbr() == 0; + bool peripheral = get_device_nbr() == 1; + bt_addr_le_t adva; + struct bt_conn *conn = NULL; + uint16_t chrc_value_handle = 0; + + if (peripheral) { + EZ(bt_gatt_service_register(&svc)); + } + + bt_enable_quiet(); + + if (peripheral) { + EZ(bt_set_name("peripheral")); + EZ(bt_testlib_adv_conn(&conn, BT_ID_DEFAULT, + (BT_LE_ADV_OPT_USE_NAME | BT_LE_ADV_OPT_FORCE_NAME_IN_AD))); + } + if (central) { + EZ(bt_testlib_scan_find_name(&adva, "peripheral")); + EZ(bt_testlib_connect(&adva, &conn)); + + /* Establish EATT bearers. */ + EZ(bt_testlib_secure(conn, BT_SECURITY_L2)); + + while (bt_eatt_count(conn) == 0) { + k_msleep(100); + }; + } + + sync("Connected"); + + if (central) { + find_the_chrc(conn, &chrc_value_handle); + + uint16_t actual_read_len = 0; + uint16_t remote_send_len = 0; + + /* Buffer is two bytes large. Testlib will truncate the + * rest for us. + */ + NET_BUF_SIMPLE_DEFINE(attr_value, 512); + + EZ(btt_gatt_long_read(&attr_value, &actual_read_len, conn, + BT_ATT_CHAN_OPT_ENHANCED_ONLY, chrc_value_handle, 0)); + + for (size_t i = 0; attr_value.len; i++) { + __ASSERT(attr_value.len >= sizeof(remote_send_len), + "Remote was supposed to send an uint16. But there are not enough " + "bytes in the buffer."); + remote_send_len = net_buf_simple_pull_le16(&attr_value); + LOG_INF("Verifying read %u: %u", i, remote_send_len); + __ASSERT((remote_send_len - 2) <= attr_value.len, "Length mismatch. %u %u", + (remote_send_len - 2), attr_value.len); + net_buf_simple_pull_mem(&attr_value, (remote_send_len - 2)); + } + LOG_INF("actual_read_len: %u", actual_read_len); + } + + sync("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..83bc7ed2e66655d --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/run.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -eu +dotslash="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" + +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" + +# Required for +cd "${BSIM_OUT_PATH}/bin" + +("$dotslash"/build/zephyr/zephyr.exe "${args_all[@]}" "${args_dev[@]}" -d=0 || echo d0 $? ) & +("$dotslash"/build/zephyr/zephyr.exe "${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..cbf3c584aa516d0 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/adv.h @@ -0,0 +1,8 @@ +/* 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..1808ea326afc3d0 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/att_read.c @@ -0,0 +1,392 @@ +/* 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 inline 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) { + k_mutex_unlock(&ctx->lock); + return BT_GATT_ITER_CONTINUE; + } else { + k_condvar_signal(&ctx->done); + k_mutex_unlock(&ctx->lock); + return BT_GATT_ITER_STOP; + } +} + +static inline 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, (.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 inline 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'. TODO: + * Document how all of this maps to the ATT RSPs. + */ + 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 inline 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; +} + +/** AKA Service discovery by UUID. + */ +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..51e7649d0011eb8 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/att_write.c @@ -0,0 +1,77 @@ +/* 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..1dff4488b641a54 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/bs_macro.h @@ -0,0 +1,26 @@ +/* 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 EZ(expr) \ + do { \ + bt_testlib_expect_zero((expr), __FILE__, __LINE__); \ + } while (0) 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..f08a9ac8d710aea --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/bs_sync.c @@ -0,0 +1,97 @@ +#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() +{ + static uint64_t counter = 0; + + LOG_DBG("bt_testlib_bs_sync %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 = 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 { + bs_bc_send_uint(backchannels[0], counter); + uint64_t counter_cfm = bs_bc_recv_uint(backchannels[0]); + __ASSERT(counter_cfm == counter, "%luu %luu", counter_cfm, counter); + } + + LOG_DBG("bt_testlib_bs_sync %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..a5fd691355ff080 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/bs_sync.h @@ -0,0 +1 @@ +void bt_testlib_bs_sync(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..3ca57557fbb8d97 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/conn_ref.h @@ -0,0 +1,11 @@ +#include + +struct bt_conn; + +void bt_testlib_conn_unref(struct bt_conn **conn) +{ + __ASSERT_NO_MSG(conn); + struct bt_conn *tmp = atomic_ptr_set((void **)conn, NULL); + __ASSERT_NO_MSG(tmp); + bt_conn_unref(tmp); +} 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..a5ce908dc250890 --- /dev/null +++ b/tests/bsim/bluetooth/host/att/long_read/testlib/conn_wait.c @@ -0,0 +1,62 @@ +/* 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, +}; + +bool bt_conn_is(struct bt_conn *conn, enum bt_conn_state state) { + __ASSERT(conn != NULL, "Invalid connection"); + struct bt_conn_info info; + int err = bt_conn_get_info(conn, &info); + __ASSERT(err == 0, "Failed to get connection info"); + return info.state == 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_is(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_is(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..cfffa6e26fd9138 --- /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/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 266fa88ab2f9da7..96a5486ab731c28 100755 --- a/tests/bsim/bluetooth/host/compile.sh +++ b/tests/bsim/bluetooth/host/compile.sh @@ -35,6 +35,7 @@ app=tests/bsim/bluetooth/host/att/eatt_notif conf_file=prj.conf compile app=tests/bsim/bluetooth/host/att/mtu_update compile 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/long_read compile app=tests/bsim/bluetooth/host/gatt/caching compile app=tests/bsim/bluetooth/host/gatt/general compile