You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6559 lines
158 KiB
6559 lines
158 KiB
/* 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 <stdint.h> |
|
|
|
#include <zephyr/bluetooth/att.h> |
|
#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 "psa/crypto.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 "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) |
|
{ |
|
/* adding one to fit the terminating null character */ |
|
char value[CONFIG_BT_DEVICE_NAME_MAX + 1] = {}; |
|
|
|
if (offset >= CONFIG_BT_DEVICE_NAME_MAX) { |
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); |
|
} |
|
|
|
if (offset + len > CONFIG_BT_DEVICE_NAME_MAX) { |
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); |
|
} |
|
|
|
memcpy(value, buf, len); |
|
|
|
value[len] = '\0'; |
|
|
|
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 defined(CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE) |
|
#define GAP_APPEARANCE_PROPS (BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE) |
|
#if defined(CONFIG_DEVICE_APPEARANCE_GATT_WRITABLE_AUTHEN) |
|
#define GAP_APPEARANCE_PERMS (BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_AUTHEN) |
|
#elif defined(CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE_ENCRYPT) |
|
#define GAP_APPEARANCE_PERMS (BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT) |
|
#else |
|
#define GAP_APPEARANCE_PERMS (BT_GATT_PERM_READ | BT_GATT_PERM_WRITE) |
|
#endif |
|
#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, |
|
¢ral_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_le_bond_exists(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_le_bond_exists(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_managed_user_data sc_ccc = |
|
BT_GATT_CCC_MANAGED_USER_DATA_INIT(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 octet */ |
|
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 % BITS_PER_BYTE); |
|
} 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 { |
|
psa_mac_operation_t operation; |
|
psa_key_id_t key; |
|
int err; |
|
}; |
|
|
|
static int db_hash_setup(struct gen_hash_state *state, uint8_t *key) |
|
{ |
|
psa_key_attributes_t key_attr = PSA_KEY_ATTRIBUTES_INIT; |
|
psa_status_t ret; |
|
|
|
psa_set_key_type(&key_attr, PSA_KEY_TYPE_AES); |
|
psa_set_key_bits(&key_attr, 128); |
|
psa_set_key_usage_flags(&key_attr, PSA_KEY_USAGE_SIGN_MESSAGE); |
|
psa_set_key_algorithm(&key_attr, PSA_ALG_CMAC); |
|
|
|
ret = psa_import_key(&key_attr, key, 16, &(state->key)); |
|
if (ret != PSA_SUCCESS) { |
|
LOG_ERR("Unable to import the key for AES CMAC %d", ret); |
|
return -EIO; |
|
} |
|
memset(&state->operation, 0, sizeof(state->operation)); |
|
|
|
ret = psa_mac_sign_setup(&(state->operation), state->key, PSA_ALG_CMAC); |
|
if (ret != PSA_SUCCESS) { |
|
LOG_ERR("CMAC operation init failed %d", ret); |
|
return -EIO; |
|
} |
|
return 0; |
|
} |
|
|
|
static int db_hash_update(struct gen_hash_state *state, uint8_t *data, size_t len) |
|
{ |
|
psa_status_t ret = psa_mac_update(&(state->operation), data, len); |
|
|
|
if (ret != PSA_SUCCESS) { |
|
LOG_ERR("CMAC update failed %d", ret); |
|
return -EIO; |
|
} |
|
return 0; |
|
} |
|
|
|
static int db_hash_finish(struct gen_hash_state *state) |
|
{ |
|
size_t mac_length; |
|
psa_status_t ret = psa_mac_sign_finish(&(state->operation), db_hash.hash, 16, &mac_length); |
|
|
|
if (ret != PSA_SUCCESS) { |
|
LOG_ERR("CMAC finish failed %d", ret); |
|
return -EIO; |
|
} |
|
return 0; |
|
} |
|
|
|
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 (db_hash_update(state, (uint8_t *)&value, |
|
sizeof(handle)) != 0) { |
|
state->err = -EINVAL; |
|
return BT_GATT_ITER_STOP; |
|
} |
|
|
|
value = sys_cpu_to_le16(u16->val); |
|
if (db_hash_update(state, (uint8_t *)&value, |
|
sizeof(u16->val)) != 0) { |
|
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 (db_hash_update(state, data, len) != 0) { |
|
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 (db_hash_update(state, (uint8_t *)&value, |
|
sizeof(handle)) != 0) { |
|
state->err = -EINVAL; |
|
return BT_GATT_ITER_STOP; |
|
} |
|
|
|
value = sys_cpu_to_le16(u16->val); |
|
if (db_hash_update(state, (uint8_t *)&value, |
|
sizeof(u16->val)) != 0) { |
|
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 gen_hash_state state; |
|
|
|
if (db_hash_setup(&state, key) != 0) { |
|
return; |
|
} |
|
|
|
bt_gatt_foreach_attr(0x0001, 0xffff, gen_hash_m, &state); |
|
|
|
if (db_hash_finish(&state) != 0) { |
|
return; |
|
} |
|
|
|
/** |
|
* Core 5.1 does not state the endianness 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 endianness as well. bt_smp_aes_cmac calculates the hash in |
|
* big endianness 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_le_bond_exists(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; |
|
|
|
} |
|
|
|
static bool is_host_managed_ccc(const struct bt_gatt_attr *attr) |
|
{ |
|
return (attr->write == bt_gatt_attr_write_ccc); |
|
} |
|
|
|
#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_managed_user_data *ccc; |
|
struct addr_match *match = user_data; |
|
|
|
if (!is_host_managed_ccc(attr)) { |
|
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_le_bond_exists(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--) { |
|
attrs->_auto_assigned_handle = 0; |
|
if (!attrs->handle) { |
|
/* Allocate handle if not set already */ |
|
attrs->handle = ++handle; |
|
attrs->_auto_assigned_handle = 1; |
|
} 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_le_bond_exists(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_le_bond_exists(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_managed_user_data *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_le_bond_exists(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 (is_host_managed_ccc(attr)) { |
|
gatt_unregister_ccc(attr->user_data); |
|
} |
|
|
|
/* The stack should not clear any handles set by the user. */ |
|
if (attr->_auto_assigned_handle) { |
|
attr->handle = 0; |
|
attr->_auto_assigned_handle = 0; |
|
} |
|
} |
|
|
|
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) |
|
{ |
|
uint16_t sc_start_handle; |
|
uint16_t sc_end_handle; |
|
int err; |
|
|
|
__ASSERT(svc, "invalid parameters\n"); |
|
|
|
/* gatt_unregister() clears handles when those were auto-assigned |
|
* by host |
|
*/ |
|
sc_start_handle = svc->attrs[0].handle; |
|
sc_end_handle = svc->attrs[svc->attr_count - 1].handle; |
|
|
|
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(sc_start_handle, sc_end_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); |
|
} |
|
|
|
if (value_len != 0U && value == NULL) { |
|
LOG_WRN("value_len of %u provided for NULL value", value_len); |
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); |
|
} |
|
|
|
if (value_len == 0U) { |
|
len = 0U; |
|
} else { |
|
len = MIN(buf_len, value_len - offset); |
|
memcpy(buf, (uint8_t *)value + offset, len); |
|
} |
|
|
|
LOG_DBG("handle 0x%04x offset %u length %u", attr->handle, 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; |
|
} __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_managed_user_data *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_managed_user_data *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_managed_user_data *ccc) |
|
{ |
|
int i; |
|
uint16_t value = 0x0000; |
|
|
|
for (i = 0; i < ARRAY_SIZE(ccc->cfg); i++) { |
|
/* `ccc->value` shall be a summary of connected peers' CCC values, but |
|
* `ccc->cfg` can contain entries for bonded but not connected peers. |
|
*/ |
|
struct bt_conn *conn = bt_conn_lookup_addr_le(ccc->cfg[i].id, &ccc->cfg[i].peer); |
|
|
|
if (conn) { |
|
if (ccc->cfg[i].value > value) { |
|
value = ccc->cfg[i].value; |
|
} |
|
|
|
bt_conn_unref(conn); |
|
} |
|
} |
|
|
|
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_managed_user_data *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) + params->len); |
|
nfy->handle = sys_cpu_to_le16(handle); |
|
nfy->len = sys_cpu_to_le16(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) + params->len); |
|
nfy->handle = sys_cpu_to_le16(handle); |
|
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) + params->len); |
|
ind->handle = sys_cpu_to_le16(handle); |
|
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_managed_user_data *ccc; |
|
size_t i; |
|
|
|
if (!is_host_managed_ccc(attr)) { |
|
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_get_handle(attr); |
|
uint16_t end_handle = start_handle && attr_count |
|
? MIN(start_handle + attr_count, BT_ATT_LAST_ATTRIBUTE_HANDLE) |
|
: BT_ATT_LAST_ATTRIBUTE_HANDLE; |
|
|
|
if (attr != NULL && start_handle == 0U) { |
|
/* If start_handle is 0 then `attr` is not in our database, and should not be used |
|
* as a starting point for the search |
|
*/ |
|
LOG_DBG("Could not find handle of attr %p", attr); |
|
return NULL; |
|
} |
|
|
|
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, ¶ms[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); |
|
} |
|
|
|
uint16_t bt_gatt_get_uatt_mtu(struct bt_conn *conn) |
|
{ |
|
return bt_att_get_uatt_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_managed_user_data *ccc; |
|
size_t i; |
|
uint8_t err; |
|
|
|
if (!is_host_managed_ccc(attr)) { |
|
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_managed_user_data *ccc; |
|
bool value_used; |
|
size_t i; |
|
|
|
if (!is_host_managed_ccc(attr)) { |
|
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_le_bond_exists(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 reset", 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) |
|
{ |
|
uint16_t ccc_bits; |
|
uint8_t ccc_bits_encoded[sizeof(ccc_bits)]; |
|
ssize_t len; |
|
|
|
__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)) { |
|
uint8_t properties; |
|
|
|
if (!attr->read) { |
|
LOG_ERR("Read method not set"); |
|
return false; |
|
} |
|
/* The charactestic properties is the first byte of the attribute value */ |
|
len = attr->read(NULL, attr, &properties, sizeof(properties), 0); |
|
if (len < 0) { |
|
LOG_ERR("Failed to read attribute %p (err %zd)", attr, len); |
|
return false; |
|
} else if (len != sizeof(properties)) { |
|
LOG_ERR("Invalid read length: %zd", len); |
|
return false; |
|
} |
|
|
|
if (!(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; |
|
} |
|
|
|
if (!attr->read) { |
|
LOG_ERR("Read method not set"); |
|
return false; |
|
} |
|
|
|
len = attr->read(conn, attr, ccc_bits_encoded, sizeof(ccc_bits_encoded), 0); |
|
if (len < 0) { |
|
LOG_ERR("Failed to read attribute %p (err %zd)", attr, len); |
|
return false; |
|
} else if (len != sizeof(ccc_bits_encoded)) { |
|
LOG_ERR("Invalid read length: %zd", len); |
|
return false; |
|
} |
|
|
|
ccc_bits = sys_get_le16(ccc_bits_encoded); |
|
|
|
/* Check if the CCC bits match the subscription type */ |
|
if (ccc_bits & ccc_type) { |
|
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, ¶ms->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; |
|
} |
|
|
|
static void call_notify_cb_and_maybe_unsubscribe(struct bt_conn *conn, struct gatt_sub *sub, |
|
uint16_t handle, const void *data, uint16_t length) |
|
{ |
|
struct bt_gatt_subscribe_params *params, *tmp; |
|
int err; |
|
|
|
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) { |
|
err = bt_gatt_unsubscribe(conn, params); |
|
if (err != 0) { |
|
LOG_WRN("Failed to unsubscribe (err %d)", err); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
void bt_gatt_notification(struct bt_conn *conn, uint16_t handle, |
|
const void *data, uint16_t length) |
|
{ |
|
struct gatt_sub *sub; |
|
|
|
LOG_DBG("handle 0x%04x length %u", handle, length); |
|
|
|
sub = gatt_sub_find(conn); |
|
if (!sub) { |
|
return; |
|
} |
|
|
|
call_notify_cb_and_maybe_unsubscribe(conn, sub, handle, data, length); |
|
} |
|
|
|
void bt_gatt_mult_notification(struct bt_conn *conn, const void *data, |
|
uint16_t length) |
|
{ |
|
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; |
|
} |
|
|
|
call_notify_cb_and_maybe_unsubscribe(conn, sub, handle, nfy->value, len); |
|
|
|
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_le_bond_exists(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 = ¶ms->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; |
|
uint16_t mtu = bt_att_get_mtu(conn); |
|
|
|
req_len = sizeof(struct bt_att_prepare_write_req); |
|
|
|
/** MTU size is bigger than the ATT_PREPARE_WRITE_REQ header (5 bytes), |
|
* unless there's no connection. |
|
*/ |
|
if (mtu == 0) { |
|
return -ENOTCONN; |
|
} |
|
|
|
len = mtu - 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 == ¶ms->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); |
|
} |
|
} |
|
|
|
|
|
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 == BT_GATT_AUTO_DISCOVER_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, ¶ms->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, ¶ms->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, ¶ms->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_le_bond_exists(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 */ |
|
|
|
#if defined(CONFIG_BT_SETTINGS_CCC_STORE_MAX) |
|
#define CCC_STORE_MAX CONFIG_BT_SETTINGS_CCC_STORE_MAX |
|
#else /* defined(CONFIG_BT_SETTINGS_CCC_STORE_MAX) */ |
|
#define CCC_STORE_MAX 0 |
|
#endif /* defined(CONFIG_BT_SETTINGS_CCC_STORE_MAX) */ |
|
|
|
static struct bt_gatt_ccc_cfg *ccc_find_cfg(struct bt_gatt_ccc_managed_user_data *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_managed_user_data *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_managed_user_data *ccc; |
|
struct bt_gatt_ccc_cfg *cfg; |
|
|
|
if (!is_host_managed_ccc(attr)) { |
|
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; |
|
} |
|
|
|
#ifdef CONFIG_BT_SETTINGS |
|
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); |
|
#endif /* CONFIG_BT_SETTINGS */ |
|
|
|
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_le_bond_exists(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_managed_user_data *ccc; |
|
struct bt_gatt_ccc_cfg *cfg; |
|
|
|
if (!is_host_managed_ccc(attr)) { |
|
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); |
|
|
|
CHECKIF(save->count >= CCC_STORE_MAX) { |
|
LOG_ERR("Too many Client Characteristic Configuration. " |
|
"See CONFIG_BT_SETTINGS_CCC_STORE_MAX\n"); |
|
return BT_GATT_ITER_STOP; |
|
} |
|
|
|
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_managed_user_data *ccc; |
|
struct bt_gatt_ccc_cfg *cfg; |
|
|
|
if (!is_host_managed_ccc(attr)) { |
|
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_cf(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_le_bond_exists(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. |
|
*/ |
|
}
|
|
|