| /* Copyright (c) 2023 Nordic Semiconductor ASA |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <stdint.h> |
| #include <zephyr/bluetooth/bluetooth.h> |
| #include <zephyr/bluetooth/conn.h> |
| #include <zephyr/bluetooth/gatt.h> |
| #include <zephyr/bluetooth/att.h> |
| #include <zephyr/bluetooth/l2cap.h> |
| #include <zephyr/bluetooth/uuid.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/sys/__assert.h> |
| |
| #include <testlib/att_read.h> |
| #include <zephyr/logging/log.h> |
| |
| LOG_MODULE_REGISTER(bt_testlib_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 bool bt_gatt_read_params_is_by_uuid(const struct bt_gatt_read_params *params) |
| { |
| return params->handle_count == 0; |
| } |
| |
| 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); |
| |
| ctx->att_err = att_err; |
| |
| if (!att_err && ctx->result_handle) { |
| __ASSERT_NO_MSG(bt_gatt_read_params_is_by_uuid(params)); |
| *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 > BT_ATT_MAX_ATTRIBUTE_LEN) { |
| LOG_ERR("result_size > 512"); |
| } |
| } |
| |
| if (read_data && 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 && read_data) { |
| /* 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; |
| |
| /* `result_size` is initialized here so that it can be plussed on in |
| * the callback. The result of a long read comes in multiple |
| * callbacks and must be added up. |
| */ |
| if (ctx->result_size) { |
| *ctx->result_size = 0; |
| } |
| |
| /* `att_read_cb` is smart and does the right thing based on `ctx`. */ |
| ctx->params.func = att_read_cb; |
| |
| /* Setup synchronization between the cb and the current function. */ |
| 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 = {}; |
| |
| ctx.att_mtu = result_att_mtu; |
| ctx.conn = conn; |
| ctx.params.handle_count = 1; |
| ctx.params.single.handle = handle; |
| ctx.params.single.offset = offset; |
| ctx.result_data = result_data; |
| ctx.result_size = result_size; |
| 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_gatt_long_read(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 err; |
| uint16_t _result_data_size = 0; |
| struct bt_testlib_att_read_closure ctx = {}; |
| |
| ctx.att_mtu = result_att_mtu; |
| ctx.conn = conn; |
| ctx.long_read = true, ctx.params.handle_count = 1; |
| ctx.params.single.handle = handle; |
| ctx.params.single.offset = offset; |
| ctx.result_data = result_data; |
| ctx.result_size = &_result_data_size; |
| 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"); |
| } |
| |
| 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_val = { |
| .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_val; |
| |
| 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) |
| { |
| 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_val = { |
| .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_val; |
| |
| 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; |
| } |