Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions include/zephyr/bluetooth/gatt.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

/**
Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions subsys/bluetooth/host/Kconfig.gatt
Original file line number Diff line number Diff line change
Expand Up @@ -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
83 changes: 83 additions & 0 deletions subsys/bluetooth/host/att.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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)
{
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
1 change: 1 addition & 0 deletions tests/bsim/bluetooth/host/compile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions tests/bsim/bluetooth/host/gatt/authorization/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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/
)
7 changes: 7 additions & 0 deletions tests/bsim/bluetooth/host/gatt/authorization/prj.conf
Original file line number Diff line number Diff line change
@@ -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
20 changes: 20 additions & 0 deletions tests/bsim/bluetooth/host/gatt/authorization/src/common.c
Original file line number Diff line number Diff line change
@@ -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;
}
73 changes: 73 additions & 0 deletions tests/bsim/bluetooth/host/gatt/authorization/src/common.h
Original file line number Diff line number Diff line change
@@ -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 <zephyr/kernel.h>

#include "bs_types.h"
#include "bs_tracing.h"
#include "time_machine.h"
#include "bstests.h"

#include <zephyr/types.h>
#include <stddef.h>
#include <errno.h>

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>

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);
Loading