diff --git a/include/zephyr/bluetooth/gatt.h b/include/zephyr/bluetooth/gatt.h index c53422648f00..eb38f765afbc 100644 --- a/include/zephyr/bluetooth/gatt.h +++ b/include/zephyr/bluetooth/gatt.h @@ -235,6 +235,37 @@ struct bt_gatt_cb { sys_snode_t node; }; +/** @brief GATT authorization callback structure. */ +struct bt_gatt_authorization_cb { + /** @brief Authorize the GATT read operation. + * + * This callback allows the application to authorize the GATT + * read operation for the attribute that is being read. + * + * @param conn Connection object. + * @param attr The attribute that is being read. + * + * @retval true Authorize the operation and allow it to execute. + * @retval false Reject the operation and prevent it from executing. + */ + bool (*read_operation_authorize)(struct bt_conn *conn, + const struct bt_gatt_attr *attr); + + /** @brief Authorize the GATT write operation. + * + * This callback allows the application to authorize the GATT + * write operation for the attribute that is being written. + * + * @param conn Connection object. + * @param attr The attribute that is being written. + * + * @retval true Authorize the operation and allow it to execute. + * @retval false Reject the operation and prevent it from executing. + */ + bool (*write_operation_authorize)(struct bt_conn *conn, + const struct bt_gatt_attr *attr); +}; + /** Characteristic Properties Bit field values */ /** @@ -377,6 +408,26 @@ struct bt_gatt_cpf { */ void bt_gatt_cb_register(struct bt_gatt_cb *cb); +/** @brief Register GATT authorization callbacks. + * + * Register callbacks to perform application-specific authorization of GATT + * operations on all registered GATT attributes. The callback structure must + * remain valid throughout the entire duration of the Bluetooth subsys + * activity. + * + * The @kconfig{CONFIG_BT_GATT_AUTHORIZATION_CUSTOM} Kconfig must be enabled + * to make this API functional. + * + * This API allows the user to register only one callback structure + * concurrently. Passing NULL unregisters the previous set of callbacks + * and makes it possible to register a new one. + * + * @param cb Callback struct. + * + * @return Zero on success or negative error code otherwise + */ +int bt_gatt_authorization_cb_register(const struct bt_gatt_authorization_cb *cb); + /** @brief Register GATT service. * * Register GATT service. Applications can make use of diff --git a/subsys/bluetooth/host/Kconfig.gatt b/subsys/bluetooth/host/Kconfig.gatt index 9209368f9438..a2364bbe5577 100644 --- a/subsys/bluetooth/host/Kconfig.gatt +++ b/subsys/bluetooth/host/Kconfig.gatt @@ -276,4 +276,13 @@ config DEVICE_NAME_GATT_WRITABLE_AUTHEN endif #BT_DEVICE_NAME_GATT_WRITABLE +config BT_GATT_AUTHORIZATION_CUSTOM + bool "Custom authorization of GATT operations [EXPERIMENTAL]" + select EXPERIMENTAL + help + This option allows the user to define application-specific + authorization logic for GATT operations that can be registered + with the bt_gatt_authorization_cb_register API. See the API + documentation for more details. + endmenu diff --git a/subsys/bluetooth/host/att.c b/subsys/bluetooth/host/att.c index 130b4ab0853a..44862f5bf255 100644 --- a/subsys/bluetooth/host/att.c +++ b/subsys/bluetooth/host/att.c @@ -115,6 +115,11 @@ static uint16_t bt_att_mtu(struct bt_att_chan *chan) return MIN(chan->chan.rx.mtu, chan->chan.tx.mtu); } +/* Descriptor of application-specific authorization callbacks that are used + * with the CONFIG_BT_GATT_AUTHORIZATION_CUSTOM Kconfig enabled. + */ +const static struct bt_gatt_authorization_cb *authorization_cb; + /* ATT connection specific data */ struct bt_att { struct bt_conn *conn; @@ -1289,6 +1294,20 @@ struct read_type_data { typedef bool (*attr_read_cb)(struct net_buf *buf, ssize_t read, void *user_data); +static bool attr_read_authorize(struct bt_conn *conn, + const struct bt_gatt_attr *attr) +{ + if (!IS_ENABLED(CONFIG_BT_GATT_AUTHORIZATION_CUSTOM)) { + return true; + } + + if (!authorization_cb || !authorization_cb->read_operation_authorize) { + return true; + } + + return authorization_cb->read_operation_authorize(conn, attr); +} + static bool attr_read_type_cb(struct net_buf *frag, ssize_t read, void *user_data) { @@ -1398,6 +1417,12 @@ static uint8_t read_type_cb(const struct bt_gatt_attr *attr, uint16_t handle, return BT_GATT_ITER_STOP; } + /* Check the attribute authorization logic */ + if (!attr_read_authorize(conn, attr)) { + data->err = BT_ATT_ERR_AUTHORIZATION; + return BT_GATT_ITER_STOP; + } + /* * If any attribute is founded in handle range it means that error * should be changed from pre-set: attr not found error to no error. @@ -1526,6 +1551,12 @@ static uint8_t read_cb(const struct bt_gatt_attr *attr, uint16_t handle, return BT_GATT_ITER_STOP; } + /* Check the attribute authorization logic */ + if (!attr_read_authorize(conn, attr)) { + data->err = BT_ATT_ERR_AUTHORIZATION; + return BT_GATT_ITER_STOP; + } + /* Read attribute value and store in the buffer */ ret = att_chan_read(chan, attr, data->buf, data->offset, NULL, NULL); if (ret < 0) { @@ -1693,6 +1724,12 @@ static uint8_t read_vl_cb(const struct bt_gatt_attr *attr, uint16_t handle, return BT_GATT_ITER_STOP; } + /* Check the attribute authorization logic */ + if (!attr_read_authorize(conn, attr)) { + data->err = BT_ATT_ERR_AUTHORIZATION; + return BT_GATT_ITER_STOP; + } + /* The Length Value Tuple List may be truncated within the first two * octets of a tuple due to the size limits of the current ATT_MTU. */ @@ -1940,6 +1977,20 @@ struct write_data { uint8_t err; }; +static bool attr_write_authorize(struct bt_conn *conn, + const struct bt_gatt_attr *attr) +{ + if (!IS_ENABLED(CONFIG_BT_GATT_AUTHORIZATION_CUSTOM)) { + return true; + } + + if (!authorization_cb || !authorization_cb->write_operation_authorize) { + return true; + } + + return authorization_cb->write_operation_authorize(conn, attr); +} + static uint8_t write_cb(const struct bt_gatt_attr *attr, uint16_t handle, void *user_data) { @@ -1956,6 +2007,12 @@ static uint8_t write_cb(const struct bt_gatt_attr *attr, uint16_t handle, return BT_GATT_ITER_STOP; } + /* Check the attribute authorization logic */ + if (!attr_write_authorize(data->conn, attr)) { + data->err = BT_ATT_ERR_AUTHORIZATION; + return BT_GATT_ITER_STOP; + } + /* Set command flag if not a request */ if (!data->req) { flags |= BT_GATT_WRITE_FLAG_CMD; @@ -2069,6 +2126,12 @@ static uint8_t prep_write_cb(const struct bt_gatt_attr *attr, uint16_t handle, return BT_GATT_ITER_STOP; } + /* Check the attribute authorization logic */ + if (!attr_write_authorize(data->conn, attr)) { + data->err = BT_ATT_ERR_AUTHORIZATION; + return BT_GATT_ITER_STOP; + } + /* Check if attribute requires handler to accept the data */ if (!(attr->perm & BT_GATT_PERM_PREPARE_WRITE)) { goto append; @@ -3997,3 +4060,23 @@ bool bt_att_chan_opt_valid(struct bt_conn *conn, enum bt_att_chan_opt chan_opt) return true; } + +int bt_gatt_authorization_cb_register(const struct bt_gatt_authorization_cb *cb) +{ + if (!IS_ENABLED(CONFIG_BT_GATT_AUTHORIZATION_CUSTOM)) { + return -ENOSYS; + } + + if (!cb) { + authorization_cb = NULL; + return 0; + } + + if (authorization_cb) { + return -EALREADY; + } + + authorization_cb = cb; + + return 0; +} diff --git a/tests/bsim/bluetooth/host/compile.sh b/tests/bsim/bluetooth/host/compile.sh index ff42fd27adf9..098c983d75f6 100755 --- a/tests/bsim/bluetooth/host/compile.sh +++ b/tests/bsim/bluetooth/host/compile.sh @@ -41,6 +41,7 @@ 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/authorization compile app=tests/bsim/bluetooth/host/gatt/caching compile app=tests/bsim/bluetooth/host/gatt/general compile app=tests/bsim/bluetooth/host/gatt/notify compile diff --git a/tests/bsim/bluetooth/host/gatt/authorization/CMakeLists.txt b/tests/bsim/bluetooth/host/gatt/authorization/CMakeLists.txt new file mode 100644 index 000000000000..acb0dd45947b --- /dev/null +++ b/tests/bsim/bluetooth/host/gatt/authorization/CMakeLists.txt @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(bsim_test_gatt_authorization) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources} ) + +zephyr_include_directories( + ${BSIM_COMPONENTS_PATH}/libUtilv1/src/ + ${BSIM_COMPONENTS_PATH}/libPhyComv1/src/ + ) diff --git a/tests/bsim/bluetooth/host/gatt/authorization/prj.conf b/tests/bsim/bluetooth/host/gatt/authorization/prj.conf new file mode 100644 index 000000000000..9cba554afef6 --- /dev/null +++ b/tests/bsim/bluetooth/host/gatt/authorization/prj.conf @@ -0,0 +1,7 @@ +CONFIG_BT=y +CONFIG_BT_DEVICE_NAME="GATT tester" +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_CENTRAL=y +CONFIG_BT_GATT_CLIENT=y +CONFIG_BT_GATT_AUTHORIZATION_CUSTOM=y +CONFIG_BT_ATT_PREPARE_COUNT=3 diff --git a/tests/bsim/bluetooth/host/gatt/authorization/src/common.c b/tests/bsim/bluetooth/host/gatt/authorization/src/common.c new file mode 100644 index 000000000000..adff2dd05ef6 --- /dev/null +++ b/tests/bsim/bluetooth/host/gatt/authorization/src/common.c @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "common.h" + +void test_tick(bs_time_t HW_device_time) +{ + if (bst_result != Passed) { + FAIL("test failed (not passed after %i seconds)\n", WAIT_TIME); + } +} + +void test_init(void) +{ + bst_ticker_set_next_tick_absolute(WAIT_TIME); + bst_result = In_progress; +} diff --git a/tests/bsim/bluetooth/host/gatt/authorization/src/common.h b/tests/bsim/bluetooth/host/gatt/authorization/src/common.h new file mode 100644 index 000000000000..339919dfe884 --- /dev/null +++ b/tests/bsim/bluetooth/host/gatt/authorization/src/common.h @@ -0,0 +1,73 @@ +/** + * Common functions and helpers for BSIM GATT tests + * + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "bs_types.h" +#include "bs_tracing.h" +#include "time_machine.h" +#include "bstests.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +extern enum bst_result_t bst_result; + +#define WAIT_TIME (30 * 1e6) /*seconds*/ + +#define CREATE_FLAG(flag) static atomic_t flag = (atomic_t)false +#define SET_FLAG(flag) (void)atomic_set(&flag, (atomic_t)true) +#define UNSET_FLAG(flag) (void)atomic_set(&flag, (atomic_t)false) +#define WAIT_FOR_FLAG(flag) \ + while (!(bool)atomic_get(&flag)) { \ + (void)k_sleep(K_MSEC(1)); \ + } + +#define FAIL(...) \ + do { \ + bst_result = Failed; \ + bs_trace_error_time_line(__VA_ARGS__); \ + } while (0) + +#define PASS(...) \ + do { \ + bst_result = Passed; \ + bs_trace_info_time(1, __VA_ARGS__); \ + } while (0) + +#define CHRC_SIZE 10 + +#define TEST_SERVICE_UUID \ + BT_UUID_DECLARE_128(0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x02, 0x03, \ + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x00) + +#define TEST_UNHANDLED_CHRC_UUID \ + BT_UUID_DECLARE_128(0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x02, 0x03, \ + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFD, 0x00) + +#define TEST_UNAUTHORIZED_CHRC_UUID \ + BT_UUID_DECLARE_128(0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x02, 0x03, \ + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFE, 0x00) + +#define TEST_AUTHORIZED_CHRC_UUID \ + BT_UUID_DECLARE_128(0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x02, 0x03, \ + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0x00) + +#define TEST_CP_CHRC_UUID \ + BT_UUID_DECLARE_128(0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x02, 0x03, \ + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xF0, 0x00) + +void test_tick(bs_time_t HW_device_time); +void test_init(void); diff --git a/tests/bsim/bluetooth/host/gatt/authorization/src/gatt_client_test.c b/tests/bsim/bluetooth/host/gatt/authorization/src/gatt_client_test.c new file mode 100644 index 000000000000..9c60c5b57b2d --- /dev/null +++ b/tests/bsim/bluetooth/host/gatt/authorization/src/gatt_client_test.c @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "common.h" + +CREATE_FLAG(flag_is_connected); +CREATE_FLAG(flag_discover_complete); +CREATE_FLAG(flag_write_complete); +CREATE_FLAG(flag_read_complete); + +static struct bt_conn *g_conn; +static uint16_t unhandled_chrc_handle; +static uint16_t unauthorized_chrc_handle; +static uint16_t authorized_chrc_handle; +static uint16_t cp_chrc_handle; +static const struct bt_uuid *test_svc_uuid = TEST_SERVICE_UUID; + +#define ARRAY_ITEM(i, _) i +static uint8_t chrc_data[] = { LISTIFY(CHRC_SIZE, ARRAY_ITEM, (,)) }; /* 1, 2, 3 ... */ + +static void connected(struct bt_conn *conn, uint8_t err) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + + if (err != 0) { + FAIL("Failed to connect to %s (%u)\n", addr, err); + return; + } + + printk("Connected to %s\n", addr); + + __ASSERT_NO_MSG(g_conn == conn); + + SET_FLAG(flag_is_connected); +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + if (conn != g_conn) { + return; + } + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + + printk("Disconnected: %s (reason 0x%02x)\n", addr, reason); + + bt_conn_unref(g_conn); + + g_conn = NULL; + UNSET_FLAG(flag_is_connected); +} + +BT_CONN_CB_DEFINE(conn_callbacks) = { + .connected = connected, + .disconnected = disconnected, +}; + +void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, + struct net_buf_simple *ad) +{ + char addr_str[BT_ADDR_LE_STR_LEN]; + int err; + + if (g_conn != NULL) { + return; + } + + /* We're only interested in connectable events */ + if (type != BT_HCI_ADV_IND && type != BT_HCI_ADV_DIRECT_IND) { + return; + } + + bt_addr_le_to_str(addr, addr_str, sizeof(addr_str)); + printk("Device found: %s (RSSI %d)\n", addr_str, rssi); + + printk("Stopping scan\n"); + err = bt_le_scan_stop(); + if (err != 0) { + FAIL("Could not stop scan: %d"); + return; + } + + err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, + BT_LE_CONN_PARAM_DEFAULT, &g_conn); + if (err != 0) { + FAIL("Could not connect to peer: %d", err); + } +} + +static uint8_t discover_func(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + int err; + + if (attr == NULL) { + if (unhandled_chrc_handle == 0 || + unauthorized_chrc_handle == 0 || + authorized_chrc_handle == 0) { + FAIL("Did not discover required characterstics"); + } + + (void)memset(params, 0, sizeof(*params)); + + SET_FLAG(flag_discover_complete); + + return BT_GATT_ITER_STOP; + } + + printk("[ATTRIBUTE] handle %u\n", attr->handle); + + if (params->type == BT_GATT_DISCOVER_PRIMARY && + bt_uuid_cmp(params->uuid, TEST_SERVICE_UUID) == 0) { + printk("Found test service\n"); + params->uuid = NULL; + params->start_handle = attr->handle + 1; + params->type = BT_GATT_DISCOVER_CHARACTERISTIC; + + err = bt_gatt_discover(conn, params); + if (err != 0) { + FAIL("Discover failed (err %d)\n", err); + } + + return BT_GATT_ITER_STOP; + } else if (params->type == BT_GATT_DISCOVER_CHARACTERISTIC) { + struct bt_gatt_chrc *chrc = (struct bt_gatt_chrc *)attr->user_data; + + if (bt_uuid_cmp(chrc->uuid, TEST_UNHANDLED_CHRC_UUID) == 0) { + printk("Found unhandled chrc\n"); + unhandled_chrc_handle = chrc->value_handle; + } else if (bt_uuid_cmp(chrc->uuid, TEST_UNAUTHORIZED_CHRC_UUID) == 0) { + printk("Found unauthorized\n"); + unauthorized_chrc_handle = chrc->value_handle; + } else if (bt_uuid_cmp(chrc->uuid, TEST_AUTHORIZED_CHRC_UUID) == 0) { + printk("Found authorized chrc\n"); + authorized_chrc_handle = chrc->value_handle; + } else if (bt_uuid_cmp(chrc->uuid, TEST_CP_CHRC_UUID) == 0) { + printk("Found CP chrc\n"); + cp_chrc_handle = chrc->value_handle; + } + } + + return BT_GATT_ITER_CONTINUE; +} + +static void gatt_discover(void) +{ + static struct bt_gatt_discover_params discover_params; + int err; + + printk("Discovering services and characteristics\n"); + + discover_params.uuid = test_svc_uuid; + discover_params.func = discover_func; + discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + discover_params.type = BT_GATT_DISCOVER_PRIMARY; + + err = bt_gatt_discover(g_conn, &discover_params); + if (err != 0) { + FAIL("Discover failed(err %d)\n", err); + } + + WAIT_FOR_FLAG(flag_discover_complete); + printk("Discover complete\n"); +} + +static void gatt_write_cb(struct bt_conn *conn, uint8_t err, + struct bt_gatt_write_params *params) +{ + if ((err != BT_ATT_ERR_SUCCESS) && (params->handle != unauthorized_chrc_handle)) { + FAIL("Write failed on authorized characteristics: 0x%02X\n", err); + } + + if ((err != BT_ATT_ERR_AUTHORIZATION) && (params->handle == unauthorized_chrc_handle)) { + FAIL("Write failed on unauthorized characteristics: 0x%02X\n", err); + } + + (void)memset(params, 0, sizeof(*params)); + + SET_FLAG(flag_write_complete); +} + +static void gatt_write(uint16_t handle) +{ + static struct bt_gatt_write_params write_params; + int err; + + printk("Writing to chrc\n"); + + write_params.data = chrc_data; + write_params.length = sizeof(chrc_data); + write_params.func = gatt_write_cb; + write_params.handle = handle; + + UNSET_FLAG(flag_write_complete); + + err = bt_gatt_write(g_conn, &write_params); + if (err != 0) { + FAIL("bt_gatt_write failed: %d\n", err); + } + + WAIT_FOR_FLAG(flag_write_complete); + printk("success\n"); +} + +static void gatt_cp_write(void) +{ + static struct bt_gatt_write_params write_params; + int err; + uint8_t cp_write_data[] = {0x00}; + + printk("Writing to CP chrc\n"); + + write_params.data = cp_write_data; + write_params.length = sizeof(cp_write_data); + write_params.func = gatt_write_cb; + write_params.handle = cp_chrc_handle; + + UNSET_FLAG(flag_write_complete); + + err = bt_gatt_write(g_conn, &write_params); + if (err != 0) { + FAIL("bt_gatt_write failed: %d\n", err); + } + + WAIT_FOR_FLAG(flag_write_complete); + printk("success\n"); +} + +static uint8_t gatt_read_cb(struct bt_conn *conn, uint8_t err, + struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + if ((err != BT_ATT_ERR_SUCCESS) && + (params->single.handle != unauthorized_chrc_handle)) { + FAIL("Read failed on authorized characteristics: 0x%02X\n", err); + + if ((length != CHRC_SIZE) || (memcmp(data, chrc_data, length) != 0)) { + FAIL("chrc data different than expected", err); + } + } + + if ((err != BT_ATT_ERR_AUTHORIZATION) && + (params->single.handle == unauthorized_chrc_handle)) { + FAIL("Read failed on unauthorized characteristics: 0x%02X\n", err); + } + + (void)memset(params, 0, sizeof(*params)); + + SET_FLAG(flag_read_complete); + + return 0; +} + +static void gatt_read(uint16_t handle) +{ + static struct bt_gatt_read_params read_params; + int err; + + printk("Reading chrc\n"); + + read_params.func = gatt_read_cb; + read_params.handle_count = 1; + read_params.single.handle = handle; + read_params.single.offset = 0; + + UNSET_FLAG(flag_read_complete); + + err = bt_gatt_read(g_conn, &read_params); + if (err != 0) { + FAIL("bt_gatt_read failed: %d\n", err); + } + + WAIT_FOR_FLAG(flag_read_complete); + printk("success\n"); +} + +static void gatt_interact(uint16_t handle) +{ + gatt_write(handle); + gatt_read(handle); + gatt_cp_write(); +} + +static void test_main(void) +{ + int err; + + err = bt_enable(NULL); + if (err != 0) { + FAIL("Bluetooth discover failed (err %d)\n", err); + } + + err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, device_found); + if (err != 0) { + FAIL("Scanning failed to start (err %d)\n", err); + } + + printk("Scanning successfully started\n"); + + WAIT_FOR_FLAG(flag_is_connected); + + gatt_discover(); + + printk("Interacting with the unhandled characteristic\n"); + gatt_interact(unhandled_chrc_handle); + + printk("Interacting with the unauthorized characteristic\n"); + gatt_interact(unauthorized_chrc_handle); + + printk("Interacting with the authorized characteristic\n"); + gatt_interact(authorized_chrc_handle); + + PASS("GATT client Passed\n"); +} + +static const struct bst_test_instance test_vcs[] = { + { + .test_id = "gatt_client", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = test_main + }, + BSTEST_END_MARKER +}; + +struct bst_test_list *test_gatt_client_install(struct bst_test_list *tests) +{ + return bst_add_tests(tests, test_vcs); +} diff --git a/tests/bsim/bluetooth/host/gatt/authorization/src/gatt_server_test.c b/tests/bsim/bluetooth/host/gatt/authorization/src/gatt_server_test.c new file mode 100644 index 000000000000..d5dd5e0e0a76 --- /dev/null +++ b/tests/bsim/bluetooth/host/gatt/authorization/src/gatt_server_test.c @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "common.h" + +extern enum bst_result_t bst_result; + +CREATE_FLAG(flag_is_chrc_ctx_validated); + +static struct bt_conn *g_conn; + +static void connected(struct bt_conn *conn, uint8_t err) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + + if (err != 0) { + FAIL("Failed to connect to %s (%u)\n", addr, err); + return; + } + + printk("Connected to %s\n", addr); + + g_conn = bt_conn_ref(conn); +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + char addr[BT_ADDR_LE_STR_LEN]; + + if (conn != g_conn) { + return; + } + + bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); + + printk("Disconnected: %s (reason 0x%02x)\n", addr, reason); + + bt_conn_unref(g_conn); + + g_conn = NULL; +} + +BT_CONN_CB_DEFINE(conn_callbacks) = { + .connected = connected, + .disconnected = disconnected, +}; + +struct test_chrc_ctx { + uint16_t auth_read_cnt; + uint16_t read_cnt; + uint16_t auth_write_cnt; + uint16_t write_cnt; + uint8_t data[CHRC_SIZE]; +}; + +static ssize_t read_test_chrc(struct test_chrc_ctx *chrc_ctx, + struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) +{ + chrc_ctx->read_cnt++; + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + (void *)chrc_ctx->data, + sizeof(chrc_ctx->data)); +} + +static ssize_t write_test_chrc(struct test_chrc_ctx *chrc_ctx, + const void *buf, uint16_t len, + uint16_t offset, uint8_t flags) +{ + chrc_ctx->write_cnt++; + + if (len != sizeof(chrc_ctx->data)) { + printk("Invalid chrc length\n"); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + if (offset != 0) { + printk("Invalid chrc offset and length\n"); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (flags != 0) { + FAIL("Invalid flags %u\n", flags); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + (void)memcpy(chrc_ctx->data, buf, len); + + return len; +} + +static struct test_chrc_ctx unhandled_chrc_ctx; + +static ssize_t read_test_unhandled_chrc(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) +{ + return read_test_chrc(&unhandled_chrc_ctx, conn, attr, buf, len, offset); +} + +static ssize_t write_test_unhandled_chrc(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, + uint16_t offset, uint8_t flags) +{ + printk("unhandled chrc len %u offset %u\n", len, offset); + + return write_test_chrc(&unhandled_chrc_ctx, buf, len, offset, flags); +} + +static struct test_chrc_ctx unauthorized_chrc_ctx; + +static ssize_t read_test_unauthorized_chrc(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) +{ + return read_test_chrc(&unauthorized_chrc_ctx, conn, attr, buf, len, offset); +} + +static ssize_t write_test_unauthorized_chrc(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, + uint16_t offset, uint8_t flags) +{ + printk("unauthorized chrc len %u offset %u\n", len, offset); + + return write_test_chrc(&unauthorized_chrc_ctx, buf, len, offset, flags); +} + +static struct test_chrc_ctx authorized_chrc_ctx; + +static ssize_t read_test_authorized_chrc(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, uint16_t len, uint16_t offset) +{ + return read_test_chrc(&authorized_chrc_ctx, conn, attr, buf, len, offset); +} + +static ssize_t write_test_authorized_chrc(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, + uint16_t offset, uint8_t flags) +{ + printk("authorized chrc len %u offset %u\n", len, offset); + + return write_test_chrc(&authorized_chrc_ctx, buf, len, offset, flags); +} + +static const struct test_chrc_ctx zeroed_chrc_ctx; + +static bool unhandled_chrc_operation_validate(void) +{ + if (memcmp(&unauthorized_chrc_ctx, &zeroed_chrc_ctx, sizeof(zeroed_chrc_ctx)) != 0) { + return false; + } + + if (memcmp(&authorized_chrc_ctx, &zeroed_chrc_ctx, sizeof(zeroed_chrc_ctx)) != 0) { + return false; + } + + if ((unhandled_chrc_ctx.read_cnt != 1) && (unhandled_chrc_ctx.write_cnt != 1)) { + return false; + } + + if ((unhandled_chrc_ctx.auth_read_cnt != 0) && + (unhandled_chrc_ctx.auth_write_cnt != 0)) { + return false; + } + + return true; +} + +static bool unauthorized_chrc_operation_validate(void) +{ + if (memcmp(&unhandled_chrc_ctx, &zeroed_chrc_ctx, sizeof(zeroed_chrc_ctx)) != 0) { + return false; + } + + if (memcmp(&authorized_chrc_ctx, &zeroed_chrc_ctx, sizeof(zeroed_chrc_ctx)) != 0) { + return false; + } + + if ((unauthorized_chrc_ctx.read_cnt != 0) && (unauthorized_chrc_ctx.write_cnt != 0)) { + return false; + } + + if ((unauthorized_chrc_ctx.auth_read_cnt != 1) && + (unauthorized_chrc_ctx.auth_write_cnt != 1)) { + return false; + } + + return true; +} + +static bool authorized_chrc_operation_validate(void) +{ + if (memcmp(&unhandled_chrc_ctx, &zeroed_chrc_ctx, sizeof(zeroed_chrc_ctx)) != 0) { + return false; + } + + if (memcmp(&unauthorized_chrc_ctx, &zeroed_chrc_ctx, sizeof(zeroed_chrc_ctx)) != 0) { + return false; + } + + if ((authorized_chrc_ctx.read_cnt != 1) && (authorized_chrc_ctx.write_cnt != 1)) { + return false; + } + + if ((authorized_chrc_ctx.auth_read_cnt != 1) && + (authorized_chrc_ctx.auth_write_cnt != 1)) { + return false; + } + + return true; +} + +static ssize_t write_cp_chrc(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, + uint16_t offset, uint8_t flags) +{ + static uint16_t cp_write_cnt; + bool pass; + char *log_str; + + if (cp_write_cnt == 0) { + pass = unhandled_chrc_operation_validate(); + log_str = "unhandled"; + } else if (cp_write_cnt == 1) { + pass = unauthorized_chrc_operation_validate(); + log_str = "unauthorized"; + } else if (cp_write_cnt == 2) { + pass = authorized_chrc_operation_validate(); + log_str = "authorized"; + } else { + FAIL("Invalid value of CP write counter %u\n", cp_write_cnt); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + if (pass) { + printk("Correct context for %s chrc\n", log_str); + } else { + FAIL("Invalid context for %s chrc\n", log_str); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + memset(&unhandled_chrc_ctx, 0, sizeof(unhandled_chrc_ctx)); + memset(&unauthorized_chrc_ctx, 0, sizeof(unauthorized_chrc_ctx)); + memset(&authorized_chrc_ctx, 0, sizeof(authorized_chrc_ctx)); + + cp_write_cnt++; + + if (cp_write_cnt == 3) { + SET_FLAG(flag_is_chrc_ctx_validated); + } + + return len; +} + +BT_GATT_SERVICE_DEFINE(test_svc, + BT_GATT_PRIMARY_SERVICE(TEST_SERVICE_UUID), + BT_GATT_CHARACTERISTIC(TEST_UNHANDLED_CHRC_UUID, + BT_GATT_CHRC_WRITE | BT_GATT_CHRC_READ, + BT_GATT_PERM_WRITE | BT_GATT_PERM_READ, + read_test_unhandled_chrc, + write_test_unhandled_chrc, NULL), + BT_GATT_CHARACTERISTIC(TEST_UNAUTHORIZED_CHRC_UUID, + BT_GATT_CHRC_WRITE | BT_GATT_CHRC_READ, + BT_GATT_PERM_WRITE | BT_GATT_PERM_READ, + read_test_unauthorized_chrc, + write_test_unauthorized_chrc, NULL), + BT_GATT_CHARACTERISTIC(TEST_AUTHORIZED_CHRC_UUID, + BT_GATT_CHRC_WRITE | BT_GATT_CHRC_READ, + BT_GATT_PERM_WRITE | BT_GATT_PERM_READ, + read_test_authorized_chrc, + write_test_authorized_chrc, NULL), + BT_GATT_CHARACTERISTIC(TEST_CP_CHRC_UUID, + BT_GATT_CHRC_WRITE, + BT_GATT_PERM_WRITE, + NULL, write_cp_chrc, NULL), +); + +static bool gatt_read_operation_authorize(struct bt_conn *conn, + const struct bt_gatt_attr *attr) +{ + if (bt_uuid_cmp(attr->uuid, TEST_UNAUTHORIZED_CHRC_UUID) == 0) { + unauthorized_chrc_ctx.auth_read_cnt++; + return false; + } else if (bt_uuid_cmp(attr->uuid, TEST_AUTHORIZED_CHRC_UUID) == 0) { + authorized_chrc_ctx.auth_read_cnt++; + return true; + } else { + return true; + } +} + +static bool gatt_write_operation_authorize(struct bt_conn *conn, + const struct bt_gatt_attr *attr) +{ + if (bt_uuid_cmp(attr->uuid, TEST_UNAUTHORIZED_CHRC_UUID) == 0) { + unauthorized_chrc_ctx.auth_write_cnt++; + return false; + } else if (bt_uuid_cmp(attr->uuid, TEST_AUTHORIZED_CHRC_UUID) == 0) { + authorized_chrc_ctx.auth_write_cnt++; + return true; + } else { + return true; + } +} + +static const struct bt_gatt_authorization_cb gatt_authorization_callbacks = { + .read_operation_authorize = gatt_read_operation_authorize, + .write_operation_authorize = gatt_write_operation_authorize, +}; + +static void test_main(void) +{ + int err; + const struct bt_data ad[] = { + BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)) + }; + + err = bt_gatt_authorization_cb_register(&gatt_authorization_callbacks); + if (err) { + FAIL("Registering GATT authorization callbacks failed (err %d)\n", err); + return; + } + + err = bt_enable(NULL); + if (err != 0) { + FAIL("Bluetooth init failed (err %d)\n", err); + return; + } + + printk("Bluetooth initialized\n"); + + err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, ad, ARRAY_SIZE(ad), NULL, 0); + if (err != 0) { + FAIL("Advertising failed to start (err %d)\n", err); + return; + } + + printk("Advertising successfully started\n"); + + WAIT_FOR_FLAG(flag_is_chrc_ctx_validated); + + PASS("GATT server passed\n"); +} + +static const struct bst_test_instance test_gatt_server[] = { + { + .test_id = "gatt_server", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = test_main + }, + BSTEST_END_MARKER +}; + +struct bst_test_list *test_gatt_server_install(struct bst_test_list *tests) +{ + return bst_add_tests(tests, test_gatt_server); +} diff --git a/tests/bsim/bluetooth/host/gatt/authorization/src/main.c b/tests/bsim/bluetooth/host/gatt/authorization/src/main.c new file mode 100644 index 000000000000..a95f0285e75a --- /dev/null +++ b/tests/bsim/bluetooth/host/gatt/authorization/src/main.c @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "bstests.h" + +extern struct bst_test_list *test_gatt_server_install(struct bst_test_list *tests); +extern struct bst_test_list *test_gatt_client_install(struct bst_test_list *tests); + +bst_test_install_t test_installers[] = { + test_gatt_server_install, + test_gatt_client_install, + NULL +}; + +int main(void) +{ + bst_main(); + return 0; +} diff --git a/tests/bsim/bluetooth/host/gatt/authorization/test_scripts/gatt.sh b/tests/bsim/bluetooth/host/gatt/authorization/test_scripts/gatt.sh new file mode 100755 index 000000000000..edad0eb86c9d --- /dev/null +++ b/tests/bsim/bluetooth/host/gatt/authorization/test_scripts/gatt.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# Copyright 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +simulation_id="gatt_authorization" +verbosity_level=2 +EXECUTE_TIMEOUT=120 + +cd ${BSIM_OUT_PATH}/bin + +Execute ./bs_${BOARD}_tests_bsim_bluetooth_host_gatt_authorization_prj_conf \ + -v=${verbosity_level} -s=${simulation_id} -d=0 -testid=gatt_client + +Execute ./bs_${BOARD}_tests_bsim_bluetooth_host_gatt_authorization_prj_conf \ + -v=${verbosity_level} -s=${simulation_id} -d=1 -testid=gatt_server + +Execute ./bs_2G4_phy_v1 -v=${verbosity_level} -s=${simulation_id} \ + -D=2 -sim_length=60e6 $@ + +wait_for_background_jobs