blob: 6744d772105882c3958d538e270cb060f3d3c5d9 [file] [log] [blame]
/* gatt.c - Generic Attribute Profile handling */
/*
* Copyright (c) 2015-2016 Intel Corporation
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <string.h>
#include <errno.h>
#include <stdbool.h>
#include <stdlib.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/iterable_sections.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/check.h>
#include <zephyr/settings/settings.h>
#if defined(CONFIG_BT_GATT_CACHING)
#include <tinycrypt/constants.h>
#include <tinycrypt/utils.h>
#include <tinycrypt/aes.h>
#include <tinycrypt/cmac_mode.h>
#include <tinycrypt/ccm_mode.h>
#endif /* CONFIG_BT_GATT_CACHING */
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/drivers/bluetooth/hci_driver.h>
#include "common/bt_str.h"
#include "hci_core.h"
#include "conn_internal.h"
#include "keys.h"
#include "l2cap_internal.h"
#include "att_internal.h"
#include "smp.h"
#include "settings.h"
#include "gatt_internal.h"
#include "long_wq.h"
#define LOG_LEVEL CONFIG_BT_GATT_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_gatt);
#define SC_TIMEOUT K_MSEC(10)
#define DB_HASH_TIMEOUT K_MSEC(10)
static uint16_t last_static_handle;
/* Persistent storage format for GATT CCC */
struct ccc_store {
uint16_t handle;
uint16_t value;
};
struct gatt_sub {
uint8_t id;
bt_addr_le_t peer;
sys_slist_t list;
};
#if defined(CONFIG_BT_GATT_CLIENT)
#define SUB_MAX (CONFIG_BT_MAX_PAIRED + CONFIG_BT_MAX_CONN)
#else
#define SUB_MAX 0
#endif /* CONFIG_BT_GATT_CLIENT */
/**
* Entry x is free for reuse whenever (subscriptions[x].peer == BT_ADDR_LE_ANY).
* Invariant: (sys_slist_is_empty(subscriptions[x].list))
* <=> (subscriptions[x].peer == BT_ADDR_LE_ANY).
*/
static struct gatt_sub subscriptions[SUB_MAX];
static sys_slist_t callback_list = SYS_SLIST_STATIC_INIT(&callback_list);
#if defined(CONFIG_BT_GATT_DYNAMIC_DB)
static sys_slist_t db;
#endif /* CONFIG_BT_GATT_DYNAMIC_DB */
enum gatt_global_flags {
GATT_INITIALIZED,
GATT_SERVICE_INITIALIZED,
GATT_NUM_FLAGS,
};
static ATOMIC_DEFINE(gatt_flags, GATT_NUM_FLAGS);
static ssize_t read_name(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
const char *name = bt_get_name();
return bt_gatt_attr_read(conn, attr, buf, len, offset, name,
strlen(name));
}
#if defined(CONFIG_BT_DEVICE_NAME_GATT_WRITABLE)
static ssize_t write_name(struct bt_conn *conn, const struct bt_gatt_attr *attr,
const void *buf, uint16_t len, uint16_t offset,
uint8_t flags)
{
char value[CONFIG_BT_DEVICE_NAME_MAX] = {};
if (offset >= sizeof(value)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
if (offset + len >= sizeof(value)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
memcpy(value, buf, len);
bt_set_name(value);
return len;
}
#endif /* CONFIG_BT_DEVICE_NAME_GATT_WRITABLE */
static ssize_t read_appearance(struct bt_conn *conn,
const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
uint16_t appearance = sys_cpu_to_le16(bt_get_appearance());
return bt_gatt_attr_read(conn, attr, buf, len, offset, &appearance,
sizeof(appearance));
}
#if defined(CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE)
static ssize_t write_appearance(struct bt_conn *conn, const struct bt_gatt_attr *attr,
const void *buf, uint16_t len, uint16_t offset,
uint8_t flags)
{
uint16_t appearance_le = sys_cpu_to_le16(bt_get_appearance());
char * const appearance_le_bytes = (char *)&appearance_le;
uint16_t appearance;
int err;
if (offset >= sizeof(appearance_le)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
if ((offset + len) > sizeof(appearance_le)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
memcpy(&appearance_le_bytes[offset], buf, len);
appearance = sys_le16_to_cpu(appearance_le);
err = bt_set_appearance(appearance);
if (err) {
return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
}
return len;
}
#endif /* CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE */
#if CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE
#define GAP_APPEARANCE_PROPS (BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE)
#define GAP_APPEARANCE_PERMS (BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_AUTHEN)
#define GAP_APPEARANCE_WRITE_HANDLER write_appearance
#else
#define GAP_APPEARANCE_PROPS BT_GATT_CHRC_READ
#define GAP_APPEARANCE_PERMS BT_GATT_PERM_READ
#define GAP_APPEARANCE_WRITE_HANDLER NULL
#endif
#if defined (CONFIG_BT_GAP_PERIPHERAL_PREF_PARAMS)
/* This checks if the range entered is valid */
BUILD_ASSERT(!(CONFIG_BT_PERIPHERAL_PREF_MIN_INT > 3200 &&
CONFIG_BT_PERIPHERAL_PREF_MIN_INT < 0xffff));
BUILD_ASSERT(!(CONFIG_BT_PERIPHERAL_PREF_MAX_INT > 3200 &&
CONFIG_BT_PERIPHERAL_PREF_MAX_INT < 0xffff));
BUILD_ASSERT(!(CONFIG_BT_PERIPHERAL_PREF_TIMEOUT > 3200 &&
CONFIG_BT_PERIPHERAL_PREF_TIMEOUT < 0xffff));
BUILD_ASSERT((CONFIG_BT_PERIPHERAL_PREF_MIN_INT == 0xffff) ||
(CONFIG_BT_PERIPHERAL_PREF_MIN_INT <=
CONFIG_BT_PERIPHERAL_PREF_MAX_INT));
BUILD_ASSERT((CONFIG_BT_PERIPHERAL_PREF_TIMEOUT * 4U) >
((1U + CONFIG_BT_PERIPHERAL_PREF_LATENCY) *
CONFIG_BT_PERIPHERAL_PREF_MAX_INT));
static ssize_t read_ppcp(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
struct __packed {
uint16_t min_int;
uint16_t max_int;
uint16_t latency;
uint16_t timeout;
} ppcp;
ppcp.min_int = sys_cpu_to_le16(CONFIG_BT_PERIPHERAL_PREF_MIN_INT);
ppcp.max_int = sys_cpu_to_le16(CONFIG_BT_PERIPHERAL_PREF_MAX_INT);
ppcp.latency = sys_cpu_to_le16(CONFIG_BT_PERIPHERAL_PREF_LATENCY);
ppcp.timeout = sys_cpu_to_le16(CONFIG_BT_PERIPHERAL_PREF_TIMEOUT);
return bt_gatt_attr_read(conn, attr, buf, len, offset, &ppcp,
sizeof(ppcp));
}
#endif
#if defined(CONFIG_BT_CENTRAL) && defined(CONFIG_BT_PRIVACY)
static ssize_t read_central_addr_res(struct bt_conn *conn,
const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
uint8_t central_addr_res = BT_GATT_CENTRAL_ADDR_RES_SUPP;
return bt_gatt_attr_read(conn, attr, buf, len, offset,
&central_addr_res, sizeof(central_addr_res));
}
#endif /* CONFIG_BT_CENTRAL && CONFIG_BT_PRIVACY */
BT_GATT_SERVICE_DEFINE(_2_gap_svc,
BT_GATT_PRIMARY_SERVICE(BT_UUID_GAP),
#if defined(CONFIG_BT_DEVICE_NAME_GATT_WRITABLE)
/* Require pairing for writes to device name */
BT_GATT_CHARACTERISTIC(BT_UUID_GAP_DEVICE_NAME,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ |
#if defined(CONFIG_DEVICE_NAME_GATT_WRITABLE_AUTHEN)
BT_GATT_PERM_WRITE_AUTHEN,
#elif defined(CONFIG_DEVICE_NAME_GATT_WRITABLE_ENCRYPT)
BT_GATT_PERM_WRITE_ENCRYPT,
#else
BT_GATT_PERM_WRITE,
#endif
read_name, write_name, bt_dev.name),
#else
BT_GATT_CHARACTERISTIC(BT_UUID_GAP_DEVICE_NAME, BT_GATT_CHRC_READ,
BT_GATT_PERM_READ, read_name, NULL, NULL),
#endif /* CONFIG_BT_DEVICE_NAME_GATT_WRITABLE */
BT_GATT_CHARACTERISTIC(BT_UUID_GAP_APPEARANCE, GAP_APPEARANCE_PROPS,
GAP_APPEARANCE_PERMS, read_appearance,
GAP_APPEARANCE_WRITE_HANDLER, NULL),
#if defined(CONFIG_BT_CENTRAL) && defined(CONFIG_BT_PRIVACY)
BT_GATT_CHARACTERISTIC(BT_UUID_CENTRAL_ADDR_RES,
BT_GATT_CHRC_READ, BT_GATT_PERM_READ,
read_central_addr_res, NULL, NULL),
#endif /* CONFIG_BT_CENTRAL && CONFIG_BT_PRIVACY */
#if defined(CONFIG_BT_GAP_PERIPHERAL_PREF_PARAMS)
BT_GATT_CHARACTERISTIC(BT_UUID_GAP_PPCP, BT_GATT_CHRC_READ,
BT_GATT_PERM_READ, read_ppcp, NULL, NULL),
#endif
);
struct sc_data {
uint16_t start;
uint16_t end;
} __packed;
struct gatt_sc_cfg {
uint8_t id;
bt_addr_le_t peer;
struct {
uint16_t start;
uint16_t end;
} data;
};
#if defined(CONFIG_BT_GATT_SERVICE_CHANGED)
#define SC_CFG_MAX (CONFIG_BT_MAX_PAIRED + CONFIG_BT_MAX_CONN)
#else
#define SC_CFG_MAX 0
#endif
static struct gatt_sc_cfg sc_cfg[SC_CFG_MAX];
BUILD_ASSERT(sizeof(struct sc_data) == sizeof(sc_cfg[0].data));
enum {
SC_RANGE_CHANGED, /* SC range changed */
SC_INDICATE_PENDING, /* SC indicate pending */
SC_LOAD, /* SC has been loaded from settings */
DB_HASH_VALID, /* Database hash needs to be calculated */
DB_HASH_LOAD, /* Database hash loaded from settings. */
DB_HASH_LOAD_PROC, /* DB hash loaded from settings has been processed. */
/* Total number of flags - must be at the end of the enum */
SC_NUM_FLAGS,
};
#if defined(CONFIG_BT_GATT_SERVICE_CHANGED)
static struct gatt_sc {
struct bt_gatt_indicate_params params;
uint16_t start;
uint16_t end;
struct k_work_delayable work;
ATOMIC_DEFINE(flags, SC_NUM_FLAGS);
} gatt_sc;
#endif /* defined(CONFIG_BT_GATT_SERVICE_CHANGED) */
#if defined(CONFIG_BT_GATT_CACHING)
static struct db_hash {
uint8_t hash[16];
#if defined(CONFIG_BT_SETTINGS)
uint8_t stored_hash[16];
#endif
struct k_work_delayable work;
struct k_work_sync sync;
} db_hash;
#endif
static struct gatt_sc_cfg *find_sc_cfg(uint8_t id, const bt_addr_le_t *addr)
{
LOG_DBG("id: %u, addr: %s", id, bt_addr_le_str(addr));
for (size_t i = 0; i < ARRAY_SIZE(sc_cfg); i++) {
if (id == sc_cfg[i].id &&
bt_addr_le_eq(&sc_cfg[i].peer, addr)) {
return &sc_cfg[i];
}
}
return NULL;
}
static void sc_store(struct gatt_sc_cfg *cfg)
{
int err;
err = bt_settings_store_sc(cfg->id, &cfg->peer, &cfg->data, sizeof(cfg->data));
if (err) {
LOG_ERR("failed to store SC (err %d)", err);
return;
}
LOG_DBG("stored SC for %s (0x%04x-0x%04x)", bt_addr_le_str(&cfg->peer), cfg->data.start,
cfg->data.end);
}
static void clear_sc_cfg(struct gatt_sc_cfg *cfg)
{
memset(cfg, 0, sizeof(*cfg));
}
static int bt_gatt_clear_sc(uint8_t id, const bt_addr_le_t *addr)
{
struct gatt_sc_cfg *cfg;
cfg = find_sc_cfg(id, (bt_addr_le_t *)addr);
if (!cfg) {
return 0;
}
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
int err;
err = bt_settings_delete_sc(cfg->id, &cfg->peer);
if (err) {
LOG_ERR("failed to delete SC (err %d)", err);
} else {
LOG_DBG("deleted SC for %s", bt_addr_le_str(&cfg->peer));
}
}
clear_sc_cfg(cfg);
return 0;
}
static void sc_clear(struct bt_conn *conn)
{
if (bt_addr_le_is_bonded(conn->id, &conn->le.dst)) {
int err;
err = bt_gatt_clear_sc(conn->id, &conn->le.dst);
if (err) {
LOG_ERR("Failed to clear SC %d", err);
}
} else {
struct gatt_sc_cfg *cfg;
cfg = find_sc_cfg(conn->id, &conn->le.dst);
if (cfg) {
clear_sc_cfg(cfg);
}
}
}
static void sc_reset(struct gatt_sc_cfg *cfg)
{
LOG_DBG("peer %s", bt_addr_le_str(&cfg->peer));
memset(&cfg->data, 0, sizeof(cfg->data));
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
sc_store(cfg);
}
}
static bool update_range(uint16_t *start, uint16_t *end, uint16_t new_start,
uint16_t new_end)
{
LOG_DBG("start 0x%04x end 0x%04x new_start 0x%04x new_end 0x%04x", *start, *end, new_start,
new_end);
/* Check if inside existing range */
if (new_start >= *start && new_end <= *end) {
return false;
}
/* Update range */
if (*start > new_start) {
*start = new_start;
}
if (*end < new_end) {
*end = new_end;
}
return true;
}
static void sc_save(uint8_t id, bt_addr_le_t *peer, uint16_t start, uint16_t end)
{
struct gatt_sc_cfg *cfg;
bool modified = false;
LOG_DBG("peer %s start 0x%04x end 0x%04x", bt_addr_le_str(peer), start, end);
cfg = find_sc_cfg(id, peer);
if (!cfg) {
/* Find and initialize a free sc_cfg entry */
cfg = find_sc_cfg(BT_ID_DEFAULT, BT_ADDR_LE_ANY);
if (!cfg) {
LOG_ERR("unable to save SC: no cfg left");
return;
}
cfg->id = id;
bt_addr_le_copy(&cfg->peer, peer);
}
/* Check if there is any change stored */
if (!(cfg->data.start || cfg->data.end)) {
cfg->data.start = start;
cfg->data.end = end;
modified = true;
goto done;
}
modified = update_range(&cfg->data.start, &cfg->data.end, start, end);
done:
if (IS_ENABLED(CONFIG_BT_SETTINGS) &&
modified && bt_addr_le_is_bonded(cfg->id, &cfg->peer)) {
sc_store(cfg);
}
}
static ssize_t sc_ccc_cfg_write(struct bt_conn *conn,
const struct bt_gatt_attr *attr, uint16_t value)
{
LOG_DBG("value 0x%04x", value);
if (value == BT_GATT_CCC_INDICATE) {
/* Create a new SC configuration entry if subscribed */
sc_save(conn->id, &conn->le.dst, 0, 0);
} else {
sc_clear(conn);
}
return sizeof(value);
}
static struct _bt_gatt_ccc sc_ccc = BT_GATT_CCC_INITIALIZER(NULL,
sc_ccc_cfg_write,
NULL);
/* Do not shuffle the values in this enum, they are used as bit offsets when
* saving the CF flags to NVS (i.e. NVS persists between FW upgrades).
*/
enum {
CF_CHANGE_AWARE, /* Client is changed aware */
CF_DB_HASH_READ, /* The client has read the database hash */
/* Total number of flags - must be at the end of the enum */
CF_NUM_FLAGS,
};
#define CF_BIT_ROBUST_CACHING 0
#define CF_BIT_EATT 1
#define CF_BIT_NOTIFY_MULTI 2
#define CF_BIT_LAST CF_BIT_NOTIFY_MULTI
#define CF_NUM_BITS (CF_BIT_LAST + 1)
#define CF_NUM_BYTES ((CF_BIT_LAST / 8) + 1)
#define CF_FLAGS_STORE_LEN 1
#define CF_ROBUST_CACHING(_cfg) (_cfg->data[0] & BIT(CF_BIT_ROBUST_CACHING))
#define CF_EATT(_cfg) (_cfg->data[0] & BIT(CF_BIT_EATT))
#define CF_NOTIFY_MULTI(_cfg) (_cfg->data[0] & BIT(CF_BIT_NOTIFY_MULTI))
struct gatt_cf_cfg {
uint8_t id;
bt_addr_le_t peer;
uint8_t data[CF_NUM_BYTES];
ATOMIC_DEFINE(flags, CF_NUM_FLAGS);
};
#if defined(CONFIG_BT_GATT_CACHING)
#define CF_CFG_MAX (CONFIG_BT_MAX_PAIRED + CONFIG_BT_MAX_CONN)
#else
#define CF_CFG_MAX 0
#endif /* CONFIG_BT_GATT_CACHING */
static struct gatt_cf_cfg cf_cfg[CF_CFG_MAX] = {};
static void clear_cf_cfg(struct gatt_cf_cfg *cfg)
{
bt_addr_le_copy(&cfg->peer, BT_ADDR_LE_ANY);
memset(cfg->data, 0, sizeof(cfg->data));
atomic_set(cfg->flags, 0);
}
enum delayed_store_flags {
DELAYED_STORE_CCC,
DELAYED_STORE_CF,
DELAYED_STORE_NUM_FLAGS
};
#if defined(CONFIG_BT_SETTINGS_DELAYED_STORE)
static void gatt_delayed_store_enqueue(uint8_t id, const bt_addr_le_t *peer_addr,
enum delayed_store_flags flag);
#endif
#if defined(CONFIG_BT_GATT_CACHING)
static bool set_change_aware_no_store(struct gatt_cf_cfg *cfg, bool aware)
{
bool changed;
if (aware) {
changed = !atomic_test_and_set_bit(cfg->flags, CF_CHANGE_AWARE);
} else {
changed = atomic_test_and_clear_bit(cfg->flags, CF_CHANGE_AWARE);
}
LOG_DBG("peer is now change-%saware", aware ? "" : "un");
return changed;
}
static void set_change_aware(struct gatt_cf_cfg *cfg, bool aware)
{
bool changed = set_change_aware_no_store(cfg, aware);
#if defined(CONFIG_BT_SETTINGS_DELAYED_STORE)
if (changed) {
gatt_delayed_store_enqueue(cfg->id, &cfg->peer, DELAYED_STORE_CF);
}
#else
(void)changed;
#endif
}
static int bt_gatt_store_cf(uint8_t id, const bt_addr_le_t *peer);
static void set_all_change_unaware(void)
{
#if defined(CONFIG_BT_SETTINGS)
/* Mark all bonded peers as change-unaware.
* - Can be called when not in a connection with said peers
* - Doesn't have any effect when no bonds are in memory. This is the
* case when the device has just booted and `settings_load` hasn't yet
* been called.
* - Expensive to call, as it will write the new status to settings
* right away.
*/
for (size_t i = 0; i < ARRAY_SIZE(cf_cfg); i++) {
struct gatt_cf_cfg *cfg = &cf_cfg[i];
if (!bt_addr_le_eq(&cfg->peer, BT_ADDR_LE_ANY)) {
set_change_aware_no_store(cfg, false);
bt_gatt_store_cf(cfg->id, &cfg->peer);
}
}
#endif /* CONFIG_BT_SETTINGS */
}
static struct gatt_cf_cfg *find_cf_cfg(struct bt_conn *conn)
{
int i;
for (i = 0; i < ARRAY_SIZE(cf_cfg); i++) {
struct gatt_cf_cfg *cfg = &cf_cfg[i];
if (!conn) {
if (bt_addr_le_eq(&cfg->peer, BT_ADDR_LE_ANY)) {
return cfg;
}
} else if (bt_conn_is_peer_addr_le(conn, cfg->id, &cfg->peer)) {
return cfg;
}
}
return NULL;
}
static ssize_t cf_read(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
struct gatt_cf_cfg *cfg;
uint8_t data[1] = {};
cfg = find_cf_cfg(conn);
if (cfg) {
memcpy(data, cfg->data, sizeof(data));
}
return bt_gatt_attr_read(conn, attr, buf, len, offset, data,
sizeof(data));
}
static bool cf_set_value(struct gatt_cf_cfg *cfg, const uint8_t *value, uint16_t len)
{
uint16_t i;
/* Validate the bits */
for (i = 0U; i <= CF_BIT_LAST && (i / 8) < len; i++) {
if ((cfg->data[i / 8] & BIT(i % 8)) &&
!(value[i / 8] & BIT(i % 8))) {
/* A client shall never clear a bit it has set */
return false;
}
}
/* Set the bits for each octect */
for (i = 0U; i < len && i < CF_NUM_BYTES; i++) {
if (i == (CF_NUM_BYTES - 1)) {
cfg->data[i] |= value[i] & BIT_MASK(CF_NUM_BITS % 8);
} else {
cfg->data[i] |= value[i];
}
LOG_DBG("byte %u: data 0x%02x value 0x%02x", i, cfg->data[i], value[i]);
}
return true;
}
static ssize_t cf_write(struct bt_conn *conn, const struct bt_gatt_attr *attr,
const void *buf, uint16_t len, uint16_t offset, uint8_t flags)
{
struct gatt_cf_cfg *cfg;
const uint8_t *value = buf;
if (offset > sizeof(cfg->data)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
if (offset + len > sizeof(cfg->data)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
cfg = find_cf_cfg(conn);
if (!cfg) {
cfg = find_cf_cfg(NULL);
}
if (!cfg) {
LOG_WRN("No space to store Client Supported Features");
return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES);
}
LOG_DBG("handle 0x%04x len %u", attr->handle, len);
if (!cf_set_value(cfg, value, len)) {
return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED);
}
bt_addr_le_copy(&cfg->peer, &conn->le.dst);
cfg->id = conn->id;
set_change_aware(cfg, true);
return len;
}
struct gen_hash_state {
struct tc_cmac_struct state;
int err;
};
union hash_attr_value {
/* Bluetooth Core Specification Version 5.3 | Vol 3, Part G
* Table 3.1: Service declaration
*/
union {
uint16_t uuid16;
uint8_t uuid128[BT_UUID_SIZE_128];
} __packed service;
/* Bluetooth Core Specification Version 5.3 | Vol 3, Part G
* Table 3.2: Include declaration
*/
struct {
uint16_t attribute_handle;
uint16_t end_group_handle;
uint16_t uuid16;
} __packed inc;
/* Bluetooth Core Specification Version 5.3 | Vol 3, Part G
* Table 3.3: Characteristic declaration
*/
struct {
uint8_t properties;
uint16_t value_handle;
union {
uint16_t uuid16;
uint8_t uuid128[BT_UUID_SIZE_128];
} __packed;
} __packed chrc;
/* Bluetooth Core Specification Version 5.3 | Vol 3, Part G
* Table 3.5: Characteristic Properties bit field
*/
struct {
uint16_t properties;
} __packed cep;
} __packed;
static uint8_t gen_hash_m(const struct bt_gatt_attr *attr, uint16_t handle,
void *user_data)
{
struct gen_hash_state *state = user_data;
struct bt_uuid_16 *u16;
uint8_t data[sizeof(union hash_attr_value)];
ssize_t len;
uint16_t value;
if (attr->uuid->type != BT_UUID_TYPE_16)
return BT_GATT_ITER_CONTINUE;
u16 = (struct bt_uuid_16 *)attr->uuid;
switch (u16->val) {
/* Attributes to hash: handle + UUID + value */
case BT_UUID_GATT_PRIMARY_VAL:
case BT_UUID_GATT_SECONDARY_VAL:
case BT_UUID_GATT_INCLUDE_VAL:
case BT_UUID_GATT_CHRC_VAL:
case BT_UUID_GATT_CEP_VAL:
value = sys_cpu_to_le16(handle);
if (tc_cmac_update(&state->state, (uint8_t *)&value,
sizeof(handle)) == TC_CRYPTO_FAIL) {
state->err = -EINVAL;
return BT_GATT_ITER_STOP;
}
value = sys_cpu_to_le16(u16->val);
if (tc_cmac_update(&state->state, (uint8_t *)&value,
sizeof(u16->val)) == TC_CRYPTO_FAIL) {
state->err = -EINVAL;
return BT_GATT_ITER_STOP;
}
len = attr->read(NULL, attr, data, sizeof(data), 0);
if (len < 0) {
state->err = len;
return BT_GATT_ITER_STOP;
}
if (tc_cmac_update(&state->state, data, len) ==
TC_CRYPTO_FAIL) {
state->err = -EINVAL;
return BT_GATT_ITER_STOP;
}
break;
/* Attributes to hash: handle + UUID */
case BT_UUID_GATT_CUD_VAL:
case BT_UUID_GATT_CCC_VAL:
case BT_UUID_GATT_SCC_VAL:
case BT_UUID_GATT_CPF_VAL:
case BT_UUID_GATT_CAF_VAL:
value = sys_cpu_to_le16(handle);
if (tc_cmac_update(&state->state, (uint8_t *)&value,
sizeof(handle)) == TC_CRYPTO_FAIL) {
state->err = -EINVAL;
return BT_GATT_ITER_STOP;
}
value = sys_cpu_to_le16(u16->val);
if (tc_cmac_update(&state->state, (uint8_t *)&value,
sizeof(u16->val)) == TC_CRYPTO_FAIL) {
state->err = -EINVAL;
return BT_GATT_ITER_STOP;
}
break;
default:
return BT_GATT_ITER_CONTINUE;
}
return BT_GATT_ITER_CONTINUE;
}
static void db_hash_store(void)
{
#if defined(CONFIG_BT_SETTINGS)
int err;
err = bt_settings_store_hash(&db_hash.hash, sizeof(db_hash.hash));
if (err) {
LOG_ERR("Failed to save Database Hash (err %d)", err);
}
LOG_DBG("Database Hash stored");
#endif /* CONFIG_BT_SETTINGS */
}
static void db_hash_gen(void)
{
uint8_t key[16] = {};
struct tc_aes_key_sched_struct sched;
struct gen_hash_state state;
if (tc_cmac_setup(&state.state, key, &sched) == TC_CRYPTO_FAIL) {
LOG_ERR("Unable to setup AES CMAC");
return;
}
bt_gatt_foreach_attr(0x0001, 0xffff, gen_hash_m, &state);
if (tc_cmac_final(db_hash.hash, &state.state) == TC_CRYPTO_FAIL) {
LOG_ERR("Unable to calculate hash");
return;
}
/**
* Core 5.1 does not state the endianess of the hash.
* However Vol 3, Part F, 3.3.1 says that multi-octet Characteristic
* Values shall be LE unless otherwise defined. PTS expects hash to be
* in little endianess as well. bt_smp_aes_cmac calculates the hash in
* big endianess so we have to swap.
*/
sys_mem_swap(db_hash.hash, sizeof(db_hash.hash));
LOG_HEXDUMP_DBG(db_hash.hash, sizeof(db_hash.hash), "Hash: ");
atomic_set_bit(gatt_sc.flags, DB_HASH_VALID);
}
static void sc_indicate(uint16_t start, uint16_t end);
static void do_db_hash(void)
{
bool new_hash = !atomic_test_bit(gatt_sc.flags, DB_HASH_VALID);
if (new_hash) {
db_hash_gen();
}
#if defined(CONFIG_BT_SETTINGS)
bool hash_loaded_from_settings =
atomic_test_bit(gatt_sc.flags, DB_HASH_LOAD);
bool already_processed =
atomic_test_bit(gatt_sc.flags, DB_HASH_LOAD_PROC);
if (!hash_loaded_from_settings) {
/* we want to generate the hash, but not overwrite the hash
* stored in settings, that we haven't yet loaded.
*/
return;
}
if (already_processed) {
/* hash has been loaded from settings and we have already
* executed the special case below once. we can now safely save
* the calculated hash to settings (if it has changed).
*/
if (new_hash) {
set_all_change_unaware();
db_hash_store();
}
} else {
/* this is only supposed to run once, on bootup, after the hash
* has been loaded from settings.
*/
atomic_set_bit(gatt_sc.flags, DB_HASH_LOAD_PROC);
/* Check if hash matches then skip SC update */
if (!memcmp(db_hash.stored_hash, db_hash.hash,
sizeof(db_hash.stored_hash))) {
LOG_DBG("Database Hash matches");
k_work_cancel_delayable(&gatt_sc.work);
atomic_clear_bit(gatt_sc.flags, SC_RANGE_CHANGED);
return;
}
LOG_HEXDUMP_DBG(db_hash.hash, sizeof(db_hash.hash), "New Hash: ");
/* GATT database has been modified since last boot, likely due
* to a firmware update or a dynamic service that was not
* re-registered on boot.
* Indicate Service Changed to all bonded devices for the full
* database range to invalidate client-side cache and force
* discovery on reconnect.
*/
sc_indicate(0x0001, 0xffff);
/* Hash did not match, overwrite with current hash.
* Also immediately set all peers (in settings) as
* change-unaware.
*/
set_all_change_unaware();
db_hash_store();
}
#endif /* defined(CONFIG_BT_SETTINGS) */
}
static void db_hash_process(struct k_work *work)
{
do_db_hash();
}
static ssize_t db_hash_read(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
struct gatt_cf_cfg *cfg;
/* Check if db_hash is already pending in which case it shall be
* generated immediately instead of waiting for the work to complete.
*/
(void)k_work_cancel_delayable_sync(&db_hash.work, &db_hash.sync);
if (!atomic_test_bit(gatt_sc.flags, DB_HASH_VALID)) {
db_hash_gen();
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
set_all_change_unaware();
db_hash_store();
}
}
/* BLUETOOTH CORE SPECIFICATION Version 5.1 | Vol 3, Part G page 2347:
* 2.5.2.1 Robust Caching
* A connected client becomes change-aware when...
* The client reads the Database Hash characteristic and then the server
* receives another ATT request from the client.
*/
cfg = find_cf_cfg(conn);
if (cfg &&
CF_ROBUST_CACHING(cfg) &&
!atomic_test_bit(cfg->flags, CF_CHANGE_AWARE)) {
atomic_set_bit(cfg->flags, CF_DB_HASH_READ);
}
return bt_gatt_attr_read(conn, attr, buf, len, offset, db_hash.hash,
sizeof(db_hash.hash));
}
static void remove_cf_cfg(struct bt_conn *conn)
{
struct gatt_cf_cfg *cfg;
cfg = find_cf_cfg(conn);
if (!cfg) {
return;
}
/* BLUETOOTH CORE SPECIFICATION Version 5.1 | Vol 3, Part G page 2405:
* For clients with a trusted relationship, the characteristic value
* shall be persistent across connections. For clients without a
* trusted relationship the characteristic value shall be set to the
* default value at each connection.
*/
if (!bt_addr_le_is_bonded(conn->id, &conn->le.dst)) {
clear_cf_cfg(cfg);
} else {
/* Update address in case it has changed */
bt_addr_le_copy(&cfg->peer, &conn->le.dst);
}
}
#if defined(CONFIG_BT_EATT)
#define SF_BIT_EATT 0
#define SF_BIT_LAST SF_BIT_EATT
static ssize_t sf_read(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
uint8_t value = BIT(SF_BIT_EATT);
return bt_gatt_attr_read(conn, attr, buf, len, offset, &value,
sizeof(value));
}
#endif /* CONFIG_BT_EATT */
#endif /* CONFIG_BT_GATT_CACHING */
static struct gatt_cf_cfg *find_cf_cfg_by_addr(uint8_t id,
const bt_addr_le_t *addr);
static int bt_gatt_store_cf(uint8_t id, const bt_addr_le_t *peer)
{
#if defined(CONFIG_BT_GATT_CACHING)
struct gatt_cf_cfg *cfg;
char dst[CF_NUM_BYTES + CF_FLAGS_STORE_LEN];
char *str;
size_t len;
int err;
cfg = find_cf_cfg_by_addr(id, peer);
if (!cfg) {
/* No cfg found, just clear it */
LOG_DBG("No config for CF");
str = NULL;
len = 0;
} else {
str = (char *)cfg->data;
len = sizeof(cfg->data);
/* add the CF data to a temp array */
memcpy(dst, str, len);
/* add the change-aware flag */
bool is_change_aware = atomic_test_bit(cfg->flags, CF_CHANGE_AWARE);
dst[len] = 0;
WRITE_BIT(dst[len], CF_CHANGE_AWARE, is_change_aware);
len += CF_FLAGS_STORE_LEN;
str = dst;
}
err = bt_settings_store_cf(id, peer, str, len);
if (err) {
LOG_ERR("Failed to store Client Features (err %d)", err);
return err;
}
LOG_DBG("Stored CF for %s", bt_addr_le_str(peer));
LOG_HEXDUMP_DBG(str, len, "Saved data");
#endif /* CONFIG_BT_GATT_CACHING */
return 0;
}
#if defined(CONFIG_BT_SETTINGS) && defined(CONFIG_BT_SMP)
/** Struct used to store both the id and the random address of a device when replacing
* random addresses in the ccc attribute's cfg array with the device's id address after
* pairing complete.
*/
struct addr_match {
const bt_addr_le_t *private_addr;
const bt_addr_le_t *id_addr;
};
static uint8_t convert_to_id_on_match(const struct bt_gatt_attr *attr,
uint16_t handle, void *user_data)
{
struct _bt_gatt_ccc *ccc;
struct addr_match *match = user_data;
/* Check if attribute is a CCC */
if (attr->write != bt_gatt_attr_write_ccc) {
return BT_GATT_ITER_CONTINUE;
}
ccc = attr->user_data;
/* Copy the device's id address to the config's address if the config's address is the
* same as the device's private address
*/
for (size_t i = 0; i < ARRAY_SIZE(ccc->cfg); i++) {
if (bt_addr_le_eq(&ccc->cfg[i].peer, match->private_addr)) {
bt_addr_le_copy(&ccc->cfg[i].peer, match->id_addr);
}
}
return BT_GATT_ITER_CONTINUE;
}
static void bt_gatt_identity_resolved(struct bt_conn *conn, const bt_addr_le_t *private_addr,
const bt_addr_le_t *id_addr)
{
/* Update the ccc cfg addresses */
struct addr_match user_data = {
.private_addr = private_addr,
.id_addr = id_addr
};
bool is_bonded = bt_addr_le_is_bonded(conn->id, &conn->le.dst);
bt_gatt_foreach_attr(0x0001, 0xffff, convert_to_id_on_match, &user_data);
/* Store the ccc */
if (is_bonded) {
bt_gatt_store_ccc(conn->id, &conn->le.dst);
}
/* Update the cf addresses and store it if we get a match */
struct gatt_cf_cfg *cfg = find_cf_cfg_by_addr(conn->id, private_addr);
if (cfg) {
bt_addr_le_copy(&cfg->peer, id_addr);
if (is_bonded) {
bt_gatt_store_cf(conn->id, &conn->le.dst);
}
}
}
static void bt_gatt_pairing_complete(struct bt_conn *conn, bool bonded)
{
if (bonded) {
/* Store the ccc and cf data */
bt_gatt_store_ccc(conn->id, &(conn->le.dst));
bt_gatt_store_cf(conn->id, &conn->le.dst);
}
}
#endif /* CONFIG_BT_SETTINGS && CONFIG_BT_SMP */
BT_GATT_SERVICE_DEFINE(_1_gatt_svc,
BT_GATT_PRIMARY_SERVICE(BT_UUID_GATT),
#if defined(CONFIG_BT_GATT_SERVICE_CHANGED)
/* Bluetooth 5.0, Vol3 Part G:
* The Service Changed characteristic Attribute Handle on the server
* shall not change if the server has a trusted relationship with any
* client.
*/
BT_GATT_CHARACTERISTIC(BT_UUID_GATT_SC, BT_GATT_CHRC_INDICATE,
BT_GATT_PERM_NONE, NULL, NULL, NULL),
BT_GATT_CCC_MANAGED(&sc_ccc, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
#if defined(CONFIG_BT_GATT_CACHING)
BT_GATT_CHARACTERISTIC(BT_UUID_GATT_CLIENT_FEATURES,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
cf_read, cf_write, NULL),
BT_GATT_CHARACTERISTIC(BT_UUID_GATT_DB_HASH,
BT_GATT_CHRC_READ, BT_GATT_PERM_READ,
db_hash_read, NULL, NULL),
#if defined(CONFIG_BT_EATT)
BT_GATT_CHARACTERISTIC(BT_UUID_GATT_SERVER_FEATURES,
BT_GATT_CHRC_READ, BT_GATT_PERM_READ,
sf_read, NULL, NULL),
#endif /* CONFIG_BT_EATT */
#endif /* CONFIG_BT_GATT_CACHING */
#endif /* CONFIG_BT_GATT_SERVICE_CHANGED */
);
#if defined(CONFIG_BT_GATT_DYNAMIC_DB)
static uint8_t found_attr(const struct bt_gatt_attr *attr, uint16_t handle,
void *user_data)
{
const struct bt_gatt_attr **found = user_data;
*found = attr;
return BT_GATT_ITER_STOP;
}
static const struct bt_gatt_attr *find_attr(uint16_t handle)
{
const struct bt_gatt_attr *attr = NULL;
bt_gatt_foreach_attr(handle, handle, found_attr, &attr);
return attr;
}
static void gatt_insert(struct bt_gatt_service *svc, uint16_t last_handle)
{
struct bt_gatt_service *tmp, *prev = NULL;
if (last_handle == 0 || svc->attrs[0].handle > last_handle) {
sys_slist_append(&db, &svc->node);
return;
}
/* DB shall always have its service in ascending order */
SYS_SLIST_FOR_EACH_CONTAINER(&db, tmp, node) {
if (tmp->attrs[0].handle > svc->attrs[0].handle) {
if (prev) {
sys_slist_insert(&db, &prev->node, &svc->node);
} else {
sys_slist_prepend(&db, &svc->node);
}
return;
}
prev = tmp;
}
}
static int gatt_register(struct bt_gatt_service *svc)
{
struct bt_gatt_service *last;
uint16_t handle, last_handle;
struct bt_gatt_attr *attrs = svc->attrs;
uint16_t count = svc->attr_count;
if (sys_slist_is_empty(&db)) {
handle = last_static_handle;
last_handle = 0;
goto populate;
}
last = SYS_SLIST_PEEK_TAIL_CONTAINER(&db, last, node);
handle = last->attrs[last->attr_count - 1].handle;
last_handle = handle;
populate:
/* Populate the handles and append them to the list */
for (; attrs && count; attrs++, count--) {
if (!attrs->handle) {
/* Allocate handle if not set already */
attrs->handle = ++handle;
} else if (attrs->handle > handle) {
/* Use existing handle if valid */
handle = attrs->handle;
} else if (find_attr(attrs->handle)) {
/* Service has conflicting handles */
LOG_ERR("Unable to register handle 0x%04x", attrs->handle);
return -EINVAL;
}
LOG_DBG("attr %p handle 0x%04x uuid %s perm 0x%02x", attrs, attrs->handle,
bt_uuid_str(attrs->uuid), attrs->perm);
}
gatt_insert(svc, last_handle);
return 0;
}
#endif /* CONFIG_BT_GATT_DYNAMIC_DB */
static inline void sc_work_submit(k_timeout_t timeout)
{
#if defined(CONFIG_BT_GATT_SERVICE_CHANGED)
k_work_reschedule(&gatt_sc.work, timeout);
#endif
}
#if defined(CONFIG_BT_GATT_SERVICE_CHANGED)
static void sc_indicate_rsp(struct bt_conn *conn,
struct bt_gatt_indicate_params *params, uint8_t err)
{
#if defined(CONFIG_BT_GATT_CACHING)
struct gatt_cf_cfg *cfg;
#endif
LOG_DBG("err 0x%02x", err);
atomic_clear_bit(gatt_sc.flags, SC_INDICATE_PENDING);
/* Check if there is new change in the meantime */
if (atomic_test_bit(gatt_sc.flags, SC_RANGE_CHANGED)) {
/* Reschedule without any delay since it is waiting already */
sc_work_submit(K_NO_WAIT);
}
#if defined(CONFIG_BT_GATT_CACHING)
/* BLUETOOTH CORE SPECIFICATION Version 5.3 | Vol 3, Part G page 1476:
* 2.5.2.1 Robust Caching
* ... a change-unaware connected client using exactly one ATT bearer
* becomes change-aware when ...
* The client receives and confirms a Handle Value Indication
* for the Service Changed characteristic
*/
if (bt_att_fixed_chan_only(conn)) {
cfg = find_cf_cfg(conn);
if (cfg && CF_ROBUST_CACHING(cfg)) {
set_change_aware(cfg, true);
}
}
#endif /* CONFIG_BT_GATT_CACHING */
}
static void sc_process(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct gatt_sc *sc = CONTAINER_OF(dwork, struct gatt_sc, work);
uint16_t sc_range[2];
__ASSERT(!atomic_test_bit(sc->flags, SC_INDICATE_PENDING),
"Indicate already pending");
LOG_DBG("start 0x%04x end 0x%04x", sc->start, sc->end);
sc_range[0] = sys_cpu_to_le16(sc->start);
sc_range[1] = sys_cpu_to_le16(sc->end);
atomic_clear_bit(sc->flags, SC_RANGE_CHANGED);
sc->start = 0U;
sc->end = 0U;
sc->params.attr = &_1_gatt_svc.attrs[2];
sc->params.func = sc_indicate_rsp;
sc->params.data = &sc_range[0];
sc->params.len = sizeof(sc_range);
#if defined(CONFIG_BT_EATT)
sc->params.chan_opt = BT_ATT_CHAN_OPT_NONE;
#endif /* CONFIG_BT_EATT */
if (bt_gatt_indicate(NULL, &sc->params)) {
/* No connections to indicate */
return;
}
atomic_set_bit(sc->flags, SC_INDICATE_PENDING);
}
#endif /* defined(CONFIG_BT_GATT_SERVICE_CHANGED) */
static void clear_ccc_cfg(struct bt_gatt_ccc_cfg *cfg)
{
bt_addr_le_copy(&cfg->peer, BT_ADDR_LE_ANY);
cfg->id = 0U;
cfg->value = 0U;
}
static void gatt_store_ccc_cf(uint8_t id, const bt_addr_le_t *peer_addr);
struct ds_peer {
uint8_t id;
bt_addr_le_t peer;
ATOMIC_DEFINE(flags, DELAYED_STORE_NUM_FLAGS);
};
IF_ENABLED(CONFIG_BT_SETTINGS_DELAYED_STORE, (
static struct gatt_delayed_store {
struct ds_peer peer_list[CONFIG_BT_MAX_PAIRED + CONFIG_BT_MAX_CONN];
struct k_work_delayable work;
} gatt_delayed_store;
))
static struct ds_peer *gatt_delayed_store_find(uint8_t id,
const bt_addr_le_t *peer_addr)
{
IF_ENABLED(CONFIG_BT_SETTINGS_DELAYED_STORE, ({
struct ds_peer *el;
for (size_t i = 0; i < ARRAY_SIZE(gatt_delayed_store.peer_list); i++) {
el = &gatt_delayed_store.peer_list[i];
if (el->id == id &&
bt_addr_le_eq(peer_addr, &el->peer)) {
return el;
}
}
}))
return NULL;
}
static void gatt_delayed_store_free(struct ds_peer *el)
{
if (el) {
el->id = 0;
memset(&el->peer, 0, sizeof(el->peer));
atomic_set(el->flags, 0);
}
}
#if defined(CONFIG_BT_SETTINGS_DELAYED_STORE)
static struct ds_peer *gatt_delayed_store_alloc(uint8_t id,
const bt_addr_le_t *peer_addr)
{
struct ds_peer *el;
for (size_t i = 0; i < ARRAY_SIZE(gatt_delayed_store.peer_list); i++) {
el = &gatt_delayed_store.peer_list[i];
/* Checking for the flags is cheaper than a memcmp for the
* address, so we use that to signal that a given slot is
* free.
*/
if (atomic_get(el->flags) == 0) {
bt_addr_le_copy(&el->peer, peer_addr);
el->id = id;
return el;
}
}
return NULL;
}
static void gatt_delayed_store_enqueue(uint8_t id, const bt_addr_le_t *peer_addr,
enum delayed_store_flags flag)
{
bool bonded = bt_addr_le_is_bonded(id, peer_addr);
struct ds_peer *el = gatt_delayed_store_find(id, peer_addr);
if (bonded) {
if (el == NULL) {
el = gatt_delayed_store_alloc(id, peer_addr);
__ASSERT(el != NULL, "Can't save CF / CCC to flash");
}
atomic_set_bit(el->flags, flag);
k_work_reschedule(&gatt_delayed_store.work,
K_MSEC(CONFIG_BT_SETTINGS_DELAYED_STORE_MS));
}
}
static void delayed_store(struct k_work *work)
{
struct ds_peer *el;
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct gatt_delayed_store *store =
CONTAINER_OF(dwork, struct gatt_delayed_store, work);
for (size_t i = 0; i < ARRAY_SIZE(gatt_delayed_store.peer_list); i++) {
el = &store->peer_list[i];
gatt_store_ccc_cf(el->id, &el->peer);
}
}
#endif /* CONFIG_BT_SETTINGS_DELAYED_STORE */
static void gatt_store_ccc_cf(uint8_t id, const bt_addr_le_t *peer_addr)
{
struct ds_peer *el = gatt_delayed_store_find(id, peer_addr);
if (bt_addr_le_is_bonded(id, peer_addr)) {
if (!IS_ENABLED(CONFIG_BT_SETTINGS_CCC_STORE_ON_WRITE) ||
(IS_ENABLED(CONFIG_BT_SETTINGS_CCC_STORE_ON_WRITE) && el &&
atomic_test_and_clear_bit(el->flags, DELAYED_STORE_CCC))) {
bt_gatt_store_ccc(id, peer_addr);
}
if (!IS_ENABLED(CONFIG_BT_SETTINGS_CF_STORE_ON_WRITE) ||
(IS_ENABLED(CONFIG_BT_SETTINGS_CF_STORE_ON_WRITE) && el &&
atomic_test_and_clear_bit(el->flags, DELAYED_STORE_CF))) {
bt_gatt_store_cf(id, peer_addr);
}
if (el && atomic_get(el->flags) == 0) {
gatt_delayed_store_free(el);
}
}
}
static void bt_gatt_service_init(void)
{
if (atomic_test_and_set_bit(gatt_flags, GATT_SERVICE_INITIALIZED)) {
return;
}
STRUCT_SECTION_FOREACH(bt_gatt_service_static, svc) {
last_static_handle += svc->attr_count;
}
}
void bt_gatt_init(void)
{
if (atomic_test_and_set_bit(gatt_flags, GATT_INITIALIZED)) {
return;
}
bt_gatt_service_init();
#if defined(CONFIG_BT_GATT_CACHING)
k_work_init_delayable(&db_hash.work, db_hash_process);
/* Submit work to Generate initial hash as there could be static
* services already in the database.
*/
if (IS_ENABLED(CONFIG_BT_LONG_WQ)) {
bt_long_wq_schedule(&db_hash.work, DB_HASH_TIMEOUT);
} else {
k_work_schedule(&db_hash.work, DB_HASH_TIMEOUT);
}
#endif /* CONFIG_BT_GATT_CACHING */
#if defined(CONFIG_BT_GATT_SERVICE_CHANGED)
k_work_init_delayable(&gatt_sc.work, sc_process);
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
/* Make sure to not send SC indications until SC
* settings are loaded
*/
atomic_set_bit(gatt_sc.flags, SC_INDICATE_PENDING);
}
#endif /* defined(CONFIG_BT_GATT_SERVICE_CHANGED) */
#if defined(CONFIG_BT_SETTINGS_DELAYED_STORE)
k_work_init_delayable(&gatt_delayed_store.work, delayed_store);
#endif
#if defined(CONFIG_BT_SETTINGS) && defined(CONFIG_BT_SMP)
static struct bt_conn_auth_info_cb gatt_conn_auth_info_cb = {
.pairing_complete = bt_gatt_pairing_complete,
};
/* Register the gatt module for authentication info callbacks so it can
* be notified when pairing has completed. This is used to enable CCC
* and CF storage on pairing complete.
*/
bt_conn_auth_info_cb_register(&gatt_conn_auth_info_cb);
static struct bt_conn_cb gatt_conn_cb = {
.identity_resolved = bt_gatt_identity_resolved,
};
/* Also update the address of CCC or CF writes that happened before the
* identity resolution. Note that to increase security in the future, we
* might want to explicitly not do this and treat a bonded device as a
* brand-new peer.
*/
bt_conn_cb_register(&gatt_conn_cb);
#endif /* CONFIG_BT_SETTINGS && CONFIG_BT_SMP */
}
static void sc_indicate(uint16_t start, uint16_t end)
{
#if defined(CONFIG_BT_GATT_DYNAMIC_DB) || \
(defined(CONFIG_BT_GATT_CACHING) && defined(CONFIG_BT_SETTINGS))
LOG_DBG("start 0x%04x end 0x%04x", start, end);
if (!atomic_test_and_set_bit(gatt_sc.flags, SC_RANGE_CHANGED)) {
gatt_sc.start = start;
gatt_sc.end = end;
goto submit;
}
if (!update_range(&gatt_sc.start, &gatt_sc.end, start, end)) {
return;
}
submit:
if (atomic_test_bit(gatt_sc.flags, SC_INDICATE_PENDING)) {
LOG_DBG("indicate pending, waiting until complete...");
return;
}
/* Reschedule since the range has changed */
sc_work_submit(SC_TIMEOUT);
#endif /* BT_GATT_DYNAMIC_DB || (BT_GATT_CACHING && BT_SETTINGS) */
}
void bt_gatt_cb_register(struct bt_gatt_cb *cb)
{
sys_slist_append(&callback_list, &cb->node);
}
#if defined(CONFIG_BT_GATT_DYNAMIC_DB)
static void db_changed(void)
{
#if defined(CONFIG_BT_GATT_CACHING)
struct bt_conn *conn;
int i;
atomic_clear_bit(gatt_sc.flags, DB_HASH_VALID);
if (IS_ENABLED(CONFIG_BT_LONG_WQ)) {
bt_long_wq_reschedule(&db_hash.work, DB_HASH_TIMEOUT);
} else {
k_work_reschedule(&db_hash.work, DB_HASH_TIMEOUT);
}
for (i = 0; i < ARRAY_SIZE(cf_cfg); i++) {
struct gatt_cf_cfg *cfg = &cf_cfg[i];
if (bt_addr_le_eq(&cfg->peer, BT_ADDR_LE_ANY)) {
continue;
}
if (CF_ROBUST_CACHING(cfg)) {
/* Core Spec 5.1 | Vol 3, Part G, 2.5.2.1 Robust Caching
*... the database changes again before the client
* becomes change-aware in which case the error response
* shall be sent again.
*/
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cfg->peer);
if (conn) {
bt_att_clear_out_of_sync_sent(conn);
bt_conn_unref(conn);
}
atomic_clear_bit(cfg->flags, CF_DB_HASH_READ);
set_change_aware(cfg, false);
}
}
#endif
}
static void gatt_unregister_ccc(struct _bt_gatt_ccc *ccc)
{
ccc->value = 0;
for (size_t i = 0; i < ARRAY_SIZE(ccc->cfg); i++) {
struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i];
if (!bt_addr_le_eq(&cfg->peer, BT_ADDR_LE_ANY)) {
struct bt_conn *conn;
bool store = true;
conn = bt_conn_lookup_addr_le(cfg->id, &cfg->peer);
if (conn) {
if (conn->state == BT_CONN_CONNECTED) {
#if defined(CONFIG_BT_SETTINGS_CCC_STORE_ON_WRITE)
gatt_delayed_store_enqueue(conn->id,
&conn->le.dst,
DELAYED_STORE_CCC);
#endif
store = false;
}
bt_conn_unref(conn);
}
if (IS_ENABLED(CONFIG_BT_SETTINGS) && store &&
bt_addr_le_is_bonded(cfg->id, &cfg->peer)) {
bt_gatt_store_ccc(cfg->id, &cfg->peer);
}
clear_ccc_cfg(cfg);
}
}
}
static int gatt_unregister(struct bt_gatt_service *svc)
{
if (!sys_slist_find_and_remove(&db, &svc->node)) {
return -ENOENT;
}
for (uint16_t i = 0; i < svc->attr_count; i++) {
struct bt_gatt_attr *attr = &svc->attrs[i];
if (attr->write == bt_gatt_attr_write_ccc) {
gatt_unregister_ccc(attr->user_data);
}
}
return 0;
}
int bt_gatt_service_register(struct bt_gatt_service *svc)
{
int err;
__ASSERT(svc, "invalid parameters\n");
__ASSERT(svc->attrs, "invalid parameters\n");
__ASSERT(svc->attr_count, "invalid parameters\n");
if (IS_ENABLED(CONFIG_BT_SETTINGS) &&
atomic_test_bit(gatt_flags, GATT_INITIALIZED) &&
!atomic_test_bit(gatt_sc.flags, SC_LOAD)) {
LOG_ERR("Can't register service after init and before settings are loaded.");
return -EINVAL;
}
/* Init GATT core services */
bt_gatt_service_init();
/* Do no allow to register mandatory services twice */
if (!bt_uuid_cmp(svc->attrs[0].uuid, BT_UUID_GAP) ||
!bt_uuid_cmp(svc->attrs[0].uuid, BT_UUID_GATT)) {
return -EALREADY;
}
k_sched_lock();
err = gatt_register(svc);
if (err < 0) {
k_sched_unlock();
return err;
}
/* Don't submit any work until the stack is initialized */
if (!atomic_test_bit(gatt_flags, GATT_INITIALIZED)) {
k_sched_unlock();
return 0;
}
sc_indicate(svc->attrs[0].handle,
svc->attrs[svc->attr_count - 1].handle);
db_changed();
k_sched_unlock();
return 0;
}
int bt_gatt_service_unregister(struct bt_gatt_service *svc)
{
int err;
__ASSERT(svc, "invalid parameters\n");
k_sched_lock();
err = gatt_unregister(svc);
if (err) {
k_sched_unlock();
return err;
}
/* Don't submit any work until the stack is initialized */
if (!atomic_test_bit(gatt_flags, GATT_INITIALIZED)) {
k_sched_unlock();
return 0;
}
sc_indicate(svc->attrs[0].handle,
svc->attrs[svc->attr_count - 1].handle);
db_changed();
k_sched_unlock();
return 0;
}
bool bt_gatt_service_is_registered(const struct bt_gatt_service *svc)
{
bool registered = false;
sys_snode_t *node;
k_sched_lock();
SYS_SLIST_FOR_EACH_NODE(&db, node) {
if (&svc->node == node) {
registered = true;
break;
}
}
k_sched_unlock();
return registered;
}
#endif /* CONFIG_BT_GATT_DYNAMIC_DB */
ssize_t bt_gatt_attr_read(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t buf_len, uint16_t offset,
const void *value, uint16_t value_len)
{
uint16_t len;
if (offset > value_len) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
len = MIN(buf_len, value_len - offset);
LOG_DBG("handle 0x%04x offset %u length %u", attr->handle, offset, len);
memcpy(buf, (uint8_t *)value + offset, len);
return len;
}
ssize_t bt_gatt_attr_read_service(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
const struct bt_uuid *uuid = attr->user_data;
if (uuid->type == BT_UUID_TYPE_16) {
uint16_t uuid16 = sys_cpu_to_le16(BT_UUID_16(uuid)->val);
return bt_gatt_attr_read(conn, attr, buf, len, offset,
&uuid16, 2);
}
return bt_gatt_attr_read(conn, attr, buf, len, offset,
BT_UUID_128(uuid)->val, 16);
}
struct gatt_incl {
uint16_t start_handle;
uint16_t end_handle;
uint16_t uuid16;
} __packed;
static uint8_t get_service_handles(const struct bt_gatt_attr *attr,
uint16_t handle, void *user_data)
{
struct gatt_incl *include = user_data;
/* Stop if attribute is a service */
if (!bt_uuid_cmp(attr->uuid, BT_UUID_GATT_PRIMARY) ||
!bt_uuid_cmp(attr->uuid, BT_UUID_GATT_SECONDARY)) {
return BT_GATT_ITER_STOP;
}
include->end_handle = sys_cpu_to_le16(handle);
return BT_GATT_ITER_CONTINUE;
}
uint16_t bt_gatt_attr_get_handle(const struct bt_gatt_attr *attr)
{
uint16_t handle = 1;
if (!attr) {
return 0;
}
if (attr->handle) {
return attr->handle;
}
STRUCT_SECTION_FOREACH(bt_gatt_service_static, static_svc) {
/* Skip ahead if start is not within service attributes array */
if ((attr < &static_svc->attrs[0]) ||
(attr > &static_svc->attrs[static_svc->attr_count - 1])) {
handle += static_svc->attr_count;
continue;
}
for (size_t i = 0; i < static_svc->attr_count; i++, handle++) {
if (attr == &static_svc->attrs[i]) {
return handle;
}
}
}
return 0;
}
ssize_t bt_gatt_attr_read_included(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
struct bt_gatt_attr *incl = attr->user_data;
uint16_t handle = bt_gatt_attr_get_handle(incl);
struct bt_uuid *uuid = incl->user_data;
struct gatt_incl pdu;
uint8_t value_len;
/* first attr points to the start handle */
pdu.start_handle = sys_cpu_to_le16(handle);
value_len = sizeof(pdu.start_handle) + sizeof(pdu.end_handle);
/*
* Core 4.2, Vol 3, Part G, 3.2,
* The Service UUID shall only be present when the UUID is a
* 16-bit Bluetooth UUID.
*/
if (uuid->type == BT_UUID_TYPE_16) {
pdu.uuid16 = sys_cpu_to_le16(BT_UUID_16(uuid)->val);
value_len += sizeof(pdu.uuid16);
}
/* Lookup for service end handle */
bt_gatt_foreach_attr(handle + 1, 0xffff, get_service_handles, &pdu);
return bt_gatt_attr_read(conn, attr, buf, len, offset, &pdu, value_len);
}
struct gatt_chrc {
uint8_t properties;
uint16_t value_handle;
union {
uint16_t uuid16;
uint8_t uuid[16];
};
} __packed;
uint16_t bt_gatt_attr_value_handle(const struct bt_gatt_attr *attr)
{
uint16_t handle = 0;
if (attr != NULL && bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CHRC) == 0) {
struct bt_gatt_chrc *chrc = attr->user_data;
handle = chrc->value_handle;
if (handle == 0) {
/* Fall back to Zephyr value handle policy */
handle = bt_gatt_attr_get_handle(attr) + 1U;
}
}
return handle;
}
ssize_t bt_gatt_attr_read_chrc(struct bt_conn *conn,
const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
struct bt_gatt_chrc *chrc = attr->user_data;
struct gatt_chrc pdu;
uint8_t value_len;
pdu.properties = chrc->properties;
/* BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part G] page 534:
* 3.3.2 Characteristic Value Declaration
* The Characteristic Value declaration contains the value of the
* characteristic. It is the first Attribute after the characteristic
* declaration. All characteristic definitions shall have a
* Characteristic Value declaration.
*/
pdu.value_handle = sys_cpu_to_le16(bt_gatt_attr_value_handle(attr));
value_len = sizeof(pdu.properties) + sizeof(pdu.value_handle);
if (chrc->uuid->type == BT_UUID_TYPE_16) {
pdu.uuid16 = sys_cpu_to_le16(BT_UUID_16(chrc->uuid)->val);
value_len += 2U;
} else {
memcpy(pdu.uuid, BT_UUID_128(chrc->uuid)->val, 16);
value_len += 16U;
}
return bt_gatt_attr_read(conn, attr, buf, len, offset, &pdu, value_len);
}
static uint8_t gatt_foreach_iter(const struct bt_gatt_attr *attr,
uint16_t handle, uint16_t start_handle,
uint16_t end_handle,
const struct bt_uuid *uuid,
const void *attr_data, uint16_t *num_matches,
bt_gatt_attr_func_t func, void *user_data)
{
uint8_t result;
/* Stop if over the requested range */
if (handle > end_handle) {
return BT_GATT_ITER_STOP;
}
/* Check if attribute handle is within range */
if (handle < start_handle) {
return BT_GATT_ITER_CONTINUE;
}
/* Match attribute UUID if set */
if (uuid && bt_uuid_cmp(uuid, attr->uuid)) {
return BT_GATT_ITER_CONTINUE;
}
/* Match attribute user_data if set */
if (attr_data && attr_data != attr->user_data) {
return BT_GATT_ITER_CONTINUE;
}
*num_matches -= 1;
result = func(attr, handle, user_data);
if (!*num_matches) {
return BT_GATT_ITER_STOP;
}
return result;
}
static void foreach_attr_type_dyndb(uint16_t start_handle, uint16_t end_handle,
const struct bt_uuid *uuid,
const void *attr_data, uint16_t num_matches,
bt_gatt_attr_func_t func, void *user_data)
{
#if defined(CONFIG_BT_GATT_DYNAMIC_DB)
size_t i;
struct bt_gatt_service *svc;
SYS_SLIST_FOR_EACH_CONTAINER(&db, svc, node) {
struct bt_gatt_service *next;
next = SYS_SLIST_PEEK_NEXT_CONTAINER(svc, node);
if (next) {
/* Skip ahead if start is not within service handles */
if (next->attrs[0].handle <= start_handle) {
continue;
}
}
for (i = 0; i < svc->attr_count; i++) {
struct bt_gatt_attr *attr = &svc->attrs[i];
if (gatt_foreach_iter(attr, attr->handle,
start_handle,
end_handle,
uuid, attr_data,
&num_matches,
func, user_data) ==
BT_GATT_ITER_STOP) {
return;
}
}
}
#endif /* CONFIG_BT_GATT_DYNAMIC_DB */
}
void bt_gatt_foreach_attr_type(uint16_t start_handle, uint16_t end_handle,
const struct bt_uuid *uuid,
const void *attr_data, uint16_t num_matches,
bt_gatt_attr_func_t func, void *user_data)
{
size_t i;
if (!num_matches) {
num_matches = UINT16_MAX;
}
if (start_handle <= last_static_handle) {
uint16_t handle = 1;
STRUCT_SECTION_FOREACH(bt_gatt_service_static, static_svc) {
/* Skip ahead if start is not within service handles */
if (handle + static_svc->attr_count < start_handle) {
handle += static_svc->attr_count;
continue;
}
for (i = 0; i < static_svc->attr_count; i++, handle++) {
if (gatt_foreach_iter(&static_svc->attrs[i],
handle, start_handle,
end_handle, uuid,
attr_data, &num_matches,
func, user_data) ==
BT_GATT_ITER_STOP) {
return;
}
}
}
}
/* Iterate over dynamic db */
foreach_attr_type_dyndb(start_handle, end_handle, uuid, attr_data,
num_matches, func, user_data);
}
static uint8_t find_next(const struct bt_gatt_attr *attr, uint16_t handle,
void *user_data)
{
struct bt_gatt_attr **next = user_data;
*next = (struct bt_gatt_attr *)attr;
return BT_GATT_ITER_STOP;
}
struct bt_gatt_attr *bt_gatt_attr_next(const struct bt_gatt_attr *attr)
{
struct bt_gatt_attr *next = NULL;
uint16_t handle = bt_gatt_attr_get_handle(attr);
bt_gatt_foreach_attr(handle + 1, handle + 1, find_next, &next);
return next;
}
static struct bt_gatt_ccc_cfg *find_ccc_cfg(const struct bt_conn *conn,
struct _bt_gatt_ccc *ccc)
{
for (size_t i = 0; i < ARRAY_SIZE(ccc->cfg); i++) {
struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i];
if (conn) {
if (bt_conn_is_peer_addr_le(conn, cfg->id,
&cfg->peer)) {
return cfg;
}
} else if (bt_addr_le_eq(&cfg->peer, BT_ADDR_LE_ANY)) {
return cfg;
}
}
return NULL;
}
ssize_t bt_gatt_attr_read_ccc(struct bt_conn *conn,
const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
struct _bt_gatt_ccc *ccc = attr->user_data;
const struct bt_gatt_ccc_cfg *cfg;
uint16_t value;
cfg = find_ccc_cfg(conn, ccc);
if (cfg) {
value = sys_cpu_to_le16(cfg->value);
} else {
/* Default to disable if there is no cfg for the peer */
value = 0x0000;
}
return bt_gatt_attr_read(conn, attr, buf, len, offset, &value,
sizeof(value));
}
static void gatt_ccc_changed(const struct bt_gatt_attr *attr,
struct _bt_gatt_ccc *ccc)
{
int i;
uint16_t value = 0x0000;
for (i = 0; i < ARRAY_SIZE(ccc->cfg); i++) {
if (ccc->cfg[i].value > value) {
value = ccc->cfg[i].value;
}
}
LOG_DBG("ccc %p value 0x%04x", ccc, value);
if (value != ccc->value) {
ccc->value = value;
if (ccc->cfg_changed) {
ccc->cfg_changed(attr, value);
}
}
}
ssize_t bt_gatt_attr_write_ccc(struct bt_conn *conn,
const struct bt_gatt_attr *attr, const void *buf,
uint16_t len, uint16_t offset, uint8_t flags)
{
struct _bt_gatt_ccc *ccc = attr->user_data;
struct bt_gatt_ccc_cfg *cfg;
bool value_changed;
uint16_t value;
if (offset) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
if (!len || len > sizeof(uint16_t)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
if (len < sizeof(uint16_t)) {
value = *(uint8_t *)buf;
} else {
value = sys_get_le16(buf);
}
cfg = find_ccc_cfg(conn, ccc);
if (!cfg) {
/* If there's no existing entry, but the new value is zero,
* we don't need to do anything, since a disabled CCC is
* behaviorally the same as no written CCC.
*/
if (!value) {
return len;
}
cfg = find_ccc_cfg(NULL, ccc);
if (!cfg) {
LOG_WRN("No space to store CCC cfg");
return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES);
}
bt_addr_le_copy(&cfg->peer, &conn->le.dst);
cfg->id = conn->id;
}
/* Confirm write if cfg is managed by application */
if (ccc->cfg_write) {
ssize_t write = ccc->cfg_write(conn, attr, value);
if (write < 0) {
return write;
}
/* Accept size=1 for backwards compatibility */
if (write != sizeof(value) && write != 1) {
return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
}
}
value_changed = cfg->value != value;
cfg->value = value;
LOG_DBG("handle 0x%04x value %u", attr->handle, cfg->value);
/* Update cfg if don't match */
if (cfg->value != ccc->value) {
gatt_ccc_changed(attr, ccc);
}
if (value_changed) {
#if defined(CONFIG_BT_SETTINGS_CCC_STORE_ON_WRITE)
/* Enqueue CCC store if value has changed for the connection */
gatt_delayed_store_enqueue(conn->id, &conn->le.dst, DELAYED_STORE_CCC);
#endif
}
/* Disabled CCC is the same as no configured CCC, so clear the entry */
if (!value) {
clear_ccc_cfg(cfg);
}
return len;
}
ssize_t bt_gatt_attr_read_cep(struct bt_conn *conn,
const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
const struct bt_gatt_cep *value = attr->user_data;
uint16_t props = sys_cpu_to_le16(value->properties);
return bt_gatt_attr_read(conn, attr, buf, len, offset, &props,
sizeof(props));
}
ssize_t bt_gatt_attr_read_cud(struct bt_conn *conn,
const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
const char *value = attr->user_data;
return bt_gatt_attr_read(conn, attr, buf, len, offset, value,
strlen(value));
}
struct gatt_cpf {
uint8_t format;
int8_t exponent;
uint16_t unit;
uint8_t name_space;
uint16_t description;
} __packed;
ssize_t bt_gatt_attr_read_cpf(struct bt_conn *conn,
const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
const struct bt_gatt_cpf *cpf = attr->user_data;
struct gatt_cpf value;
value.format = cpf->format;
value.exponent = cpf->exponent;
value.unit = sys_cpu_to_le16(cpf->unit);
value.name_space = cpf->name_space;
value.description = sys_cpu_to_le16(cpf->description);
return bt_gatt_attr_read(conn, attr, buf, len, offset, &value,
sizeof(value));
}
struct notify_data {
const struct bt_gatt_attr *attr;
uint16_t handle;
int err;
uint16_t type;
union {
struct bt_gatt_notify_params *nfy_params;
struct bt_gatt_indicate_params *ind_params;
};
};
#if defined(CONFIG_BT_GATT_NOTIFY_MULTIPLE)
static struct net_buf *nfy_mult[CONFIG_BT_MAX_CONN];
static int gatt_notify_mult_send(struct bt_conn *conn, struct net_buf *buf)
{
int ret;
uint8_t *pdu = buf->data;
/* PDU structure is [Opcode (1)] [Handle (2)] [Length (2)] [Value (Length)] */
uint16_t first_attr_len = sys_get_le16(&pdu[3]);
/* Convert to ATT_HANDLE_VALUE_NTF if containing a single handle. */
if (buf->len ==
(1 + sizeof(struct bt_att_notify_mult) + first_attr_len)) {
/* Store attr handle */
uint16_t handle = sys_get_le16(&pdu[1]);
/* Remove the ATT_MULTIPLE_HANDLE_VALUE_NTF opcode,
* attribute handle and length
*/
(void)net_buf_pull(buf, 1 + sizeof(struct bt_att_notify_mult));
/* Add back an ATT_HANDLE_VALUE_NTF opcode and attr handle */
/* PDU structure is now [Opcode (1)] [Handle (1)] [Value] */
net_buf_push_le16(buf, handle);
net_buf_push_u8(buf, BT_ATT_OP_NOTIFY);
LOG_DBG("Converted BT_ATT_OP_NOTIFY_MULT with single attr to BT_ATT_OP_NOTIFY");
}
ret = bt_att_send(conn, buf);
if (ret < 0) {
net_buf_unref(buf);
}
return ret;
}
static void notify_mult_process(struct k_work *work)
{
int i;
/* Send to any connection with an allocated buffer */
for (i = 0; i < ARRAY_SIZE(nfy_mult); i++) {
struct net_buf **buf = &nfy_mult[i];
if (*buf) {
struct bt_conn *conn = bt_conn_lookup_index(i);
gatt_notify_mult_send(conn, *buf);
*buf = NULL;
bt_conn_unref(conn);
}
}
}
K_WORK_DELAYABLE_DEFINE(nfy_mult_work, notify_mult_process);
static bool gatt_cf_notify_multi(struct bt_conn *conn)
{
struct gatt_cf_cfg *cfg;
cfg = find_cf_cfg(conn);
if (!cfg) {
return false;
}
return CF_NOTIFY_MULTI(cfg);
}
static int gatt_notify_flush(struct bt_conn *conn)
{
int err = 0;
struct net_buf **buf = &nfy_mult[bt_conn_index(conn)];
if (*buf) {
err = gatt_notify_mult_send(conn, *buf);
*buf = NULL;
}
return err;
}
static void cleanup_notify(struct bt_conn *conn)
{
struct net_buf **buf = &nfy_mult[bt_conn_index(conn)];
if (*buf) {
net_buf_unref(*buf);
*buf = NULL;
}
}
static void gatt_add_nfy_to_buf(struct net_buf *buf,
uint16_t handle,
struct bt_gatt_notify_params *params)
{
struct bt_att_notify_mult *nfy;
nfy = net_buf_add(buf, sizeof(*nfy));
nfy->handle = sys_cpu_to_le16(handle);
nfy->len = sys_cpu_to_le16(params->len);
net_buf_add(buf, params->len);
(void)memcpy(nfy->value, params->data, params->len);
}
#if (CONFIG_BT_GATT_NOTIFY_MULTIPLE_FLUSH_MS != 0)
static int gatt_notify_mult(struct bt_conn *conn, uint16_t handle,
struct bt_gatt_notify_params *params)
{
struct net_buf **buf = &nfy_mult[bt_conn_index(conn)];
/* Check if we can fit more data into it, in case it doesn't fit send
* the existing buffer and proceed to create a new one
*/
if (*buf && ((net_buf_tailroom(*buf) < sizeof(struct bt_att_notify_mult) + params->len) ||
!bt_att_tx_meta_data_match(*buf, params->func, params->user_data,
BT_ATT_CHAN_OPT(params)))) {
int ret;
ret = gatt_notify_mult_send(conn, *buf);
*buf = NULL;
if (ret < 0) {
return ret;
}
}
if (!*buf) {
*buf = bt_att_create_pdu(conn, BT_ATT_OP_NOTIFY_MULT,
sizeof(struct bt_att_notify_mult) + params->len);
if (!*buf) {
return -ENOMEM;
}
bt_att_set_tx_meta_data(*buf, params->func, params->user_data,
BT_ATT_CHAN_OPT(params));
} else {
/* Increment the number of handles, ensuring the notify callback
* gets called once for every attribute.
*/
bt_att_increment_tx_meta_data_attr_count(*buf, 1);
}
LOG_DBG("handle 0x%04x len %u", handle, params->len);
gatt_add_nfy_to_buf(*buf, handle, params);
/* Use `k_work_schedule` to keep the original deadline, instead of
* re-setting the timeout whenever a new notification is appended.
*/
k_work_schedule(&nfy_mult_work,
K_MSEC(CONFIG_BT_GATT_NOTIFY_MULTIPLE_FLUSH_MS));
return 0;
}
#endif /* CONFIG_BT_GATT_NOTIFY_MULTIPLE_FLUSH_MS != 0 */
#endif /* CONFIG_BT_GATT_NOTIFY_MULTIPLE */
static int gatt_notify(struct bt_conn *conn, uint16_t handle,
struct bt_gatt_notify_params *params)
{
struct net_buf *buf;
struct bt_att_notify *nfy;
#if defined(CONFIG_BT_GATT_ENFORCE_CHANGE_UNAWARE)
/* BLUETOOTH CORE SPECIFICATION Version 5.3
* Vol 3, Part G 2.5.3 (page 1479):
*
* Except for a Handle Value indication for the Service Changed
* characteristic, the server shall not send notifications and
* indications to such a client until it becomes change-aware.
*/
if (!bt_gatt_change_aware(conn, false)) {
return -EAGAIN;
}
#endif
/* Confirm that the connection has the correct level of security */
if (bt_gatt_check_perm(conn, params->attr, BT_GATT_PERM_READ_ENCRYPT_MASK)) {
LOG_WRN("Link is not encrypted");
return -EPERM;
}
if (IS_ENABLED(CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION)) {
/* Check if client has subscribed before sending notifications.
* This is not really required in the Bluetooth specification,
* but follows its spirit.
*/
if (!bt_gatt_is_subscribed(conn, params->attr, BT_GATT_CCC_NOTIFY)) {
LOG_WRN("Device is not subscribed to characteristic");
return -EINVAL;
}
}
if (IS_ENABLED(CONFIG_BT_EATT) &&
!bt_att_chan_opt_valid(conn, BT_ATT_CHAN_OPT(params))) {
return -EINVAL;
}
#if defined(CONFIG_BT_GATT_NOTIFY_MULTIPLE) && (CONFIG_BT_GATT_NOTIFY_MULTIPLE_FLUSH_MS != 0)
if (gatt_cf_notify_multi(conn)) {
return gatt_notify_mult(conn, handle, params);
}
#endif /* CONFIG_BT_GATT_NOTIFY_MULTIPLE */
buf = bt_att_create_pdu(conn, BT_ATT_OP_NOTIFY,
sizeof(*nfy) + params->len);
if (!buf) {
LOG_WRN("No buffer available to send notification");
return -ENOMEM;
}
LOG_DBG("conn %p handle 0x%04x", conn, handle);
nfy = net_buf_add(buf, sizeof(*nfy));
nfy->handle = sys_cpu_to_le16(handle);
net_buf_add(buf, params->len);
memcpy(nfy->value, params->data, params->len);
bt_att_set_tx_meta_data(buf, params->func, params->user_data, BT_ATT_CHAN_OPT(params));
return bt_att_send(conn, buf);
}
/* Converts error (negative errno) to ATT Error code */
static uint8_t att_err_from_int(int err)
{
LOG_DBG("%d", err);
/* ATT error codes are 1 byte values, so any value outside the range is unknown */
if (!IN_RANGE(err, 0, UINT8_MAX)) {
return BT_ATT_ERR_UNLIKELY;
}
return err;
}
static void gatt_indicate_rsp(struct bt_conn *conn, int err,
const void *pdu, uint16_t length, void *user_data)
{
struct bt_gatt_indicate_params *params = user_data;
if (params->func) {
params->func(conn, params, att_err_from_int(err));
}
params->_ref--;
if (params->destroy && (params->_ref == 0)) {
params->destroy(params);
}
}
static struct bt_att_req *gatt_req_alloc(bt_att_func_t func, void *params,
bt_att_encode_t encode,
uint8_t op,
size_t len)
{
struct bt_att_req *req;
/* Allocate new request */
req = bt_att_req_alloc(BT_ATT_TIMEOUT);
if (!req) {
return NULL;
}
#if defined(CONFIG_BT_SMP)
req->att_op = op;
req->len = len;
req->encode = encode;
#endif
req->func = func;
req->user_data = params;
return req;
}
#ifdef CONFIG_BT_GATT_CLIENT
static int gatt_req_send(struct bt_conn *conn, bt_att_func_t func, void *params,
bt_att_encode_t encode, uint8_t op, size_t len,
enum bt_att_chan_opt chan_opt)
{
struct bt_att_req *req;
struct net_buf *buf;
int err;
if (IS_ENABLED(CONFIG_BT_EATT) &&
!bt_att_chan_opt_valid(conn, chan_opt)) {
return -EINVAL;
}
req = gatt_req_alloc(func, params, encode, op, len);
if (!req) {
return -ENOMEM;
}
buf = bt_att_create_pdu(conn, op, len);
if (!buf) {
bt_att_req_free(req);
return -ENOMEM;
}
bt_att_set_tx_meta_data(buf, NULL, NULL, chan_opt);
req->buf = buf;
err = encode(buf, len, params);
if (err) {
bt_att_req_free(req);
return err;
}
err = bt_att_req_send(conn, req);
if (err) {
bt_att_req_free(req);
}
return err;
}
#endif
static int gatt_indicate(struct bt_conn *conn, uint16_t handle,
struct bt_gatt_indicate_params *params)
{
struct net_buf *buf;
struct bt_att_indicate *ind;
struct bt_att_req *req;
size_t len;
int err;
#if defined(CONFIG_BT_GATT_ENFORCE_CHANGE_UNAWARE)
/* BLUETOOTH CORE SPECIFICATION Version 5.1 | Vol 3, Part G page 2350:
* Except for the Handle Value indication, the server shall not send
* notifications and indications to such a client until it becomes
* change-aware.
*/
if (!(params->func && (params->func == sc_indicate_rsp ||
params->func == sc_restore_rsp)) &&
!bt_gatt_change_aware(conn, false)) {
return -EAGAIN;
}
#endif
/* Confirm that the connection has the correct level of security */
if (bt_gatt_check_perm(conn, params->attr, BT_GATT_PERM_READ_ENCRYPT_MASK)) {
LOG_WRN("Link is not encrypted");
return -EPERM;
}
if (IS_ENABLED(CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION)) {
/* Check if client has subscribed before sending notifications.
* This is not really required in the Bluetooth specification,
* but follows its spirit.
*/
if (!bt_gatt_is_subscribed(conn, params->attr, BT_GATT_CCC_INDICATE)) {
LOG_WRN("Device is not subscribed to characteristic");
return -EINVAL;
}
}
if (IS_ENABLED(CONFIG_BT_EATT) &&
!bt_att_chan_opt_valid(conn, BT_ATT_CHAN_OPT(params))) {
return -EINVAL;
}
len = sizeof(*ind) + params->len;
req = gatt_req_alloc(gatt_indicate_rsp, params, NULL,
BT_ATT_OP_INDICATE, len);
if (!req) {
return -ENOMEM;
}
buf = bt_att_create_pdu(conn, BT_ATT_OP_INDICATE, len);
if (!buf) {
LOG_WRN("No buffer available to send indication");
bt_att_req_free(req);
return -ENOMEM;
}
bt_att_set_tx_meta_data(buf, NULL, NULL, BT_ATT_CHAN_OPT(params));
ind = net_buf_add(buf, sizeof(*ind));
ind->handle = sys_cpu_to_le16(handle);
net_buf_add(buf, params->len);
memcpy(ind->value, params->data, params->len);
LOG_DBG("conn %p handle 0x%04x", conn, handle);
req->buf = buf;
err = bt_att_req_send(conn, req);
if (err) {
bt_att_req_free(req);
}
return err;
}
static uint8_t notify_cb(const struct bt_gatt_attr *attr, uint16_t handle,
void *user_data)
{
struct notify_data *data = user_data;
struct _bt_gatt_ccc *ccc;
size_t i;
/* Check attribute user_data must be of type struct _bt_gatt_ccc */
if (attr->write != bt_gatt_attr_write_ccc) {
return BT_GATT_ITER_CONTINUE;
}
ccc = attr->user_data;
/* Save Service Changed data if peer is not connected */
if (IS_ENABLED(CONFIG_BT_GATT_SERVICE_CHANGED) && ccc == &sc_ccc) {
for (i = 0; i < ARRAY_SIZE(sc_cfg); i++) {
struct gatt_sc_cfg *cfg = &sc_cfg[i];
struct bt_conn *conn;
if (bt_addr_le_eq(&cfg->peer, BT_ADDR_LE_ANY)) {
continue;
}
conn = bt_conn_lookup_state_le(cfg->id, &cfg->peer,
BT_CONN_CONNECTED);
if (!conn) {
struct sc_data *sc;
sc = (struct sc_data *)data->ind_params->data;
sc_save(cfg->id, &cfg->peer,
sys_le16_to_cpu(sc->start),
sys_le16_to_cpu(sc->end));
continue;
}
bt_conn_unref(conn);
}
}
/* Notify all peers configured */
for (i = 0; i < ARRAY_SIZE(ccc->cfg); i++) {
struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i];
struct bt_conn *conn;
int err;
/* Check if config value matches data type since consolidated
* value may be for a different peer.
*/
if (cfg->value != data->type) {
continue;
}
conn = bt_conn_lookup_addr_le(cfg->id, &cfg->peer);
if (!conn) {
continue;
}
if (conn->state != BT_CONN_CONNECTED) {
bt_conn_unref(conn);
continue;
}
/* Confirm match if cfg is managed by application */
if (ccc->cfg_match && !ccc->cfg_match(conn, attr)) {
bt_conn_unref(conn);
continue;
}
/* Confirm that the connection has the correct level of security */
if (bt_gatt_check_perm(conn, attr, BT_GATT_PERM_READ_ENCRYPT_MASK)) {
LOG_WRN("Link is not encrypted");
bt_conn_unref(conn);
continue;
}
/* Use the Characteristic Value handle discovered since the
* Client Characteristic Configuration descriptor may occur
* in any position within the characteristic definition after
* the Characteristic Value.
* Only notify or indicate devices which are subscribed.
*/
if ((data->type == BT_GATT_CCC_INDICATE) &&
(cfg->value & BT_GATT_CCC_INDICATE)) {
err = gatt_indicate(conn, data->handle, data->ind_params);
if (err == 0) {
data->ind_params->_ref++;
}
} else if ((data->type == BT_GATT_CCC_NOTIFY) &&
(cfg->value & BT_GATT_CCC_NOTIFY)) {
err = gatt_notify(conn, data->handle, data->nfy_params);
} else {
err = 0;
}
bt_conn_unref(conn);
data->err = err;
if (err < 0) {
return BT_GATT_ITER_STOP;
}
}
return BT_GATT_ITER_CONTINUE;
}
static uint8_t match_uuid(const struct bt_gatt_attr *attr, uint16_t handle,
void *user_data)
{
struct notify_data *data = user_data;
data->attr = attr;
data->handle = handle;
return BT_GATT_ITER_STOP;
}
static bool gatt_find_by_uuid(struct notify_data *found,
const struct bt_uuid *uuid)
{
found->attr = NULL;
bt_gatt_foreach_attr_type(found->handle, 0xffff, uuid, NULL, 1,
match_uuid, found);
return found->attr ? true : false;
}
struct bt_gatt_attr *bt_gatt_find_by_uuid(const struct bt_gatt_attr *attr,
uint16_t attr_count,
const struct bt_uuid *uuid)
{
struct bt_gatt_attr *found = NULL;
uint16_t start_handle = bt_gatt_attr_value_handle(attr);
uint16_t end_handle = start_handle && attr_count ?
start_handle + attr_count : 0xffff;
bt_gatt_foreach_attr_type(start_handle, end_handle, uuid, NULL, 1,
find_next, &found);
return found;
}
int bt_gatt_notify_cb(struct bt_conn *conn,
struct bt_gatt_notify_params *params)
{
struct notify_data data;
__ASSERT(params, "invalid parameters\n");
__ASSERT(params->attr || params->uuid, "invalid parameters\n");
if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) {
return -EAGAIN;
}
if (conn && conn->state != BT_CONN_CONNECTED) {
return -ENOTCONN;
}
data.attr = params->attr;
data.handle = bt_gatt_attr_get_handle(data.attr);
/* Lookup UUID if it was given */
if (params->uuid) {
if (!gatt_find_by_uuid(&data, params->uuid)) {
return -ENOENT;
}
params->attr = data.attr;
} else {
if (!data.handle) {
return -ENOENT;
}
}
/* Check if attribute is a characteristic then adjust the handle */
if (!bt_uuid_cmp(data.attr->uuid, BT_UUID_GATT_CHRC)) {
struct bt_gatt_chrc *chrc = data.attr->user_data;
if (!(chrc->properties & BT_GATT_CHRC_NOTIFY)) {
return -EINVAL;
}
data.handle = bt_gatt_attr_value_handle(data.attr);
}
if (conn) {
return gatt_notify(conn, data.handle, params);
}
data.err = -ENOTCONN;
data.type = BT_GATT_CCC_NOTIFY;
data.nfy_params = params;
bt_gatt_foreach_attr_type(data.handle, 0xffff, BT_UUID_GATT_CCC, NULL,
1, notify_cb, &data);
return data.err;
}
#if defined(CONFIG_BT_GATT_NOTIFY_MULTIPLE)
static int gatt_notify_multiple_verify_args(struct bt_conn *conn,
struct bt_gatt_notify_params params[],
uint16_t num_params)
{
__ASSERT(params, "invalid parameters\n");
__ASSERT(params->attr, "invalid parameters\n");
CHECKIF(num_params < 2) {
/* Use the standard notification API when sending only one
* notification.
*/
return -EINVAL;
}
CHECKIF(conn == NULL) {
/* Use the standard notification API to send to all connected
* peers.
*/
return -EINVAL;
}
if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) {
return -EAGAIN;
}
if (conn->state != BT_CONN_CONNECTED) {
return -ENOTCONN;
}
#if defined(CONFIG_BT_GATT_ENFORCE_CHANGE_UNAWARE)
/* BLUETOOTH CORE SPECIFICATION Version 5.3
* Vol 3, Part G 2.5.3 (page 1479):
*
* Except for a Handle Value indication for the Service Changed
* characteristic, the server shall not send notifications and
* indications to such a client until it becomes change-aware.
*/
if (!bt_gatt_change_aware(conn, false)) {
return -EAGAIN;
}
#endif
/* This API guarantees an ATT_MULTIPLE_HANDLE_VALUE_NTF over the air. */
if (!gatt_cf_notify_multi(conn)) {
return -EOPNOTSUPP;
}
return 0;
}
static int gatt_notify_multiple_verify_params(struct bt_conn *conn,
struct bt_gatt_notify_params params[],
uint16_t num_params, size_t *total_len)
{
for (uint16_t i = 0; i < num_params; i++) {
/* Compute the total data length. */
*total_len += params[i].len;
/* Confirm that the connection has the correct level of security. */
if (bt_gatt_check_perm(conn, params[i].attr,
BT_GATT_PERM_READ_ENCRYPT |
BT_GATT_PERM_READ_AUTHEN)) {
LOG_WRN("Link is not encrypted");
return -EPERM;
}
/* The current implementation requires the same callbacks and
* user_data.
*/
if ((params[0].func != params[i].func) ||
(params[0].user_data != params[i].user_data)) {
return -EINVAL;
}
/* This API doesn't support passing UUIDs. */
if (params[i].uuid) {
return -EINVAL;
}
/* Check if the supplied handle is invalid. */
if (!bt_gatt_attr_get_handle(params[i].attr)) {
return -EINVAL;
}
/* Check if the characteristic is subscribed. */
if (IS_ENABLED(CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION) &&
!bt_gatt_is_subscribed(conn, params[i].attr,
BT_GATT_CCC_NOTIFY)) {
LOG_WRN("Device is not subscribed to characteristic");
return -EINVAL;
}
}
/* PDU length is specified with a 16-bit value. */
if (*total_len > UINT16_MAX) {
return -ERANGE;
}
/* Check there is a bearer with a high enough MTU. */
if (bt_att_get_mtu(conn) <
(sizeof(struct bt_att_notify_mult) + *total_len)) {
return -ERANGE;
}
return 0;
}
int bt_gatt_notify_multiple(struct bt_conn *conn,
uint16_t num_params,
struct bt_gatt_notify_params params[])
{
int err;
size_t total_len = 0;
struct net_buf *buf;
/* Validate arguments, connection state and feature support. */
err = gatt_notify_multiple_verify_args(conn, params, num_params);
if (err) {
return err;
}
/* Validate all the attributes that we want to notify.
* Also gets us the total length of the PDU as a side-effect.
*/
err = gatt_notify_multiple_verify_params(conn, params, num_params, &total_len);
if (err) {
return err;
}
/* Send any outstanding notifications.
* Frees up buffer space for our PDU.
*/
gatt_notify_flush(conn);
/* Build the PDU */
buf = bt_att_create_pdu(conn, BT_ATT_OP_NOTIFY_MULT,
sizeof(struct bt_att_notify_mult) + total_len);
if (!buf) {
return -ENOMEM;
}
/* Register the callback. It will be called num_params times. */
bt_att_set_tx_meta_data(buf, params->func, params->user_data, BT_ATT_CHAN_OPT(params));
bt_att_increment_tx_meta_data_attr_count(buf, num_params - 1);
for (uint16_t i = 0; i < num_params; i++) {
struct notify_data data;
data.attr = params[i].attr;
data.handle = bt_gatt_attr_get_handle(data.attr);
/* Check if attribute is a characteristic then adjust the
* handle
*/
if (!bt_uuid_cmp(data.attr->uuid, BT_UUID_GATT_CHRC)) {
data.handle = bt_gatt_attr_value_handle(data.attr);
}
/* Add handle and data to the command buffer. */
gatt_add_nfy_to_buf(buf, data.handle, &params[i]);
}
/* Send the buffer. */
return gatt_notify_mult_send(conn, buf);
}
#endif /* CONFIG_BT_GATT_NOTIFY_MULTIPLE */
int bt_gatt_indicate(struct bt_conn *conn,
struct bt_gatt_indicate_params *params)
{
struct notify_data data;
__ASSERT(params, "invalid parameters\n");
__ASSERT(params->attr || params->uuid, "invalid parameters\n");
if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) {
return -EAGAIN;
}
if (conn && conn->state != BT_CONN_CONNECTED) {
return -ENOTCONN;
}
data.attr = params->attr;
data.handle = bt_gatt_attr_get_handle(data.attr);
/* Lookup UUID if it was given */
if (params->uuid) {
if (!gatt_find_by_uuid(&data, params->uuid)) {
return -ENOENT;
}
params->attr = data.attr;
} else {
if (!data.handle) {
return -ENOENT;
}
}
/* Check if attribute is a characteristic then adjust the handle */
if (!bt_uuid_cmp(data.attr->uuid, BT_UUID_GATT_CHRC)) {
struct bt_gatt_chrc *chrc = data.attr->user_data;
if (!(chrc->properties & BT_GATT_CHRC_INDICATE)) {
return -EINVAL;
}
data.handle = bt_gatt_attr_value_handle(data.attr);
}
if (conn) {
params->_ref = 1;
return gatt_indicate(conn, data.handle, params);
}
data.err = -ENOTCONN;
data.type = BT_GATT_CCC_INDICATE;
data.ind_params = params;
params->_ref = 0;
bt_gatt_foreach_attr_type(data.handle, 0xffff, BT_UUID_GATT_CCC, NULL,
1, notify_cb, &data);
return data.err;
}
uint16_t bt_gatt_get_mtu(struct bt_conn *conn)
{
return bt_att_get_mtu(conn);
}
uint8_t bt_gatt_check_perm(struct bt_conn *conn, const struct bt_gatt_attr *attr,
uint16_t mask)
{
if ((mask & BT_GATT_PERM_READ) &&
(!(attr->perm & BT_GATT_PERM_READ_MASK) || !attr->read)) {
return BT_ATT_ERR_READ_NOT_PERMITTED;
}
if ((mask & BT_GATT_PERM_WRITE) &&
(!(attr->perm & BT_GATT_PERM_WRITE_MASK) || !attr->write)) {
return BT_ATT_ERR_WRITE_NOT_PERMITTED;
}
if (IS_ENABLED(CONFIG_BT_CONN_DISABLE_SECURITY)) {
return 0;
}
mask &= attr->perm;
/*
* Core Specification 5.4 Vol. 3 Part C 10.3.1
*
* If neither an LTK nor an STK is available, the service
* request shall be rejected with the error code
* “Insufficient Authentication”.
* Note: When the link is not encrypted, the error code
* “Insufficient Authentication” does not indicate that
* MITM protection is required.
*
* If an LTK or an STK is available and encryption is
* required (LE security mode 1) but encryption is not
* enabled, the service request shall be rejected with
* the error code “Insufficient Encryption”.
*/
if (mask &
(BT_GATT_PERM_ENCRYPT_MASK | BT_GATT_PERM_AUTHEN_MASK | BT_GATT_PERM_LESC_MASK)) {
#if defined(CONFIG_BT_SMP)
if (!conn->encrypt) {
if (bt_conn_ltk_present(conn)) {
return BT_ATT_ERR_INSUFFICIENT_ENCRYPTION;
} else {
return BT_ATT_ERR_AUTHENTICATION;
}
}
if (mask & BT_GATT_PERM_AUTHEN_MASK) {
if (bt_conn_get_security(conn) < BT_SECURITY_L3) {
return BT_ATT_ERR_AUTHENTICATION;
}
}
if (mask & BT_GATT_PERM_LESC_MASK) {
const struct bt_keys *keys = conn->le.keys;
if (!keys || (keys->flags & BT_KEYS_SC) == 0) {
return BT_ATT_ERR_AUTHENTICATION;
}
}
#else
return BT_ATT_ERR_AUTHENTICATION;
#endif /* CONFIG_BT_SMP */
}
return 0;
}
static void sc_restore_rsp(struct bt_conn *conn,
struct bt_gatt_indicate_params *params, uint8_t err)
{
#if defined(CONFIG_BT_GATT_CACHING)
struct gatt_cf_cfg *cfg;
#endif
LOG_DBG("err 0x%02x", err);
#if defined(CONFIG_BT_GATT_CACHING)
/* BLUETOOTH CORE SPECIFICATION Version 5.3 | Vol 3, Part G page 1476:
* 2.5.2.1 Robust Caching
* ... a change-unaware connected client using exactly one ATT bearer
* becomes change-aware when ...
* The client receives and confirms a Handle Value Indication
* for the Service Changed characteristic
*/
if (bt_att_fixed_chan_only(conn)) {
cfg = find_cf_cfg(conn);
if (cfg && CF_ROBUST_CACHING(cfg)) {
set_change_aware(cfg, true);
}
}
#endif /* CONFIG_BT_GATT_CACHING */
if (!err && IS_ENABLED(CONFIG_BT_GATT_SERVICE_CHANGED)) {
struct gatt_sc_cfg *gsc_cfg = find_sc_cfg(conn->id, &conn->le.dst);
if (gsc_cfg) {
sc_reset(gsc_cfg);
}
}
}
static struct bt_gatt_indicate_params sc_restore_params[CONFIG_BT_MAX_CONN];
static uint16_t sc_range[CONFIG_BT_MAX_CONN][2];
static void sc_restore(struct bt_conn *conn)
{
struct gatt_sc_cfg *cfg;
uint8_t index;
cfg = find_sc_cfg(conn->id, &conn->le.dst);
if (!cfg) {
LOG_DBG("no SC data found");
return;
}
if (!(cfg->data.start || cfg->data.end)) {
return;
}
LOG_DBG("peer %s start 0x%04x end 0x%04x", bt_addr_le_str(&cfg->peer), cfg->data.start,
cfg->data.end);
index = bt_conn_index(conn);
sc_range[index][0] = sys_cpu_to_le16(cfg->data.start);
sc_range[index][1] = sys_cpu_to_le16(cfg->data.end);
sc_restore_params[index].attr = &_1_gatt_svc.attrs[2];
sc_restore_params[index].func = sc_restore_rsp;
sc_restore_params[index].data = &sc_range[index][0];
sc_restore_params[index].len = sizeof(sc_range[index]);
if (bt_gatt_indicate(conn, &sc_restore_params[index])) {
LOG_ERR("SC restore indication failed");
}
}
struct conn_data {
struct bt_conn *conn;
bt_security_t sec;
};
static uint8_t update_ccc(const struct bt_gatt_attr *attr, uint16_t handle,
void *user_data)
{
struct conn_data *data = user_data;
struct bt_conn *conn = data->conn;
struct _bt_gatt_ccc *ccc;
size_t i;
uint8_t err;
/* Check attribute user_data must be of type struct _bt_gatt_ccc */
if (attr->write != bt_gatt_attr_write_ccc) {
return BT_GATT_ITER_CONTINUE;
}
ccc = attr->user_data;
for (i = 0; i < ARRAY_SIZE(ccc->cfg); i++) {
struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i];
/* Ignore configuration for different peer or not active */
if (!cfg->value ||
!bt_conn_is_peer_addr_le(conn, cfg->id, &cfg->peer)) {
continue;
}
/* Check if attribute requires encryption/authentication */
err = bt_gatt_check_perm(conn, attr, BT_GATT_PERM_WRITE_MASK);
if (err) {
bt_security_t sec;
if (err == BT_ATT_ERR_WRITE_NOT_PERMITTED) {
LOG_WRN("CCC %p not writable", attr);
continue;
}
sec = BT_SECURITY_L2;
if (err == BT_ATT_ERR_AUTHENTICATION) {
sec = BT_SECURITY_L3;
}
/* Check if current security is enough */
if (IS_ENABLED(CONFIG_BT_SMP) &&
bt_conn_get_security(conn) < sec) {
if (data->sec < sec) {
data->sec = sec;
}
continue;
}
}
gatt_ccc_changed(attr, ccc);
if (IS_ENABLED(CONFIG_BT_GATT_SERVICE_CHANGED) &&
ccc == &sc_ccc) {
sc_restore(conn);
}
return BT_GATT_ITER_CONTINUE;
}
return BT_GATT_ITER_CONTINUE;
}
static uint8_t disconnected_cb(const struct bt_gatt_attr *attr, uint16_t handle,
void *user_data)
{
struct bt_conn *conn = user_data;
struct _bt_gatt_ccc *ccc;
bool value_used;
size_t i;
/* Check attribute user_data must be of type struct _bt_gatt_ccc */
if (attr->write != bt_gatt_attr_write_ccc) {
return BT_GATT_ITER_CONTINUE;
}
ccc = attr->user_data;
/* If already disabled skip */
if (!ccc->value) {
return BT_GATT_ITER_CONTINUE;
}
/* Checking if all values are disabled */
value_used = false;
for (i = 0; i < ARRAY_SIZE(ccc->cfg); i++) {
struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i];
/* Ignore configurations with disabled value */
if (!cfg->value) {
continue;
}
if (!bt_conn_is_peer_addr_le(conn, cfg->id, &cfg->peer)) {
struct bt_conn *tmp;
/* Skip if there is another peer connected */
tmp = bt_conn_lookup_addr_le(cfg->id, &cfg->peer);
if (tmp) {
if (tmp->state == BT_CONN_CONNECTED) {
value_used = true;
}
bt_conn_unref(tmp);
}
} else {
/* Clear value if not paired */
if (!bt_addr_le_is_bonded(conn->id, &conn->le.dst)) {
if (ccc == &sc_ccc) {
sc_clear(conn);
}
clear_ccc_cfg(cfg);
} else {
/* Update address in case it has changed */
bt_addr_le_copy(&cfg->peer, &conn->le.dst);
}
}
}
/* If all values are now disabled, reset value while disconnected */
if (!value_used) {
ccc->value = 0U;
if (ccc->cfg_changed) {
ccc->cfg_changed(attr, ccc->value);
}
LOG_DBG("ccc %p reseted", ccc);
}
return BT_GATT_ITER_CONTINUE;
}
bool bt_gatt_is_subscribed(struct bt_conn *conn,
const struct bt_gatt_attr *attr, uint16_t ccc_type)
{
const struct _bt_gatt_ccc *ccc;
__ASSERT(conn, "invalid parameter\n");
__ASSERT(attr, "invalid parameter\n");
if (conn->state != BT_CONN_CONNECTED) {
return false;
}
/* Check if attribute is a characteristic declaration */
if (!bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CHRC)) {
struct bt_gatt_chrc *chrc = attr->user_data;
if (!(chrc->properties &
(BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE))) {
/* Characteristic doesn't support subscription */
return false;
}
attr = bt_gatt_attr_next(attr);
__ASSERT(attr, "No more attributes\n");
}
/* Check if attribute is a characteristic value */
if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CCC) != 0) {
attr = bt_gatt_attr_next(attr);
__ASSERT(attr, "No more attributes\n");
}
/* Find the CCC Descriptor */
while (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CCC) &&
/* Also stop if we leave the current characteristic definition */
bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CHRC) &&
bt_uuid_cmp(attr->uuid, BT_UUID_GATT_PRIMARY) &&
bt_uuid_cmp(attr->uuid, BT_UUID_GATT_SECONDARY)) {
attr = bt_gatt_attr_next(attr);
if (!attr) {
return false;
}
}
if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CCC) != 0) {
return false;
}
ccc = attr->user_data;
/* Check if the connection is subscribed */
for (size_t i = 0; i < BT_GATT_CCC_MAX; i++) {
const struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i];
if (bt_conn_is_peer_addr_le(conn, cfg->id, &cfg->peer) &&
(ccc_type & ccc->cfg[i].value)) {
return true;
}
}
return false;
}
static bool gatt_sub_is_empty(struct gatt_sub *sub)
{
return sys_slist_is_empty(&sub->list);
}
/** @brief Free sub for reuse.
*/
static void gatt_sub_free(struct gatt_sub *sub)
{
__ASSERT_NO_MSG(gatt_sub_is_empty(sub));
bt_addr_le_copy(&sub->peer, BT_ADDR_LE_ANY);
}
static void gatt_sub_remove(struct bt_conn *conn, struct gatt_sub *sub,
sys_snode_t *prev,
struct bt_gatt_subscribe_params *params)
{
if (params) {
/* Remove subscription from the list*/
sys_slist_remove(&sub->list, prev, &params->node);
/* Notify removal */
params->notify(conn, params, NULL, 0);
}
if (gatt_sub_is_empty(sub)) {
gatt_sub_free(sub);
}
}
#if defined(CONFIG_BT_GATT_CLIENT)
static struct gatt_sub *gatt_sub_find(struct bt_conn *conn)
{
for (int i = 0; i < ARRAY_SIZE(subscriptions); i++) {
struct gatt_sub *sub = &subscriptions[i];
if (!conn) {
if (bt_addr_le_eq(&sub->peer, BT_ADDR_LE_ANY)) {
return sub;
}
} else if (bt_conn_is_peer_addr_le(conn, sub->id, &sub->peer)) {
return sub;
}
}
return NULL;
}
static struct gatt_sub *gatt_sub_add(struct bt_conn *conn)
{
struct gatt_sub *sub;
sub = gatt_sub_find(conn);
if (!sub) {
sub = gatt_sub_find(NULL);
if (sub) {
bt_addr_le_copy(&sub->peer, &conn->le.dst);
sub->id = conn->id;
}
}
return sub;
}
static struct gatt_sub *gatt_sub_find_by_addr(uint8_t id,
const bt_addr_le_t *addr)
{
for (int i = 0; i < ARRAY_SIZE(subscriptions); i++) {
struct gatt_sub *sub = &subscriptions[i];
if (id == sub->id && bt_addr_le_eq(&sub->peer, addr)) {
return sub;
}
}
return NULL;
}
static struct gatt_sub *gatt_sub_add_by_addr(uint8_t id,
const bt_addr_le_t *addr)
{
struct gatt_sub *sub;
sub = gatt_sub_find_by_addr(id, addr);
if (!sub) {
sub = gatt_sub_find(NULL);
if (sub) {
bt_addr_le_copy(&sub->peer, addr);
sub->id = id;
}
}
return sub;
}
static bool check_subscribe_security_level(struct bt_conn *conn,
const struct bt_gatt_subscribe_params *params)
{
#if defined(CONFIG_BT_SMP)
return conn->sec_level >= params->min_security;
#endif
return true;
}
void bt_gatt_notification(struct bt_conn *conn, uint16_t handle,
const void *data, uint16_t length)
{
struct bt_gatt_subscribe_params *params, *tmp;
struct gatt_sub *sub;
LOG_DBG("handle 0x%04x length %u", handle, length);
sub = gatt_sub_find(conn);
if (!sub) {
return;
}
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&sub->list, params, tmp, node) {
if (handle != params->value_handle) {
continue;
}
if (check_subscribe_security_level(conn, params)) {
if (params->notify(conn, params, data, length) ==
BT_GATT_ITER_STOP) {
bt_gatt_unsubscribe(conn, params);
}
}
}
}
void bt_gatt_mult_notification(struct bt_conn *conn, const void *data,
uint16_t length)
{
struct bt_gatt_subscribe_params *params, *tmp;
const struct bt_att_notify_mult *nfy;
struct net_buf_simple buf;
struct gatt_sub *sub;
LOG_DBG("length %u", length);
sub = gatt_sub_find(conn);
if (!sub) {
return;
}
/* This is fine since there no write operation to the buffer. */
net_buf_simple_init_with_data(&buf, (void *)data, length);
while (buf.len > sizeof(*nfy)) {
uint16_t handle;
uint16_t len;
nfy = net_buf_simple_pull_mem(&buf, sizeof(*nfy));
handle = sys_cpu_to_le16(nfy->handle);
len = sys_cpu_to_le16(nfy->len);
LOG_DBG("handle 0x%02x len %u", handle, len);
if (len > buf.len) {
LOG_ERR("Invalid data len %u > %u", len, length);
return;
}
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&sub->list, params, tmp,
node) {
if (handle != params->value_handle) {
continue;
}
if (check_subscribe_security_level(conn, params)) {
if (params->notify(conn, params, nfy->value, len) ==
BT_GATT_ITER_STOP) {
bt_gatt_unsubscribe(conn, params);
}
}
}
net_buf_simple_pull_mem(&buf, len);
}
}
static void gatt_sub_update(struct bt_conn *conn, struct gatt_sub *sub)
{
if (sub->peer.type == BT_ADDR_LE_PUBLIC) {
return;
}
/* Update address */
bt_addr_le_copy(&sub->peer, &conn->le.dst);
}
static void remove_subscriptions(struct bt_conn *conn)
{
struct gatt_sub *sub;
struct bt_gatt_subscribe_params *params, *tmp;
sys_snode_t *prev = NULL;
sub = gatt_sub_find(conn);
if (!sub) {
return;
}
/* Lookup existing subscriptions */
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&sub->list, params, tmp, node) {
atomic_clear_bit(params->flags, BT_GATT_SUBSCRIBE_FLAG_SENT);
if (!bt_addr_le_is_bonded(conn->id, &conn->le.dst) ||
(atomic_test_bit(params->flags,
BT_GATT_SUBSCRIBE_FLAG_VOLATILE))) {
/* Remove subscription */
params->value = 0U;
gatt_sub_remove(conn, sub, prev, params);
} else {
gatt_sub_update(conn, sub);
prev = &params->node;
}
}
}
static void gatt_mtu_rsp(struct bt_conn *conn, int err, const void *pdu,
uint16_t length, void *user_data)
{
struct bt_gatt_exchange_params *params = user_data;
params->func(conn, att_err_from_int(err), params);
}
static int gatt_exchange_mtu_encode(struct net_buf *buf, size_t len,
void *user_data)
{
struct bt_att_exchange_mtu_req *req;
uint16_t mtu;
mtu = BT_LOCAL_ATT_MTU_UATT;
LOG_DBG("Client MTU %u", mtu);
req = net_buf_add(buf, sizeof(*req));
req->mtu = sys_cpu_to_le16(mtu);
return 0;
}
int bt_gatt_exchange_mtu(struct bt_conn *conn,
struct bt_gatt_exchange_params *params)
{
int err;
__ASSERT(conn, "invalid parameter\n");
__ASSERT(params && params->func, "invalid parameters\n");
if (conn->state != BT_CONN_CONNECTED) {
return -ENOTCONN;
}
/* This request shall only be sent once during a connection by the client. */
if (atomic_test_and_set_bit(conn->flags, BT_CONN_ATT_MTU_EXCHANGED)) {
return -EALREADY;
}
err = gatt_req_send(conn, gatt_mtu_rsp, params,
gatt_exchange_mtu_encode, BT_ATT_OP_MTU_REQ,
sizeof(struct bt_att_exchange_mtu_req),
BT_ATT_CHAN_OPT_UNENHANCED_ONLY);
if (err) {
atomic_clear_bit(conn->flags, BT_CONN_ATT_MTU_EXCHANGED);
}
return err;
}
static void gatt_discover_next(struct bt_conn *conn, uint16_t last_handle,
struct bt_gatt_discover_params *params)
{
/* Skip if last_handle is not set */
if (!last_handle)
goto discover;
/* Continue from the last found handle */
params->start_handle = last_handle;
if (params->start_handle < UINT16_MAX) {
params->start_handle++;
} else {
goto done;
}
/* Stop if over the range or the requests */
if (params->start_handle > params->end_handle) {
goto done;
}
discover:
/* Discover next range */
if (!bt_gatt_discover(conn, params)) {
return;
}
done:
params->func(conn, NULL, params);
}
static void gatt_find_type_rsp(struct bt_conn *conn, int err,
const void *pdu, uint16_t length,
void *user_data)
{
const struct bt_att_handle_group *rsp = pdu;
struct bt_gatt_discover_params *params = user_data;
uint8_t count;
uint16_t end_handle = 0U, start_handle;
LOG_DBG("err %d", err);
if (err || (length % sizeof(struct bt_att_handle_group) != 0)) {
goto done;
}
count = length / sizeof(struct bt_att_handle_group);
/* Parse attributes found */
for (uint8_t i = 0U; i < count; i++) {
struct bt_uuid_16 uuid_svc;
struct bt_gatt_attr attr;
struct bt_gatt_service_val value;
start_handle = sys_le16_to_cpu(rsp[i].start_handle);
end_handle = sys_le16_to_cpu(rsp[i].end_handle);
LOG_DBG("start_handle 0x%04x end_handle 0x%04x", start_handle, end_handle);
uuid_svc.uuid.type = BT_UUID_TYPE_16;
if (params->type == BT_GATT_DISCOVER_PRIMARY) {
uuid_svc.val = BT_UUID_GATT_PRIMARY_VAL;
} else {
uuid_svc.val = BT_UUID_GATT_SECONDARY_VAL;
}
value.end_handle = end_handle;
value.uuid = params->uuid;
attr = (struct bt_gatt_attr) {
.uuid = &uuid_svc.uuid,
.user_data = &value,
.handle = start_handle,
};
if (params->func(conn, &attr, params) == BT_GATT_ITER_STOP) {
return;
}
}
gatt_discover_next(conn, end_handle, params);
return;
done:
params->func(conn, NULL, params);
}
static int gatt_find_type_encode(struct net_buf *buf, size_t len,
void *user_data)
{
struct bt_gatt_discover_params *params = user_data;
struct bt_att_find_type_req *req;
uint16_t uuid_val;
req = net_buf_add(buf, sizeof(*req));
req->start_handle = sys_cpu_to_le16(params->start_handle);
req->end_handle = sys_cpu_to_le16(params->end_handle);
if (params->type == BT_GATT_DISCOVER_PRIMARY) {
uuid_val = BT_UUID_GATT_PRIMARY_VAL;
} else {
uuid_val = BT_UUID_GATT_SECONDARY_VAL;
}
req->type = sys_cpu_to_le16(uuid_val);
LOG_DBG("uuid %s start_handle 0x%04x end_handle 0x%04x", bt_uuid_str(params->uuid),
params->start_handle, params->end_handle);
switch (params->uuid->type) {
case BT_UUID_TYPE_16:
net_buf_add_le16(buf, BT_UUID_16(params->uuid)->val);
break;
case BT_UUID_TYPE_128:
net_buf_add_mem(buf, BT_UUID_128(params->uuid)->val, 16);
break;
}
return 0;
}
static int gatt_find_type(struct bt_conn *conn,
struct bt_gatt_discover_params *params)
{
size_t len;
len = sizeof(struct bt_att_find_type_req);
switch (params->uuid->type) {
case BT_UUID_TYPE_16:
len += BT_UUID_SIZE_16;
break;
case BT_UUID_TYPE_128:
len += BT_UUID_SIZE_128;
break;
default:
LOG_ERR("Unknown UUID type %u", params->uuid->type);
return -EINVAL;
}
return gatt_req_send(conn, gatt_find_type_rsp, params,
gatt_find_type_encode, BT_ATT_OP_FIND_TYPE_REQ,
len, BT_ATT_CHAN_OPT(params));
}
static void read_included_uuid_cb(struct bt_conn *conn, int err,
const void *pdu, uint16_t length,
void *user_data)
{
struct bt_gatt_discover_params *params = user_data;
struct bt_gatt_include value;
struct bt_gatt_attr attr;
uint16_t handle;
union {
struct bt_uuid uuid;
struct bt_uuid_128 u128;
} u;
if (length != 16U) {
LOG_ERR("Invalid data len %u", length);
params->func(conn, NULL, params);
return;
}
handle = params->_included.attr_handle;
value.start_handle = params->_included.start_handle;
value.end_handle = params->_included.end_handle;
value.uuid = &u.uuid;
u.uuid.type = BT_UUID_TYPE_128;
memcpy(u.u128.val, pdu, length);
LOG_DBG("handle 0x%04x uuid %s start_handle 0x%04x "
"end_handle 0x%04x\n", params->_included.attr_handle,
bt_uuid_str(&u.uuid), value.start_handle, value.end_handle);
/* Skip if UUID is set but doesn't match */
if (params->uuid && bt_uuid_cmp(&u.uuid, params->uuid)) {
goto next;
}
attr = (struct bt_gatt_attr) {
.uuid = BT_UUID_GATT_INCLUDE,
.user_data = &value,
.handle = handle,
};
if (params->func(conn, &attr, params) == BT_GATT_ITER_STOP) {
return;
}
next:
gatt_discover_next(conn, params->start_handle, params);
return;
}
static int read_included_uuid_encode(struct net_buf *buf, size_t len,
void *user_data)
{
struct bt_gatt_discover_params *params = user_data;
struct bt_att_read_req *req;
req = net_buf_add(buf, sizeof(*req));
req->handle = sys_cpu_to_le16(params->_included.start_handle);
return 0;
}
static int read_included_uuid(struct bt_conn *conn,
struct bt_gatt_discover_params *params)
{
LOG_DBG("handle 0x%04x", params->_included.start_handle);
return gatt_req_send(conn, read_included_uuid_cb, params,
read_included_uuid_encode, BT_ATT_OP_READ_REQ,
sizeof(struct bt_att_read_req), BT_ATT_CHAN_OPT(params));
}
static uint16_t parse_include(struct bt_conn *conn, const void *pdu,
struct bt_gatt_discover_params *params,
uint16_t length)
{
const struct bt_att_read_type_rsp *rsp;
uint16_t handle = 0U;
struct bt_gatt_include value;
union {
struct bt_uuid uuid;
struct bt_uuid_16 u16;
struct bt_uuid_128 u128;
} u;
if (length < sizeof(*rsp)) {
LOG_WRN("Parse err");
goto done;
}
rsp = pdu;
/* Data can be either in UUID16 or UUID128 */
switch (rsp->len) {
case 8: /* UUID16 */
u.uuid.type = BT_UUID_TYPE_16;
break;
case 6: /* UUID128 */
/* BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part G] page 550
* To get the included service UUID when the included service
* uses a 128-bit UUID, the Read Request is used.
*/
u.uuid.type = BT_UUID_TYPE_128;
break;
default:
LOG_ERR("Invalid data len %u", rsp->len);
goto done;
}
/* Parse include found */
for (length--, pdu = rsp->data; length >= rsp->len;
length -= rsp->len, pdu = (const uint8_t *)pdu + rsp->len) {
struct bt_gatt_attr attr;
const struct bt_att_data *data = pdu;
struct gatt_incl *incl = (void *)data->value;
handle = sys_le16_to_cpu(data->handle);
/* Handle 0 is invalid */
if (!handle) {
goto done;
}
/* Convert include data, bt_gatt_incl and gatt_incl
* have different formats so the conversion have to be done
* field by field.
*/
value.start_handle = sys_le16_to_cpu(incl->start_handle);
value.end_handle = sys_le16_to_cpu(incl->end_handle);
switch (u.uuid.type) {
case BT_UUID_TYPE_16:
value.uuid = &u.uuid;
u.u16.val = sys_le16_to_cpu(incl->uuid16);
break;
case BT_UUID_TYPE_128:
params->_included.attr_handle = handle;
params->_included.start_handle = value.start_handle;
params->_included.end_handle = value.end_handle;
return read_included_uuid(conn, params);
}
LOG_DBG("handle 0x%04x uuid %s start_handle 0x%04x "
"end_handle 0x%04x\n", handle, bt_uuid_str(&u.uuid),
value.start_handle, value.end_handle);
/* Skip if UUID is set but doesn't match */
if (params->uuid && bt_uuid_cmp(&u.uuid, params->uuid)) {
continue;
}
attr = (struct bt_gatt_attr) {
.uuid = BT_UUID_GATT_INCLUDE,
.user_data = &value,
.handle = handle,
};
if (params->func(conn, &attr, params) == BT_GATT_ITER_STOP) {
return 0;
}
}
/* Whole PDU read without error */
if (length == 0U && handle) {
return handle;
}
done:
params->func(conn, NULL, params);
return 0;
}
static uint16_t parse_characteristic(struct bt_conn *conn, const void *pdu,
struct bt_gatt_discover_params *params,
uint16_t length)
{
const struct bt_att_read_type_rsp *rsp;
uint16_t handle = 0U;
union {
struct bt_uuid uuid;
struct bt_uuid_16 u16;
struct bt_uuid_128 u128;
} u;
if (length < sizeof(*rsp)) {
LOG_WRN("Parse err");
goto done;
}
rsp = pdu;
/* Data can be either in UUID16 or UUID128 */
switch (rsp->len) {
case 7: /* UUID16 */
u.uuid.type = BT_UUID_TYPE_16;
break;
case 21: /* UUID128 */
u.uuid.type = BT_UUID_TYPE_128;
break;
default:
LOG_ERR("Invalid data len %u", rsp->len);
goto done;
}
/* Parse characteristics found */
for (length--, pdu = rsp->data; length >= rsp->len;
length -= rsp->len, pdu = (const uint8_t *)pdu + rsp->len) {
struct bt_gatt_attr attr;
struct bt_gatt_chrc value;
const struct bt_att_data *data = pdu;
struct gatt_chrc *chrc = (void *)data->value;
handle = sys_le16_to_cpu(data->handle);
/* Handle 0 is invalid */
if (!handle) {
goto done;
}
switch (u.uuid.type) {
case BT_UUID_TYPE_16:
u.u16.val = sys_le16_to_cpu(chrc->uuid16);
break;
case BT_UUID_TYPE_128:
memcpy(u.u128.val, chrc->uuid, sizeof(chrc->uuid));
break;
}
LOG_DBG("handle 0x%04x uuid %s properties 0x%02x", handle, bt_uuid_str(&u.uuid),
chrc->properties);
/* Skip if UUID is set but doesn't match */
if (params->uuid && bt_uuid_cmp(&u.uuid, params->uuid)) {
continue;
}
value = (struct bt_gatt_chrc)BT_GATT_CHRC_INIT(
&u.uuid, sys_le16_to_cpu(chrc->value_handle),
chrc->properties);
attr = (struct bt_gatt_attr) {
.uuid = BT_UUID_GATT_CHRC,
.user_data = &value,
.handle = handle,
};
if (params->func(conn, &attr, params) == BT_GATT_ITER_STOP) {
return 0;
}
}
/* Whole PDU read without error */
if (length == 0U && handle) {
return handle;
}
done:
params->func(conn, NULL, params);
return 0;
}
static uint16_t parse_read_std_char_desc(struct bt_conn *conn, const void *pdu,
struct bt_gatt_discover_params *params,
uint16_t length)
{
const struct bt_att_read_type_rsp *rsp;
uint16_t handle = 0U;
uint16_t uuid_val;
if (params->uuid->type != BT_UUID_TYPE_16) {
goto done;
}
uuid_val = BT_UUID_16(params->uuid)->val;
if (length < sizeof(*rsp)) {
LOG_WRN("Parse err");
goto done;
}
rsp = pdu;
/* Parse characteristics found */
for (length--, pdu = rsp->data; length >= rsp->len;
length -= rsp->len, pdu = (const uint8_t *)pdu + rsp->len) {
union {
struct bt_gatt_ccc ccc;
struct bt_gatt_cpf cpf;
struct bt_gatt_cep cep;
struct bt_gatt_scc scc;
} value;
const struct bt_att_data *data;
struct bt_gatt_attr attr;
if (length < sizeof(*data)) {
LOG_WRN("Parse err dat");
goto done;
}
data = pdu;
handle = sys_le16_to_cpu(data->handle);
/* Handle 0 is invalid */
if (!handle) {
goto done;
}
switch (uuid_val) {
case BT_UUID_GATT_CEP_VAL:
if (length < sizeof(*data) + sizeof(uint16_t)) {
LOG_WRN("Parse err cep");
goto done;
}
value.cep.properties = sys_get_le16(data->value);
break;
case BT_UUID_GATT_CCC_VAL:
if (length < sizeof(*data) + sizeof(uint16_t)) {
LOG_WRN("Parse err ccc");
goto done;
}
value.ccc.flags = sys_get_le16(data->value);
break;
case BT_UUID_GATT_SCC_VAL:
if (length < sizeof(*data) + sizeof(uint16_t)) {
LOG_WRN("Parse err scc");
goto done;
}
value.scc.flags = sys_get_le16(data->value);
break;
case BT_UUID_GATT_CPF_VAL:
{
struct gatt_cpf *cpf;
if (length < sizeof(*data) + sizeof(*cpf)) {
LOG_WRN("Parse err cpf");
goto done;
}
cpf = (void *)data->value;
value.cpf.format = cpf->format;
value.cpf.exponent = cpf->exponent;
value.cpf.unit = sys_le16_to_cpu(cpf->unit);
value.cpf.name_space = cpf->name_space;
value.cpf.description = sys_le16_to_cpu(cpf->description);
break;
}
default:
goto done;
}
attr = (struct bt_gatt_attr) {
.uuid = params->uuid,
.user_data = &value,
.handle = handle,
};
if (params->func(conn, &attr, params) == BT_GATT_ITER_STOP) {
return 0;
}
}
/* Whole PDU read without error */
if (length == 0U && handle) {
return handle;
}
done:
params->func(conn, NULL, params);
return 0;
}
static void gatt_read_type_rsp(struct bt_conn *conn, int err,
const void *pdu, uint16_t length,
void *user_data)
{
struct bt_gatt_discover_params *params = user_data;
uint16_t handle;
LOG_DBG("err %d", err);
if (err) {
params->func(conn, NULL, params);
return;
}
if (params->type == BT_GATT_DISCOVER_INCLUDE) {
handle = parse_include(conn, pdu, params, length);
} else if (params->type == BT_GATT_DISCOVER_CHARACTERISTIC) {
handle = parse_characteristic(conn, pdu, params, length);
} else {
handle = parse_read_std_char_desc(conn, pdu, params, length);
}
if (!handle) {
return;
}
gatt_discover_next(conn, handle, params);
}
static int gatt_read_type_encode(struct net_buf *buf, size_t len,
void *user_data)
{
struct bt_gatt_discover_params *params = user_data;
struct bt_att_read_type_req *req;
req = net_buf_add(buf, sizeof(*req));
req->start_handle = sys_cpu_to_le16(params->start_handle);
req->end_handle = sys_cpu_to_le16(params->end_handle);
switch (params->type) {
case BT_GATT_DISCOVER_INCLUDE:
net_buf_add_le16(buf, BT_UUID_GATT_INCLUDE_VAL);
break;
case BT_GATT_DISCOVER_CHARACTERISTIC:
net_buf_add_le16(buf, BT_UUID_GATT_CHRC_VAL);
break;
default:
/* Only 16-bit UUIDs supported */
net_buf_add_le16(buf, BT_UUID_16(params->uuid)->val);
break;
}
return 0;
}
static int gatt_read_type(struct bt_conn *conn,
struct bt_gatt_discover_params *params)
{
LOG_DBG("start_handle 0x%04x end_handle 0x%04x", params->start_handle, params->end_handle);
return gatt_req_send(conn, gatt_read_type_rsp, params,
gatt_read_type_encode, BT_ATT_OP_READ_TYPE_REQ,
sizeof(struct bt_att_read_type_req), BT_ATT_CHAN_OPT(params));
}
static uint16_t parse_service(struct bt_conn *conn, const void *pdu,
struct bt_gatt_discover_params *params,
uint16_t length)
{
const struct bt_att_read_group_rsp *rsp;
uint16_t start_handle, end_handle = 0U;
union {
struct bt_uuid uuid;
struct bt_uuid_16 u16;
struct bt_uuid_128 u128;
} u;
if (length < sizeof(*rsp)) {
LOG_WRN("Parse err");
goto done;
}
rsp = pdu;
/* Data can be either in UUID16 or UUID128 */
switch (rsp->len) {
case 6: /* UUID16 */
u.uuid.type = BT_UUID_TYPE_16;
break;
case 20: /* UUID128 */
u.uuid.type = BT_UUID_TYPE_128;
break;
default:
LOG_ERR("Invalid data len %u", rsp->len);
goto done;
}
/* Parse services found */
for (length--, pdu = rsp->data; length >= rsp->len;
length -= rsp->len, pdu = (const uint8_t *)pdu + rsp->len) {
struct bt_uuid_16 uuid_svc;
struct bt_gatt_attr attr = {};
struct bt_gatt_service_val value;
const struct bt_att_group_data *data = pdu;
start_handle = sys_le16_to_cpu(data->start_handle);
if (!start_handle) {
goto done;
}
end_handle = sys_le16_to_cpu(data->end_handle);
if (!end_handle || end_handle < start_handle) {
goto done;
}
switch (u.uuid.type) {
case BT_UUID_TYPE_16:
memcpy(&u.u16.val, data->value, sizeof(u.u16.val));
u.u16.val = sys_le16_to_cpu(u.u16.val);
break;
case BT_UUID_TYPE_128:
memcpy(u.u128.val, data->value, sizeof(u.u128.val));
break;
}
LOG_DBG("start_handle 0x%04x end_handle 0x%04x uuid %s", start_handle, end_handle,
bt_uuid_str(&u.uuid));
uuid_svc.uuid.type = BT_UUID_TYPE_16;
if (params->type == BT_GATT_DISCOVER_PRIMARY) {
uuid_svc.val = BT_UUID_GATT_PRIMARY_VAL;
} else {
uuid_svc.val = BT_UUID_GATT_SECONDARY_VAL;
}
value.end_handle = end_handle;
value.uuid = &u.uuid;
attr.uuid = &uuid_svc.uuid;
attr.handle = start_handle;
attr.user_data = &value;
if (params->func(conn, &attr, params) == BT_GATT_ITER_STOP) {
return 0;
}
}
/* Whole PDU read without error */
if (length == 0U && end_handle) {
return end_handle;
}
done:
params->func(conn, NULL, params);
return 0;
}
static void gatt_read_group_rsp(struct bt_conn *conn, int err,
const void *pdu, uint16_t length,
void *user_data)
{
struct bt_gatt_discover_params *params = user_data;
uint16_t handle;
LOG_DBG("err %d", err);
if (err) {
params->func(conn, NULL, params);
return;
}
handle = parse_service(conn, pdu, params, length);
if (!handle) {
return;
}
gatt_discover_next(conn, handle, params);
}
static int gatt_read_group_encode(struct net_buf *buf, size_t len,
void *user_data)
{
struct bt_gatt_discover_params *params = user_data;
struct bt_att_read_group_req *req;
req = net_buf_add(buf, sizeof(*req));
req->start_handle = sys_cpu_to_le16(params->start_handle);
req->end_handle = sys_cpu_to_le16(params->end_handle);
if (params->type == BT_GATT_DISCOVER_PRIMARY) {
net_buf_add_le16(buf, BT_UUID_GATT_PRIMARY_VAL);
} else {
net_buf_add_le16(buf, BT_UUID_GATT_SECONDARY_VAL);
}
return 0;
}
static int gatt_read_group(struct bt_conn *conn,
struct bt_gatt_discover_params *params)
{
LOG_DBG("start_handle 0x%04x end_handle 0x%04x", params->start_handle, params->end_handle);
return gatt_req_send(conn, gatt_read_group_rsp, params,
gatt_read_group_encode,
BT_ATT_OP_READ_GROUP_REQ,
sizeof(struct bt_att_read_group_req),
BT_ATT_CHAN_OPT(params));
}
static void gatt_find_info_rsp(struct bt_conn *conn, int err,
const void *pdu, uint16_t length,
void *user_data)
{
const struct bt_att_find_info_rsp *rsp;
struct bt_gatt_discover_params *params = user_data;
uint16_t handle = 0U;
uint16_t len;
union {
const struct bt_att_info_16 *i16;
const struct bt_att_info_128 *i128;
} info;
union {
struct bt_uuid uuid;
struct bt_uuid_16 u16;
struct bt_uuid_128 u128;
} u;
int i;
bool skip = false;
LOG_DBG("err %d", err);
if (err) {
goto done;
}
if (length < sizeof(*rsp)) {
LOG_WRN("Parse err");
goto done;
}
rsp = pdu;
/* Data can be either in UUID16 or UUID128 */
switch (rsp->format) {
case BT_ATT_INFO_16:
u.uuid.type = BT_UUID_TYPE_16;
len = sizeof(*info.i16);
break;
case BT_ATT_INFO_128:
u.uuid.type = BT_UUID_TYPE_128;
len = sizeof(*info.i128);
break;
default:
LOG_ERR("Invalid format %u", rsp->format);
goto done;
}
length--;
/* Check if there is a least one descriptor in the response */
if (length < len) {
goto done;
}
/* Parse descriptors found */
for (i = length / len, pdu = rsp->info; i != 0;
i--, pdu = (const uint8_t *)pdu + len) {
struct bt_gatt_attr attr;
info.i16 = pdu;
handle = sys_le16_to_cpu(info.i16->handle);
if (skip) {
skip = false;
continue;
}
switch (u.uuid.type) {
case BT_UUID_TYPE_16:
u.u16.val = sys_le16_to_cpu(info.i16->uuid);
break;
case BT_UUID_TYPE_128:
memcpy(u.u128.val, info.i128->uuid, 16);
break;
}
LOG_DBG("handle 0x%04x uuid %s", handle, bt_uuid_str(&u.uuid));
/* Skip if UUID is set but doesn't match */
if (params->uuid && bt_uuid_cmp(&u.uuid, params->uuid)) {
continue;
}
if (params->type == BT_GATT_DISCOVER_DESCRIPTOR) {
/* Skip attributes that are not considered
* descriptors.
*/
if (!bt_uuid_cmp(&u.uuid, BT_UUID_GATT_PRIMARY) ||
!bt_uuid_cmp(&u.uuid, BT_UUID_GATT_SECONDARY) ||
!bt_uuid_cmp(&u.uuid, BT_UUID_GATT_INCLUDE)) {
continue;
}
/* If Characteristic Declaration skip ahead as the next
* entry must be its value.
*/
if (!bt_uuid_cmp(&u.uuid, BT_UUID_GATT_CHRC)) {
skip = true;
continue;
}
}
/* No user_data in this case */
attr = (struct bt_gatt_attr) {
.uuid = &u.uuid,
.handle = handle,
};
if (params->func(conn, &attr, params) == BT_GATT_ITER_STOP) {
return;
}
}
gatt_discover_next(conn, handle, params);
return;
done:
params->func(conn, NULL, params);
}
static int gatt_find_info_encode(struct net_buf *buf, size_t len,
void *user_data)
{
struct bt_gatt_discover_params *params = user_data;
struct bt_att_find_info_req *req;
req = net_buf_add(buf, sizeof(*req));
req->start_handle = sys_cpu_to_le16(params->start_handle);
req->end_handle = sys_cpu_to_le16(params->end_handle);
return 0;
}
static int gatt_find_info(struct bt_conn *conn,
struct bt_gatt_discover_params *params)
{
LOG_DBG("start_handle 0x%04x end_handle 0x%04x", params->start_handle, params->end_handle);
return gatt_req_send(conn, gatt_find_info_rsp, params,
gatt_find_info_encode, BT_ATT_OP_FIND_INFO_REQ,
sizeof(struct bt_att_find_info_req),
BT_ATT_CHAN_OPT(params));
}
int bt_gatt_discover(struct bt_conn *conn,
struct bt_gatt_discover_params *params)
{
__ASSERT(conn, "invalid parameters\n");
__ASSERT(params && params->func, "invalid parameters\n");
__ASSERT((params->start_handle && params->end_handle),
"invalid parameters\n");
__ASSERT((params->start_handle <= params->end_handle),
"invalid parameters\n");
if (conn->state != BT_CONN_CONNECTED) {
return -ENOTCONN;
}
switch (params->type) {
case BT_GATT_DISCOVER_PRIMARY:
case BT_GATT_DISCOVER_SECONDARY:
if (params->uuid) {
return gatt_find_type(conn, params);
}
return gatt_read_group(conn, params);
case BT_GATT_DISCOVER_STD_CHAR_DESC:
if (!(params->uuid && params->uuid->type == BT_UUID_TYPE_16 &&
(!bt_uuid_cmp(params->uuid, BT_UUID_GATT_CEP) ||
!bt_uuid_cmp(params->uuid, BT_UUID_GATT_CCC) ||
!bt_uuid_cmp(params->uuid, BT_UUID_GATT_SCC) ||
!bt_uuid_cmp(params->uuid, BT_UUID_GATT_CPF)))) {
return -EINVAL;
}
__fallthrough;
case BT_GATT_DISCOVER_INCLUDE:
case BT_GATT_DISCOVER_CHARACTERISTIC:
return gatt_read_type(conn, params);
case BT_GATT_DISCOVER_DESCRIPTOR:
/* Only descriptors can be filtered */
if (params->uuid &&
(!bt_uuid_cmp(params->uuid, BT_UUID_GATT_PRIMARY) ||
!bt_uuid_cmp(params->uuid, BT_UUID_GATT_SECONDARY) ||
!bt_uuid_cmp(params->uuid, BT_UUID_GATT_INCLUDE) ||
!bt_uuid_cmp(params->uuid, BT_UUID_GATT_CHRC))) {
return -EINVAL;
}
__fallthrough;
case BT_GATT_DISCOVER_ATTRIBUTE:
return gatt_find_info(conn, params);
default:
LOG_ERR("Invalid discovery type: %u", params->type);
}
return -EINVAL;
}
static void parse_read_by_uuid(struct bt_conn *conn,
struct bt_gatt_read_params *params,
const void *pdu, uint16_t length)
{
const struct bt_att_read_type_rsp *rsp = pdu;
const uint16_t req_start_handle = params->by_uuid.start_handle;
const uint16_t req_end_handle = params->by_uuid.end_handle;
/* Parse values found */
for (length--, pdu = rsp->data; length;
length -= rsp->len, pdu = (const uint8_t *)pdu + rsp->len) {
const struct bt_att_data *data = pdu;
uint16_t handle;
uint16_t len;
handle = sys_le16_to_cpu(data->handle);
/* Handle 0 is invalid */
if (!handle) {
LOG_ERR("Invalid handle");
return;
}
len = rsp->len > length ? length - 2 : rsp->len - 2;
LOG_DBG("handle 0x%04x len %u value %u", handle, rsp->len, len);
if (!IN_RANGE(handle, req_start_handle, req_end_handle)) {
LOG_WRN("Bad peer: ATT read-by-uuid rsp: "
"Handle 0x%04x is outside requested range 0x%04x-0x%04x. "
"Aborting read.",
handle, req_start_handle, req_end_handle);
params->func(conn, BT_ATT_ERR_UNLIKELY, params, NULL, 0);
return;
}
/* Update start_handle */
params->by_uuid.start_handle = handle;
if (params->func(conn, 0, params, data->value, len) ==
BT_GATT_ITER_STOP) {
return;
}
/* Check if long attribute */
if (rsp->len > length) {
break;
}
/* Stop if it's the last handle to be read */
if (params->by_uuid.start_handle == params->by_uuid.end_handle) {
params->func(conn, 0, params, NULL, 0);
return;
}
params->by_uuid.start_handle++;
}
/* Continue reading the attributes */
if (bt_gatt_read(conn, params) < 0) {
params->func(conn, BT_ATT_ERR_UNLIKELY, params, NULL, 0);
}
}
static void gatt_read_rsp(struct bt_conn *conn, int err, const void *pdu,
uint16_t length, void *user_data)
{
struct bt_gatt_read_params *params = user_data;
LOG_DBG("err %d", err);
if (err || !length) {
params->func(conn, att_err_from_int(err), params, NULL, 0);
return;
}
if (!params->handle_count) {
parse_read_by_uuid(conn, params, pdu, length);
return;
}
if (params->func(conn, 0, params, pdu, length) == BT_GATT_ITER_STOP) {
return;
}
/*
* Core Spec 4.2, Vol. 3, Part G, 4.8.1
* 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 < (params->_att_mtu - 1)) {
params->func(conn, 0, params, NULL, 0);
return;
}
params->single.offset += length;
/* Continue reading the attribute */
if (bt_gatt_read(conn, params) < 0) {
params->func(conn, BT_ATT_ERR_UNLIKELY, params, NULL, 0);
}
}
static int gatt_read_blob_encode(struct net_buf *buf, size_t len,
void *user_data)
{
struct bt_gatt_read_params *params = user_data;
struct bt_att_read_blob_req *req;
req = net_buf_add(buf, sizeof(*req));
req->handle = sys_cpu_to_le16(params->single.handle);
req->offset = sys_cpu_to_le16(params->single.offset);
return 0;
}
static int gatt_read_blob(struct bt_conn *conn,
struct bt_gatt_read_params *params)
{
LOG_DBG("handle 0x%04x offset 0x%04x", params->single.handle, params->single.offset);
return gatt_req_send(conn, gatt_read_rsp, params,
gatt_read_blob_encode, BT_ATT_OP_READ_BLOB_REQ,
sizeof(struct bt_att_read_blob_req),
BT_ATT_CHAN_OPT(params));
}
static int gatt_read_uuid_encode(struct net_buf *buf, size_t len,
void *user_data)
{
struct bt_gatt_read_params *params = user_data;
struct bt_att_read_type_req *req;
req = net_buf_add(buf, sizeof(*req));
req->start_handle = sys_cpu_to_le16(params->by_uuid.start_handle);
req->end_handle = sys_cpu_to_le16(params->by_uuid.end_handle);
if (params->by_uuid.uuid->type == BT_UUID_TYPE_16) {
net_buf_add_le16(buf, BT_UUID_16(params->by_uuid.uuid)->val);
} else {
net_buf_add_mem(buf, BT_UUID_128(params->by_uuid.uuid)->val, 16);
}
return 0;
}
static int gatt_read_uuid(struct bt_conn *conn,
struct bt_gatt_read_params *params)
{
LOG_DBG("start_handle 0x%04x end_handle 0x%04x uuid %s", params->by_uuid.start_handle,
params->by_uuid.end_handle, bt_uuid_str(params->by_uuid.uuid));
return gatt_req_send(conn, gatt_read_rsp, params,
gatt_read_uuid_encode, BT_ATT_OP_READ_TYPE_REQ,
sizeof(struct bt_att_read_type_req),
BT_ATT_CHAN_OPT(params));
}
#if defined(CONFIG_BT_GATT_READ_MULTIPLE)
static void gatt_read_mult_rsp(struct bt_conn *conn, int err, const void *pdu,
uint16_t length, void *user_data)
{
struct bt_gatt_read_params *params = user_data;
LOG_DBG("err %d", err);
if (err || !length) {
params->func(conn, att_err_from_int(err), params, NULL, 0);
return;
}
params->func(conn, 0, params, pdu, length);
/* mark read as complete since read multiple is single response */
params->func(conn, 0, params, NULL, 0);
}
static int gatt_read_mult_encode(struct net_buf *buf, size_t len,
void *user_data)
{
struct bt_gatt_read_params *params = user_data;
uint8_t i;
for (i = 0U; i < params->handle_count; i++) {
net_buf_add_le16(buf, params->multiple.handles[i]);
}
return 0;
}
static int gatt_read_mult(struct bt_conn *conn,
struct bt_gatt_read_params *params)
{
LOG_DBG("handle_count %zu", params->handle_count);
return gatt_req_send(conn, gatt_read_mult_rsp, params,
gatt_read_mult_encode, BT_ATT_OP_READ_MULT_REQ,
params->handle_count * sizeof(uint16_t),
BT_ATT_CHAN_OPT(params));
}
#else
static int gatt_read_mult(struct bt_conn *conn,
struct bt_gatt_read_params *params)
{
return -ENOTSUP;
}
#endif /* CONFIG_BT_GATT_READ_MULTIPLE */
#if defined(CONFIG_BT_GATT_READ_MULT_VAR_LEN)
static void gatt_read_mult_vl_rsp(struct bt_conn *conn, int err,
const void *pdu, uint16_t length,
void *user_data)
{
struct bt_gatt_read_params *params = user_data;
const struct bt_att_read_mult_vl_rsp *rsp;
struct net_buf_simple buf;
LOG_DBG("err %d", err);
if (err || !length) {
params->func(conn, att_err_from_int(err), params, NULL, 0);
return;
}
net_buf_simple_init_with_data(&buf, (void *)pdu, length);
while (buf.len >= sizeof(*rsp)) {
uint16_t len;
rsp = net_buf_simple_pull_mem(&buf, sizeof(*rsp));
len = sys_le16_to_cpu(rsp->len);
/* If a Length Value Tuple is truncated, then the amount of
* Attribute Value will be less than the value of the Value
* Length field.
*/
if (len > buf.len) {
len = buf.len;
}
params->func(conn, 0, params, rsp->value, len);
net_buf_simple_pull_mem(&buf, len);
}
/* mark read as complete since read multiple is single response */
params->func(conn, 0, params, NULL, 0);
}
static int gatt_read_mult_vl_encode(struct net_buf *buf, size_t len,
void *user_data)
{
struct bt_gatt_read_params *params = user_data;
uint8_t i;
for (i = 0U; i < params->handle_count; i++) {
net_buf_add_le16(buf, params->multiple.handles[i]);
}
return 0;
}
static int gatt_read_mult_vl(struct bt_conn *conn,
struct bt_gatt_read_params *params)
{
LOG_DBG("handle_count %zu", params->handle_count);
return gatt_req_send(conn, gatt_read_mult_vl_rsp, params,
gatt_read_mult_vl_encode,
BT_ATT_OP_READ_MULT_VL_REQ,
params->handle_count * sizeof(uint16_t),
BT_ATT_CHAN_OPT(params));
}
#else
static int gatt_read_mult_vl(struct bt_conn *conn,
struct bt_gatt_read_params *params)
{
return -ENOTSUP;
}
#endif /* CONFIG_BT_GATT_READ_MULT_VAR_LEN */
static int gatt_read_encode(struct net_buf *buf, size_t len, void *user_data)
{
struct bt_gatt_read_params *params = user_data;
struct bt_att_read_req *req;
req = net_buf_add(buf, sizeof(*req));
req->handle = sys_cpu_to_le16(params->single.handle);
return 0;
}
int bt_gatt_read(struct bt_conn *conn, struct bt_gatt_read_params *params)
{
__ASSERT(conn, "invalid parameters\n");
__ASSERT(params && params->func, "invalid parameters\n");
if (conn->state != BT_CONN_CONNECTED) {
return -ENOTCONN;
}
if (params->handle_count == 0) {
return gatt_read_uuid(conn, params);
}
if (params->handle_count > 1) {
if (params->multiple.variable) {
return gatt_read_mult_vl(conn, params);
} else {
return gatt_read_mult(conn, params);
}
}
if (params->single.offset) {
return gatt_read_blob(conn, params);
}
LOG_DBG("handle 0x%04x", params->single.handle);
return gatt_req_send(conn, gatt_read_rsp, params, gatt_read_encode,
BT_ATT_OP_READ_REQ, sizeof(struct bt_att_read_req),
BT_ATT_CHAN_OPT(params));
}
static void gatt_write_rsp(struct bt_conn *conn, int err, const void *pdu,
uint16_t length, void *user_data)
{
struct bt_gatt_write_params *params = user_data;
LOG_DBG("err %d", err);
params->func(conn, att_err_from_int(err), params);
}
int bt_gatt_write_without_response_cb(struct bt_conn *conn, uint16_t handle,
const void *data, uint16_t length, bool sign,
bt_gatt_complete_func_t func,
void *user_data)
{
struct net_buf *buf;
struct bt_att_write_cmd *cmd;
size_t write;
__ASSERT(conn, "invalid parameters\n");
__ASSERT(handle, "invalid parameters\n");
if (conn->state != BT_CONN_CONNECTED) {
return -ENOTCONN;
}
#if defined(CONFIG_BT_SMP)
if (conn->encrypt) {
/* Don't need to sign if already encrypted */
sign = false;
}
#endif
if (sign) {
buf = bt_att_create_pdu(conn, BT_ATT_OP_SIGNED_WRITE_CMD,
sizeof(*cmd) + length + 12);
} else {
buf = bt_att_create_pdu(conn, BT_ATT_OP_WRITE_CMD,
sizeof(*cmd) + length);
}
if (!buf) {
return -ENOMEM;
}
cmd = net_buf_add(buf, sizeof(*cmd));
cmd->handle = sys_cpu_to_le16(handle);
write = net_buf_append_bytes(buf, length, data, K_NO_WAIT, NULL, NULL);
if (write != length) {
LOG_WRN("Unable to allocate length %u: only %zu written", length, write);
net_buf_unref(buf);
return -ENOMEM;
}
LOG_DBG("handle 0x%04x length %u", handle, length);
bt_att_set_tx_meta_data(buf, func, user_data, BT_ATT_CHAN_OPT_NONE);
return bt_att_send(conn, buf);
}
static int gatt_exec_encode(struct net_buf *buf, size_t len, void *user_data)
{
struct bt_att_exec_write_req *req;
req = net_buf_add(buf, sizeof(*req));
req->flags = BT_ATT_FLAG_EXEC;
return 0;
}
static int gatt_exec_write(struct bt_conn *conn,
struct bt_gatt_write_params *params)
{
LOG_DBG("");
return gatt_req_send(conn, gatt_write_rsp, params, gatt_exec_encode,
BT_ATT_OP_EXEC_WRITE_REQ,
sizeof(struct bt_att_exec_write_req),
BT_ATT_CHAN_OPT(params));
}
static int gatt_cancel_encode(struct net_buf *buf, size_t len, void *user_data)
{
struct bt_att_exec_write_req *req;
req = net_buf_add(buf, sizeof(*req));
req->flags = BT_ATT_FLAG_CANCEL;
return 0;
}
static int gatt_cancel_all_writes(struct bt_conn *conn,
struct bt_gatt_write_params *params)
{
LOG_DBG("");
return gatt_req_send(conn, gatt_write_rsp, params, gatt_cancel_encode,
BT_ATT_OP_EXEC_WRITE_REQ,
sizeof(struct bt_att_exec_write_req),
BT_ATT_CHAN_OPT(params));
}
static void gatt_prepare_write_rsp(struct bt_conn *conn, int err,
const void *pdu, uint16_t length,
void *user_data)
{
struct bt_gatt_write_params *params = user_data;
const struct bt_att_prepare_write_rsp *rsp;
size_t len;
bool data_valid;
LOG_DBG("err %d", err);
/* Don't continue in case of error */
if (err) {
params->func(conn, att_err_from_int(err), params);
return;
}
if (length < sizeof(*rsp)) {
LOG_WRN("Parse err");
goto fail;
}
rsp = pdu;
len = length - sizeof(*rsp);
if (len > params->length) {
LOG_ERR("Incorrect length, canceling write");
if (gatt_cancel_all_writes(conn, params)) {
goto fail;
}
return;
}
data_valid = memcmp(params->data, rsp->value, len) == 0;
if (params->offset != rsp->offset || !data_valid) {
LOG_ERR("Incorrect offset or data in response, canceling write");
if (gatt_cancel_all_writes(conn, params)) {
goto fail;
}
return;
}
/* Update params */
params->offset += len;
params->data = (const uint8_t *)params->data + len;
params->length -= len;
/* If there is no more data execute */
if (!params->length) {
if (gatt_exec_write(conn, params)) {
goto fail;
}
return;
}
/* Write next chunk */
if (!bt_gatt_write(conn, params)) {
/* Success */
return;
}
fail:
/* Notify application that the write operation has failed */
params->func(conn, BT_ATT_ERR_UNLIKELY, params);
}
static int gatt_prepare_write_encode(struct net_buf *buf, size_t len,
void *user_data)
{
struct bt_gatt_write_params *params = user_data;
struct bt_att_prepare_write_req *req;
size_t write;
req = net_buf_add(buf, sizeof(*req));
req->handle = sys_cpu_to_le16(params->handle);
req->offset = sys_cpu_to_le16(params->offset);
write = net_buf_append_bytes(buf, len - sizeof(*req),
(uint8_t *)params->data, K_NO_WAIT, NULL,
NULL);
if (write != (len - sizeof(*req))) {
return -ENOMEM;
}
return 0;
}
static int gatt_prepare_write(struct bt_conn *conn,
struct bt_gatt_write_params *params)
{
uint16_t len, req_len;
req_len = sizeof(struct bt_att_prepare_write_req);
len = bt_att_get_mtu(conn) - req_len - 1;
len = MIN(params->length, len);
len += req_len;
return gatt_req_send(conn, gatt_prepare_write_rsp, params,
gatt_prepare_write_encode,
BT_ATT_OP_PREPARE_WRITE_REQ, len,
BT_ATT_CHAN_OPT(params));
}
static int gatt_write_encode(struct net_buf *buf, size_t len, void *user_data)
{
struct bt_gatt_write_params *params = user_data;
struct bt_att_write_req *req;
size_t write;
req = net_buf_add(buf, sizeof(*req));
req->handle = sys_cpu_to_le16(params->handle);
write = net_buf_append_bytes(buf, params->length, params->data,
K_NO_WAIT, NULL, NULL);
if (write != params->length) {
return -ENOMEM;
}
return 0;
}
int bt_gatt_write(struct bt_conn *conn, struct bt_gatt_write_params *params)
{
size_t len;
__ASSERT(conn, "invalid parameters\n");
__ASSERT(params && params->func, "invalid parameters\n");
__ASSERT(params->handle, "invalid parameters\n");
if (conn->state != BT_CONN_CONNECTED) {
return -ENOTCONN;
}
len = sizeof(struct bt_att_write_req) + params->length;
/* Use Prepare Write if offset is set or Long Write is required */
if (params->offset || len > (bt_att_get_mtu(conn) - 1)) {
return gatt_prepare_write(conn, params);
}
LOG_DBG("handle 0x%04x length %u", params->handle, params->length);
return gatt_req_send(conn, gatt_write_rsp, params, gatt_write_encode,
BT_ATT_OP_WRITE_REQ, len, BT_ATT_CHAN_OPT(params));
}
static void gatt_write_ccc_rsp(struct bt_conn *conn, int err,
const void *pdu, uint16_t length,
void *user_data)
{
struct bt_gatt_subscribe_params *params = user_data;
uint8_t att_err;
LOG_DBG("err %d", err);
atomic_clear_bit(params->flags, BT_GATT_SUBSCRIBE_FLAG_WRITE_PENDING);
/* if write to CCC failed we remove subscription and notify app */
if (err) {
struct gatt_sub *sub;
sys_snode_t *node, *tmp, *prev;
sub = gatt_sub_find(conn);
if (!sub) {
return;
}
prev = NULL;
SYS_SLIST_FOR_EACH_NODE_SAFE(&sub->list, node, tmp) {
if (node == &params->node) {
gatt_sub_remove(conn, sub, prev, params);
break;
}
prev = node;
}
} else if (!params->value) {
/* Notify with NULL data to complete unsubscribe */
params->notify(conn, params, NULL, 0);
}
att_err = att_err_from_int(err);
if (params->subscribe) {
params->subscribe(conn, att_err, params);
} else if (params->write) {
/* TODO: Remove after deprecation */
LOG_WRN("write callback is deprecated, use subscribe cb instead");
params->write(conn, att_err, NULL);
}
}
static int gatt_write_ccc_buf(struct net_buf *buf, size_t len, void *user_data)
{
struct bt_gatt_subscribe_params *params = user_data;
struct bt_att_write_req *write_req;
write_req = net_buf_add(buf, sizeof(*write_req));
write_req->handle = sys_cpu_to_le16(params->ccc_handle);
net_buf_add_le16(buf, params->value);
atomic_set_bit(params->flags, BT_GATT_SUBSCRIBE_FLAG_WRITE_PENDING);
return 0;
}
static int gatt_write_ccc(struct bt_conn *conn,
struct bt_gatt_subscribe_params *params,
bt_att_func_t rsp)
{
size_t len = sizeof(struct bt_att_write_req) + sizeof(uint16_t);
LOG_DBG("handle 0x%04x value 0x%04x", params->ccc_handle, params->value);
/* The value of the params doesn't matter, this is just so we don't
* repeat CCC writes when the AUTO_RESUBSCRIBE quirk is enabled.
*/
atomic_set_bit(params->flags, BT_GATT_SUBSCRIBE_FLAG_SENT);
return gatt_req_send(conn, rsp, params,
gatt_write_ccc_buf, BT_ATT_OP_WRITE_REQ, len,
BT_ATT_CHAN_OPT(params));
}
#if defined(CONFIG_BT_GATT_AUTO_DISCOVER_CCC)
static uint8_t gatt_ccc_discover_cb(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
struct bt_gatt_subscribe_params *sub_params = params->sub_params;
if (!attr) {
memset(params, 0, sizeof(*params));
sub_params->notify(conn, sub_params, NULL, 0);
return BT_GATT_ITER_STOP;
}
if (params->type == BT_GATT_DISCOVER_DESCRIPTOR) {
memset(params, 0, sizeof(*params));
sub_params->ccc_handle = attr->handle;
if (bt_gatt_subscribe(conn, sub_params)) {
sub_params->notify(conn, sub_params, NULL, 0);
}
/* else if no error occurred, then `bt_gatt_subscribe` will
* call the notify function once subscribed.
*/
return BT_GATT_ITER_STOP;
}
return BT_GATT_ITER_CONTINUE;
}
static int gatt_ccc_discover(struct bt_conn *conn,
struct bt_gatt_subscribe_params *params)
{
int err;
static struct bt_uuid_16 ccc_uuid = BT_UUID_INIT_16(0);
memcpy(&ccc_uuid, BT_UUID_GATT_CCC, sizeof(ccc_uuid));
memset(params->disc_params, 0, sizeof(*params->disc_params));
params->disc_params->sub_params = params;
params->disc_params->uuid = &ccc_uuid.uuid;
params->disc_params->type = BT_GATT_DISCOVER_DESCRIPTOR;
params->disc_params->start_handle = params->value_handle;
params->disc_params->end_handle = params->end_handle;
params->disc_params->func = gatt_ccc_discover_cb;
#if defined(CONFIG_BT_EATT)
params->disc_params->chan_opt = params->chan_opt;
#endif /* CONFIG_BT_EATT */
err = bt_gatt_discover(conn, params->disc_params);
if (err) {
LOG_DBG("CCC Discovery failed (err %d)", err);
return err;
}
return 0;
}
#endif /* CONFIG_BT_GATT_AUTO_DISCOVER_CCC */
int bt_gatt_subscribe(struct bt_conn *conn,
struct bt_gatt_subscribe_params *params)
{
struct gatt_sub *sub;
struct bt_gatt_subscribe_params *tmp;
bool has_subscription = false;
__ASSERT(conn, "invalid parameters\n");
__ASSERT(params && params->notify, "invalid parameters\n");
__ASSERT(params->value, "invalid parameters\n");
#if defined(CONFIG_BT_GATT_AUTO_DISCOVER_CCC)
__ASSERT(params->ccc_handle ||
(params->end_handle && params->disc_params),
"invalid parameters\n");
#else
__ASSERT(params->ccc_handle, "invalid parameters\n");
#endif
if (conn->state != BT_CONN_CONNECTED) {
return -ENOTCONN;
}
sub = gatt_sub_add(conn);
if (!sub) {
return -ENOMEM;
}
#if defined(CONFIG_BT_GATT_AUTO_DISCOVER_CCC)
if (params->disc_params != NULL && params->disc_params->func == gatt_ccc_discover_cb) {
/* Already in progress */
return -EBUSY;
}
#endif
/* Lookup existing subscriptions */
SYS_SLIST_FOR_EACH_CONTAINER(&sub->list, tmp, node) {
/* Fail if entry already exists */
if (tmp == params) {
gatt_sub_remove(conn, sub, NULL, NULL);
return -EALREADY;
}
/* Check if another subscription exists */
if (tmp->value_handle == params->value_handle &&
tmp->value >= params->value) {
has_subscription = true;
}
}
/* Skip write if already subscribed */
if (!has_subscription) {
int err;
#if defined(CONFIG_BT_GATT_AUTO_DISCOVER_CCC)
if (!params->ccc_handle) {
return gatt_ccc_discover(conn, params);
}
#endif
err = gatt_write_ccc(conn, params, gatt_write_ccc_rsp);
if (err) {
gatt_sub_remove(conn, sub, NULL, NULL);
return err;
}
}
/*
* Add subscription before write complete as some implementation were
* reported to send notification before reply to CCC write.
*/
sys_slist_prepend(&sub->list, &params->node);
return 0;
}
int bt_gatt_resubscribe(uint8_t id, const bt_addr_le_t *peer,
struct bt_gatt_subscribe_params *params)
{
struct gatt_sub *sub;
struct bt_gatt_subscribe_params *tmp;
__ASSERT(params && params->notify, "invalid parameters\n");
__ASSERT(params->value, "invalid parameters\n");
__ASSERT(params->ccc_handle, "invalid parameters\n");
sub = gatt_sub_add_by_addr(id, peer);
if (!sub) {
return -ENOMEM;
}
/* Lookup existing subscriptions */
SYS_SLIST_FOR_EACH_CONTAINER(&sub->list, tmp, node) {
/* Fail if entry already exists */
if (tmp == params) {
gatt_sub_remove(NULL, sub, NULL, NULL);
return -EALREADY;
}
}
sys_slist_prepend(&sub->list, &params->node);
return 0;
}
int bt_gatt_unsubscribe(struct bt_conn *conn,
struct bt_gatt_subscribe_params *params)
{
struct gatt_sub *sub;
struct bt_gatt_subscribe_params *tmp;
bool has_subscription = false, found = false;
__ASSERT(conn, "invalid parameters\n");
__ASSERT(params, "invalid parameters\n");
if (conn->state != BT_CONN_CONNECTED) {
return -ENOTCONN;
}
sub = gatt_sub_find(conn);
if (!sub) {
return -EINVAL;
}
/* Lookup existing subscriptions */
SYS_SLIST_FOR_EACH_CONTAINER(&sub->list, tmp, node) {
if (params == tmp) {
found = true;
continue;
}
/* Check if there still remains any other subscription */
if (tmp->value_handle == params->value_handle) {
has_subscription = true;
}
}
if (!found) {
return -EINVAL;
}
/* Attempt to cancel if write is pending */
if (atomic_test_bit(params->flags, BT_GATT_SUBSCRIBE_FLAG_WRITE_PENDING)) {
bt_gatt_cancel(conn, params);
}
if (!has_subscription) {
int err;
params->value = 0x0000;
err = gatt_write_ccc(conn, params, gatt_write_ccc_rsp);
if (err) {
return err;
}
}
sys_slist_find_and_remove(&sub->list, &params->node);
if (gatt_sub_is_empty(sub)) {
gatt_sub_free(sub);
}
if (has_subscription) {
/* Notify with NULL data to complete unsubscribe */
params->notify(conn, params, NULL, 0);
}
return 0;
}
void bt_gatt_cancel(struct bt_conn *conn, void *params)
{
struct bt_att_req *req;
bt_att_func_t func = NULL;
k_sched_lock();
req = bt_att_find_req_by_user_data(conn, params);
if (req) {
func = req->func;
bt_att_req_cancel(conn, req);
}
k_sched_unlock();
if (func) {
func(conn, BT_ATT_ERR_UNLIKELY, NULL, 0, params);
}
}
#if defined(CONFIG_BT_GATT_AUTO_RESUBSCRIBE)
static void gatt_resub_ccc_rsp(struct bt_conn *conn, int err,
const void *pdu, uint16_t length,
void *user_data)
{
LOG_DBG("err %d", err);
if (err == -ECONNRESET) {
/* The resubscriptions are implicit, thus in the case of ACL
* disconnection during the CCC value ATT Write, there is no
* need to notify the application.
*/
return;
}
gatt_write_ccc_rsp(conn, err, pdu, length, user_data);
}
static int gatt_resub_ccc(struct bt_conn *conn,
struct bt_gatt_subscribe_params *params)
{
return gatt_write_ccc(conn, params, gatt_resub_ccc_rsp);
}
static void add_subscriptions(struct bt_conn *conn)
{
struct gatt_sub *sub;
struct bt_gatt_subscribe_params *params;
if (!bt_addr_le_is_bonded(conn->id, &conn->le.dst)) {
return;
}
sub = gatt_sub_find(conn);
if (!sub) {
return;
}
/* Lookup existing subscriptions */
SYS_SLIST_FOR_EACH_CONTAINER(&sub->list, params, node) {
if (!atomic_test_bit(params->flags,
BT_GATT_SUBSCRIBE_FLAG_SENT) &&
!atomic_test_bit(params->flags,
BT_GATT_SUBSCRIBE_FLAG_NO_RESUB)) {
int err;
/* Force write to CCC to workaround devices that don't
* track it properly.
*/
err = gatt_resub_ccc(conn, params);
if (err < 0) {
LOG_WRN("conn %p params %p resub failed (err %d)",
(void *)conn, params, err);
}
}
}
}
#endif /* CONFIG_BT_GATT_AUTO_RESUBSCRIBE */
#if defined(CONFIG_BT_GATT_AUTO_UPDATE_MTU)
static void gatt_exchange_mtu_func(struct bt_conn *conn, uint8_t err,
struct bt_gatt_exchange_params *params)
{
if (err) {
LOG_WRN("conn %p err 0x%02x", conn, err);
}
}
static struct bt_gatt_exchange_params gatt_exchange_params = {
.func = gatt_exchange_mtu_func,
};
#endif /* CONFIG_BT_GATT_AUTO_UPDATE_MTU */
#endif /* CONFIG_BT_GATT_CLIENT */
#define CCC_STORE_MAX 48
static struct bt_gatt_ccc_cfg *ccc_find_cfg(struct _bt_gatt_ccc *ccc,
const bt_addr_le_t *addr,
uint8_t id)
{
for (size_t i = 0; i < ARRAY_SIZE(ccc->cfg); i++) {
if (id == ccc->cfg[i].id &&
bt_addr_le_eq(&ccc->cfg[i].peer, addr)) {
return &ccc->cfg[i];
}
}
return NULL;
}
struct addr_with_id {
const bt_addr_le_t *addr;
uint8_t id;
};
struct ccc_load {
struct addr_with_id addr_with_id;
struct ccc_store *entry;
size_t count;
};
static void ccc_clear(struct _bt_gatt_ccc *ccc,
const bt_addr_le_t *addr,
uint8_t id)
{
struct bt_gatt_ccc_cfg *cfg;
cfg = ccc_find_cfg(ccc, addr, id);
if (!cfg) {
LOG_DBG("Unable to clear CCC: cfg not found");
return;
}
clear_ccc_cfg(cfg);
}
static uint8_t ccc_load(const struct bt_gatt_attr *attr, uint16_t handle,
void *user_data)
{
struct ccc_load *load = user_data;
struct _bt_gatt_ccc *ccc;
struct bt_gatt_ccc_cfg *cfg;
/* Check if attribute is a CCC */
if (attr->write != bt_gatt_attr_write_ccc) {
return BT_GATT_ITER_CONTINUE;
}
ccc = attr->user_data;
/* Clear if value was invalidated */
if (!load->entry) {
ccc_clear(ccc, load->addr_with_id.addr, load->addr_with_id.id);
return BT_GATT_ITER_CONTINUE;
} else if (!load->count) {
return BT_GATT_ITER_STOP;
}
/* Skip if value is not for the given attribute */
if (load->entry->handle != handle) {
/* If attribute handle is bigger then it means
* the attribute no longer exists and cannot
* be restored.
*/
if (load->entry->handle < handle) {
LOG_DBG("Unable to restore CCC: handle 0x%04x cannot be"
" found", load->entry->handle);
goto next;
}
return BT_GATT_ITER_CONTINUE;
}
LOG_DBG("Restoring CCC: handle 0x%04x value 0x%04x", load->entry->handle,
load->entry->value);
cfg = ccc_find_cfg(ccc, load->addr_with_id.addr, load->addr_with_id.id);
if (!cfg) {
cfg = ccc_find_cfg(ccc, BT_ADDR_LE_ANY, 0);
if (!cfg) {
LOG_DBG("Unable to restore CCC: no cfg left");
goto next;
}
bt_addr_le_copy(&cfg->peer, load->addr_with_id.addr);
cfg->id = load->addr_with_id.id;
}
cfg->value = load->entry->value;
next:
load->entry++;
load->count--;
return load->count ? BT_GATT_ITER_CONTINUE : BT_GATT_ITER_STOP;
}
static int ccc_set(const char *name, size_t len_rd, settings_read_cb read_cb,
void *cb_arg)
{
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
struct ccc_store ccc_store[CCC_STORE_MAX];
struct ccc_load load;
bt_addr_le_t addr;
ssize_t len;
int err;
const char *next;
settings_name_next(name, &next);
if (!name) {
LOG_ERR("Insufficient number of arguments");
return -EINVAL;
} else if (!next) {
load.addr_with_id.id = BT_ID_DEFAULT;
} else {
unsigned long next_id = strtoul(next, NULL, 10);
if (next_id >= CONFIG_BT_ID_MAX) {
LOG_ERR("Invalid local identity %lu", next_id);
return -EINVAL;
}
load.addr_with_id.id = (uint8_t)next_id;
}
err = bt_settings_decode_key(name, &addr);
if (err) {
LOG_ERR("Unable to decode address %s", name);
return -EINVAL;
}
load.addr_with_id.addr = &addr;
if (len_rd) {
len = read_cb(cb_arg, ccc_store, sizeof(ccc_store));
if (len < 0) {
LOG_ERR("Failed to decode value (err %zd)", len);
return len;
}
load.entry = ccc_store;
load.count = len / sizeof(*ccc_store);
for (size_t i = 0; i < load.count; i++) {
LOG_DBG("Read CCC: handle 0x%04x value 0x%04x", ccc_store[i].handle,
ccc_store[i].value);
}
} else {
load.entry = NULL;
load.count = 0;
}
bt_gatt_foreach_attr(0x0001, 0xffff, ccc_load, &load);
LOG_DBG("Restored CCC for id:%" PRIu8 " addr:%s", load.addr_with_id.id,
bt_addr_le_str(load.addr_with_id.addr));
}
return 0;
}
static int ccc_set_cb(const char *name, size_t len_rd, settings_read_cb read_cb,
void *cb_arg)
{
if (IS_ENABLED(CONFIG_BT_SETTINGS_CCC_LAZY_LOADING)) {
/* Only load CCCs on demand */
return 0;
}
return ccc_set(name, len_rd, read_cb, cb_arg);
}
BT_SETTINGS_DEFINE(ccc, "ccc", ccc_set_cb, NULL);
static int ccc_set_direct(const char *key, size_t len, settings_read_cb read_cb,
void *cb_arg, void *param)
{
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
const char *name;
LOG_DBG("key: %s", (const char *)param);
/* Only "bt/ccc" settings should ever come here */
if (!settings_name_steq((const char *)param, "bt/ccc", &name)) {
LOG_ERR("Invalid key");
return -EINVAL;
}
return ccc_set(name, len, read_cb, cb_arg);
}
return 0;
}
void bt_gatt_connected(struct bt_conn *conn)
{
struct conn_data data;
LOG_DBG("conn %p", conn);
data.conn = conn;
data.sec = BT_SECURITY_L1;
/* Load CCC settings from backend if bonded */
if (IS_ENABLED(CONFIG_BT_SETTINGS_CCC_LAZY_LOADING) &&
bt_addr_le_is_bonded(conn->id, &conn->le.dst)) {
char key[BT_SETTINGS_KEY_MAX];
if (conn->id) {
char id_str[4];
u8_to_dec(id_str, sizeof(id_str), conn->id);
bt_settings_encode_key(key, sizeof(key), "ccc",
&conn->le.dst, id_str);
} else {
bt_settings_encode_key(key, sizeof(key), "ccc",
&conn->le.dst, NULL);
}
settings_load_subtree_direct(key, ccc_set_direct, (void *)key);
}
bt_gatt_foreach_attr(0x0001, 0xffff, update_ccc, &data);
/* BLUETOOTH CORE SPECIFICATION Version 5.1 | Vol 3, Part C page 2192:
*
* 10.3.1.1 Handling of GATT indications and notifications
*
* A client “requests” a server to send indications and notifications
* by appropriately configuring the server via a Client Characteristic
* Configuration Descriptor. Since the configuration is persistent
* across a disconnection and reconnection, security requirements must
* be checked against the configuration upon a reconnection before
* sending indications or notifications. When a server reconnects to a
* client to send an indication or notification for which security is
* required, the server shall initiate or request encryption with the
* client prior to sending an indication or notification. If the client
* does not have an LTK indicating that the client has lost the bond,
* enabling encryption will fail.
*/
if (IS_ENABLED(CONFIG_BT_SMP) &&
(conn->role == BT_HCI_ROLE_CENTRAL ||
IS_ENABLED(CONFIG_BT_GATT_AUTO_SEC_REQ)) &&
bt_conn_get_security(conn) < data.sec) {
int err = bt_conn_set_security(conn, data.sec);
if (err) {
LOG_WRN("Failed to set security for bonded peer (%d)", err);
}
}
#if defined(CONFIG_BT_GATT_AUTO_UPDATE_MTU)
int err;
err = bt_gatt_exchange_mtu(conn, &gatt_exchange_params);
if (err) {
LOG_WRN("MTU Exchange failed (err %d)", err);
}
#endif /* CONFIG_BT_GATT_AUTO_UPDATE_MTU */
}
void bt_gatt_att_max_mtu_changed(struct bt_conn *conn, uint16_t tx, uint16_t rx)
{
struct bt_gatt_cb *cb;
SYS_SLIST_FOR_EACH_CONTAINER(&callback_list, cb, node) {
if (cb->att_mtu_updated) {
cb->att_mtu_updated(conn, tx, rx);
}
}
}
void bt_gatt_encrypt_change(struct bt_conn *conn)
{
struct conn_data data;
LOG_DBG("conn %p", conn);
data.conn = conn;
data.sec = BT_SECURITY_L1;
#if defined(CONFIG_BT_GATT_AUTO_RESUBSCRIBE)
add_subscriptions(conn);
#endif /* CONFIG_BT_GATT_AUTO_RESUBSCRIBE */
bt_gatt_foreach_attr(0x0001, 0xffff, update_ccc, &data);
if (!bt_gatt_change_aware(conn, false)) {
/* Send a Service Changed indication if the current peer is
* marked as change-unaware.
*/
sc_indicate(0x0001, 0xffff);
}
}
bool bt_gatt_change_aware(struct bt_conn *conn, bool req)
{
#if defined(CONFIG_BT_GATT_CACHING)
struct gatt_cf_cfg *cfg;
cfg = find_cf_cfg(conn);
if (!cfg || !CF_ROBUST_CACHING(cfg)) {
return true;
}
if (atomic_test_bit(cfg->flags, CF_CHANGE_AWARE)) {
return true;
}
/* BLUETOOTH CORE SPECIFICATION Version 5.1 | Vol 3, Part G page 2350:
* If a change-unaware client sends an ATT command, the server shall
* ignore it.
*/
if (!req) {
return false;
}
/* BLUETOOTH CORE SPECIFICATION Version 5.3 | Vol 3, Part G page 1475:
* 2.5.2.1 Robust Caching
* A change-unaware connected client becomes change-aware when it reads
* the Database Hash characteristic and then the server receives another
* ATT request from the client.
*/
if (atomic_test_and_clear_bit(cfg->flags, CF_DB_HASH_READ)) {
bt_att_clear_out_of_sync_sent(conn);
set_change_aware(cfg, true);
return true;
}
/* BLUETOOTH CORE SPECIFICATION Version 5.3 | Vol 3, Part G page 1476:
* 2.5.2.1 Robust Caching
* ... a change-unaware connected client using exactly one ATT bearer
* becomes change-aware when ...
* The server sends the client a response with the Error Code parameter
* set to Database Out Of Sync (0x12) and then the server receives
* another ATT request from the client.
*/
if (bt_att_fixed_chan_only(conn) && bt_att_out_of_sync_sent_on_fixed(conn)) {
atomic_clear_bit(cfg->flags, CF_DB_HASH_READ);
bt_att_clear_out_of_sync_sent(conn);
set_change_aware(cfg, true);
return true;
}
return false;
#else
return true;
#endif
}
static struct gatt_cf_cfg *find_cf_cfg_by_addr(uint8_t id,
const bt_addr_le_t *addr)
{
if (IS_ENABLED(CONFIG_BT_GATT_CACHING)) {
int i;
for (i = 0; i < ARRAY_SIZE(cf_cfg); i++) {
if (id == cf_cfg[i].id &&
bt_addr_le_eq(addr, &cf_cfg[i].peer)) {
return &cf_cfg[i];
}
}
}
return NULL;
}
#if defined(CONFIG_BT_SETTINGS)
struct ccc_save {
struct addr_with_id addr_with_id;
struct ccc_store store[CCC_STORE_MAX];
size_t count;
};
static uint8_t ccc_save(const struct bt_gatt_attr *attr, uint16_t handle,
void *user_data)
{
struct ccc_save *save = user_data;
struct _bt_gatt_ccc *ccc;
struct bt_gatt_ccc_cfg *cfg;
/* Check if attribute is a CCC */
if (attr->write != bt_gatt_attr_write_ccc) {
return BT_GATT_ITER_CONTINUE;
}
ccc = attr->user_data;
/* Check if there is a cfg for the peer */
cfg = ccc_find_cfg(ccc, save->addr_with_id.addr, save->addr_with_id.id);
if (!cfg) {
return BT_GATT_ITER_CONTINUE;
}
LOG_DBG("Storing CCCs handle 0x%04x value 0x%04x", handle, cfg->value);
save->store[save->count].handle = handle;
save->store[save->count].value = cfg->value;
save->count++;
return BT_GATT_ITER_CONTINUE;
}
int bt_gatt_store_ccc(uint8_t id, const bt_addr_le_t *addr)
{
struct ccc_save save;
size_t len;
char *str;
int err;
save.addr_with_id.addr = addr;
save.addr_with_id.id = id;
save.count = 0;
bt_gatt_foreach_attr(0x0001, 0xffff, ccc_save, &save);
if (save.count) {
str = (char *)save.store;
len = save.count * sizeof(*save.store);
} else {
/* No entries to encode, just clear */
str = NULL;
len = 0;
}
err = bt_settings_store_ccc(id, addr, str, len);
if (err) {
LOG_ERR("Failed to store CCCs (err %d)", err);
return err;
}
LOG_DBG("Stored CCCs for %s", bt_addr_le_str(addr));
if (len) {
for (size_t i = 0; i < save.count; i++) {
LOG_DBG(" CCC: handle 0x%04x value 0x%04x", save.store[i].handle,
save.store[i].value);
}
} else {
LOG_DBG(" CCC: NULL");
}
return 0;
}
#if defined(CONFIG_BT_GATT_SERVICE_CHANGED)
static int sc_set(const char *name, size_t len_rd, settings_read_cb read_cb,
void *cb_arg)
{
struct gatt_sc_cfg *cfg;
uint8_t id;
bt_addr_le_t addr;
ssize_t len;
int err;
const char *next;
if (!name) {
LOG_ERR("Insufficient number of arguments");
return -EINVAL;
}
err = bt_settings_decode_key(name, &addr);
if (err) {
LOG_ERR("Unable to decode address %s", name);
return -EINVAL;
}
settings_name_next(name, &next);
if (!next) {
id = BT_ID_DEFAULT;
} else {
unsigned long next_id = strtoul(next, NULL, 10);
if (next_id >= CONFIG_BT_ID_MAX) {
LOG_ERR("Invalid local identity %lu", next_id);
return -EINVAL;
}
id = (uint8_t)next_id;
}
cfg = find_sc_cfg(id, &addr);
if (!cfg && len_rd) {
/* Find and initialize a free sc_cfg entry */
cfg = find_sc_cfg(BT_ID_DEFAULT, BT_ADDR_LE_ANY);
if (!cfg) {
LOG_ERR("Unable to restore SC: no cfg left");
return -ENOMEM;
}
cfg->id = id;
bt_addr_le_copy(&cfg->peer, &addr);
}
if (len_rd) {
len = read_cb(cb_arg, &cfg->data, sizeof(cfg->data));
if (len < 0) {
LOG_ERR("Failed to decode value (err %zd)", len);
return len;
}
LOG_DBG("Read SC: len %zd", len);
LOG_DBG("Restored SC for %s", bt_addr_le_str(&addr));
} else if (cfg) {
/* Clear configuration */
memset(cfg, 0, sizeof(*cfg));
LOG_DBG("Removed SC for %s", bt_addr_le_str(&addr));
}
return 0;
}
static int sc_commit(void)
{
atomic_set_bit(gatt_sc.flags, SC_LOAD);
atomic_clear_bit(gatt_sc.flags, SC_INDICATE_PENDING);
if (atomic_test_bit(gatt_sc.flags, SC_RANGE_CHANGED)) {
/* Schedule SC indication since the range has changed */
sc_work_submit(SC_TIMEOUT);
}
return 0;
}
BT_SETTINGS_DEFINE(sc, "sc", sc_set, sc_commit);
#endif /* CONFIG_BT_GATT_SERVICE_CHANGED */
#if defined(CONFIG_BT_GATT_CACHING)
static int cf_set(const char *name, size_t len_rd, settings_read_cb read_cb,
void *cb_arg)
{
struct gatt_cf_cfg *cfg;
bt_addr_le_t addr;
const char *next;
ssize_t len;
int err;
uint8_t id;
if (!name) {
LOG_ERR("Insufficient number of arguments");
return -EINVAL;
}
err = bt_settings_decode_key(name, &addr);
if (err) {
LOG_ERR("Unable to decode address %s", name);
return -EINVAL;
}
settings_name_next(name, &next);
if (!next) {
id = BT_ID_DEFAULT;
} else {
unsigned long next_id = strtoul(next, NULL, 10);
if (next_id >= CONFIG_BT_ID_MAX) {
LOG_ERR("Invalid local identity %lu", next_id);
return -EINVAL;
}
id = (uint8_t)next_id;
}
cfg = find_cf_cfg_by_addr(id, &addr);
if (!cfg) {
cfg = find_cf_cfg(NULL);
if (!cfg) {
LOG_ERR("Unable to restore CF: no cfg left");
return -ENOMEM;
}
cfg->id = id;
bt_addr_le_copy(&cfg->peer, &addr);
}
if (len_rd) {
char dst[CF_NUM_BYTES + CF_FLAGS_STORE_LEN];
len = read_cb(cb_arg, dst, sizeof(dst));
if (len < 0) {
LOG_ERR("Failed to decode value (err %zd)", len);
return len;
}
memcpy(cfg->data, dst, sizeof(cfg->data));
LOG_DBG("Read CF: len %zd", len);
if (len != sizeof(dst)) {
LOG_WRN("Change-aware status not found in settings, "
"defaulting peer status to change-unaware");
set_change_aware(cfg, false);
} else {
/* change-aware byte is present in NVS */
uint8_t change_aware = dst[sizeof(cfg->data)];
if (change_aware & ~BIT(CF_CHANGE_AWARE)) {
LOG_WRN("Read back bad change-aware value: 0x%x, "
"defaulting peer status to change-unaware",
change_aware);
set_change_aware(cfg, false);
} else {
set_change_aware_no_store(cfg, change_aware);
}
}
} else {
clear_cf_cfg(cfg);
}
LOG_DBG("Restored CF for %s", bt_addr_le_str(&addr));
return 0;
}
BT_SETTINGS_DEFINE(cf, "cf", cf_set, NULL);
static int db_hash_set(const char *name, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
ssize_t len;
len = read_cb(cb_arg, db_hash.stored_hash, sizeof(db_hash.stored_hash));
if (len < 0) {
LOG_ERR("Failed to decode value (err %zd)", len);
return len;
}
LOG_HEXDUMP_DBG(db_hash.stored_hash, sizeof(db_hash.stored_hash), "Stored Hash: ");
return 0;
}
static int db_hash_commit(void)
{
atomic_set_bit(gatt_sc.flags, DB_HASH_LOAD);
/* Calculate the hash and compare it against the value loaded from
* flash. Do it from the current context to avoid any potential race
* conditions.
*/
do_db_hash();
return 0;
}
BT_SETTINGS_DEFINE(hash, "hash", db_hash_set, db_hash_commit);
#endif /*CONFIG_BT_GATT_CACHING */
#endif /* CONFIG_BT_SETTINGS */
static uint8_t remove_peer_from_attr(const struct bt_gatt_attr *attr,
uint16_t handle, void *user_data)
{
const struct addr_with_id *addr_with_id = user_data;
struct _bt_gatt_ccc *ccc;
struct bt_gatt_ccc_cfg *cfg;
/* Check if attribute is a CCC */
if (attr->write != bt_gatt_attr_write_ccc) {
return BT_GATT_ITER_CONTINUE;
}
ccc = attr->user_data;
/* Check if there is a cfg for the peer */
cfg = ccc_find_cfg(ccc, addr_with_id->addr, addr_with_id->id);
if (cfg) {
memset(cfg, 0, sizeof(*cfg));
}
return BT_GATT_ITER_CONTINUE;
}
static int bt_gatt_clear_ccc(uint8_t id, const bt_addr_le_t *addr)
{
struct addr_with_id addr_with_id = {
.addr = addr,
.id = id,
};
bt_gatt_foreach_attr(0x0001, 0xffff, remove_peer_from_attr,
&addr_with_id);
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
return bt_settings_delete_ccc(id, addr);
}
return 0;
}
static int bt_gatt_clear_cf(uint8_t id, const bt_addr_le_t *addr)
{
struct gatt_cf_cfg *cfg;
cfg = find_cf_cfg_by_addr(id, addr);
if (cfg) {
clear_cf_cfg(cfg);
}
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
return bt_settings_delete_ccc(id, addr);
}
return 0;
}
static struct gatt_sub *find_gatt_sub(uint8_t id, const bt_addr_le_t *addr)
{
for (int i = 0; i < ARRAY_SIZE(subscriptions); i++) {
struct gatt_sub *sub = &subscriptions[i];
if (id == sub->id &&
bt_addr_le_eq(addr, &sub->peer)) {
return sub;
}
}
return NULL;
}
static void bt_gatt_clear_subscriptions(uint8_t id, const bt_addr_le_t *addr)
{
struct gatt_sub *sub;
struct bt_gatt_subscribe_params *params, *tmp;
sys_snode_t *prev = NULL;
sub = find_gatt_sub(id, addr);
if (!sub) {
return;
}
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&sub->list, params, tmp,
node) {
params->value = 0U;
gatt_sub_remove(NULL, sub, prev, params);
}
}
int bt_gatt_clear(uint8_t id, const bt_addr_le_t *addr)
{
int err;
err = bt_gatt_clear_ccc(id, addr);
if (err < 0) {
return err;
}
if (IS_ENABLED(CONFIG_BT_GATT_SERVICE_CHANGED)) {
err = bt_gatt_clear_sc(id, addr);
if (err < 0) {
return err;
}
}
if (IS_ENABLED(CONFIG_BT_GATT_CACHING)) {
err = bt_gatt_clear_cf(id, addr);
if (err < 0) {
return err;
}
}
if (IS_ENABLED(CONFIG_BT_GATT_CLIENT)) {
bt_gatt_clear_subscriptions(id, addr);
}
return 0;
}
void bt_gatt_disconnected(struct bt_conn *conn)
{
LOG_DBG("conn %p", conn);
bt_gatt_foreach_attr(0x0001, 0xffff, disconnected_cb, conn);
#if defined(CONFIG_BT_GATT_NOTIFY_MULTIPLE)
/* Clear pending notifications */
cleanup_notify(conn);
#endif /* CONFIG_BT_GATT_NOTIFY_MULTIPLE */
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
gatt_store_ccc_cf(conn->id, &conn->le.dst);
}
/* Make sure to clear the CCC entry when using lazy loading */
if (IS_ENABLED(CONFIG_BT_SETTINGS_CCC_LAZY_LOADING) &&
bt_addr_le_is_bonded(conn->id, &conn->le.dst)) {
struct addr_with_id addr_with_id = {
.addr = &conn->le.dst,
.id = conn->id,
};
bt_gatt_foreach_attr(0x0001, 0xffff,
remove_peer_from_attr,
&addr_with_id);
}
#if defined(CONFIG_BT_GATT_CLIENT)
remove_subscriptions(conn);
#endif /* CONFIG_BT_GATT_CLIENT */
#if defined(CONFIG_BT_GATT_CACHING)
remove_cf_cfg(conn);
#endif
}
void bt_gatt_req_set_mtu(struct bt_att_req *req, uint16_t mtu)
{
IF_ENABLED(CONFIG_BT_GATT_CLIENT, ({
if (req->func == gatt_read_rsp) {
struct bt_gatt_read_params *params = req->user_data;
__ASSERT_NO_MSG(params);
params->_att_mtu = mtu;
return;
}
}));
/* Otherwise: This request type does not have an `_att_mtu`
* params field or any other method to get this value, so we can
* just drop it here. Feel free to add this capability to other
* request types if needed.
*/
}