bt_gatt_find_by_uuid used bt_gatt_attr_value_handle but that function only works to get the value handle of a characteristic declaration, i.e. if the UUID is not BT_UUID_GATT_CHRC then it would always return handle = 0. This meant that bt_gatt_find_by_uuid would always use handle = 0 as the starting handle for non-BT_UUID_GATT_CHRC attributes, instead of the handle of the provided attr. This was not an issue for any UUIDs that may only exist once on a GATT server, which is most UUIDs, but for UUIDs like the BT_UUID_TBS_* UUIDs that may be multiple instances of, it would always return the first attribute rather than the one starting from the provided start attr. This commit also ensures that we do not overflow the `end_handle` when adding 2 uint16_t values. Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
6545 lines
158 KiB
C
6545 lines
158 KiB
C
/* 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)
|
|
{
|
|
char value[CONFIG_BT_DEVICE_NAME_MAX] = {};
|
|
|
|
if (offset >= sizeof(value)) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
|
}
|
|
|
|
if (offset + len >= sizeof(value)) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
}
|
|
|
|
memcpy(value, buf, len);
|
|
|
|
bt_set_name(value);
|
|
|
|
return len;
|
|
}
|
|
|
|
#endif /* CONFIG_BT_DEVICE_NAME_GATT_WRITABLE */
|
|
|
|
static ssize_t read_appearance(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr, void *buf,
|
|
uint16_t len, uint16_t offset)
|
|
{
|
|
uint16_t appearance = sys_cpu_to_le16(bt_get_appearance());
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, &appearance,
|
|
sizeof(appearance));
|
|
}
|
|
|
|
#if defined(CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE)
|
|
static ssize_t write_appearance(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
|
const void *buf, uint16_t len, uint16_t offset,
|
|
uint8_t flags)
|
|
{
|
|
uint16_t appearance_le = sys_cpu_to_le16(bt_get_appearance());
|
|
char * const appearance_le_bytes = (char *)&appearance_le;
|
|
uint16_t appearance;
|
|
int err;
|
|
|
|
if (offset >= sizeof(appearance_le)) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
|
}
|
|
|
|
if ((offset + len) > sizeof(appearance_le)) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
}
|
|
|
|
memcpy(&appearance_le_bytes[offset], buf, len);
|
|
appearance = sys_le16_to_cpu(appearance_le);
|
|
|
|
err = bt_set_appearance(appearance);
|
|
|
|
if (err) {
|
|
return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
|
|
}
|
|
|
|
return len;
|
|
}
|
|
#endif /* CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE */
|
|
|
|
#if 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_addr_le_is_bonded(conn->id, &conn->le.dst)) {
|
|
int err;
|
|
|
|
err = bt_gatt_clear_sc(conn->id, &conn->le.dst);
|
|
if (err) {
|
|
LOG_ERR("Failed to clear SC %d", err);
|
|
}
|
|
} else {
|
|
struct gatt_sc_cfg *cfg;
|
|
|
|
cfg = find_sc_cfg(conn->id, &conn->le.dst);
|
|
if (cfg) {
|
|
clear_sc_cfg(cfg);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void sc_reset(struct gatt_sc_cfg *cfg)
|
|
{
|
|
LOG_DBG("peer %s", bt_addr_le_str(&cfg->peer));
|
|
|
|
memset(&cfg->data, 0, sizeof(cfg->data));
|
|
|
|
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
|
|
sc_store(cfg);
|
|
}
|
|
}
|
|
|
|
static bool update_range(uint16_t *start, uint16_t *end, uint16_t new_start,
|
|
uint16_t new_end)
|
|
{
|
|
LOG_DBG("start 0x%04x end 0x%04x new_start 0x%04x new_end 0x%04x", *start, *end, new_start,
|
|
new_end);
|
|
|
|
/* Check if inside existing range */
|
|
if (new_start >= *start && new_end <= *end) {
|
|
return false;
|
|
}
|
|
|
|
/* Update range */
|
|
if (*start > new_start) {
|
|
*start = new_start;
|
|
}
|
|
|
|
if (*end < new_end) {
|
|
*end = new_end;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void sc_save(uint8_t id, bt_addr_le_t *peer, uint16_t start, uint16_t end)
|
|
{
|
|
struct gatt_sc_cfg *cfg;
|
|
bool modified = false;
|
|
|
|
LOG_DBG("peer %s start 0x%04x end 0x%04x", bt_addr_le_str(peer), start, end);
|
|
|
|
cfg = find_sc_cfg(id, peer);
|
|
if (!cfg) {
|
|
/* Find and initialize a free sc_cfg entry */
|
|
cfg = find_sc_cfg(BT_ID_DEFAULT, BT_ADDR_LE_ANY);
|
|
if (!cfg) {
|
|
LOG_ERR("unable to save SC: no cfg left");
|
|
return;
|
|
}
|
|
|
|
cfg->id = id;
|
|
bt_addr_le_copy(&cfg->peer, peer);
|
|
}
|
|
|
|
/* Check if there is any change stored */
|
|
if (!(cfg->data.start || cfg->data.end)) {
|
|
cfg->data.start = start;
|
|
cfg->data.end = end;
|
|
modified = true;
|
|
goto done;
|
|
}
|
|
|
|
modified = update_range(&cfg->data.start, &cfg->data.end, start, end);
|
|
|
|
done:
|
|
if (IS_ENABLED(CONFIG_BT_SETTINGS) &&
|
|
modified && bt_addr_le_is_bonded(cfg->id, &cfg->peer)) {
|
|
sc_store(cfg);
|
|
}
|
|
}
|
|
|
|
static ssize_t sc_ccc_cfg_write(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr, uint16_t value)
|
|
{
|
|
LOG_DBG("value 0x%04x", value);
|
|
|
|
if (value == BT_GATT_CCC_INDICATE) {
|
|
/* Create a new SC configuration entry if subscribed */
|
|
sc_save(conn->id, &conn->le.dst, 0, 0);
|
|
} else {
|
|
sc_clear(conn);
|
|
}
|
|
|
|
return sizeof(value);
|
|
}
|
|
|
|
static struct _bt_gatt_ccc sc_ccc = BT_GATT_CCC_INITIALIZER(NULL,
|
|
sc_ccc_cfg_write,
|
|
NULL);
|
|
|
|
/* Do not shuffle the values in this enum, they are used as bit offsets when
|
|
* saving the CF flags to NVS (i.e. NVS persists between FW upgrades).
|
|
*/
|
|
enum {
|
|
CF_CHANGE_AWARE, /* Client is changed aware */
|
|
CF_DB_HASH_READ, /* The client has read the database hash */
|
|
|
|
/* Total number of flags - must be at the end of the enum */
|
|
CF_NUM_FLAGS,
|
|
};
|
|
|
|
#define CF_BIT_ROBUST_CACHING 0
|
|
#define CF_BIT_EATT 1
|
|
#define CF_BIT_NOTIFY_MULTI 2
|
|
#define CF_BIT_LAST CF_BIT_NOTIFY_MULTI
|
|
|
|
#define CF_NUM_BITS (CF_BIT_LAST + 1)
|
|
#define CF_NUM_BYTES ((CF_BIT_LAST / 8) + 1)
|
|
#define CF_FLAGS_STORE_LEN 1
|
|
|
|
#define CF_ROBUST_CACHING(_cfg) (_cfg->data[0] & BIT(CF_BIT_ROBUST_CACHING))
|
|
#define CF_EATT(_cfg) (_cfg->data[0] & BIT(CF_BIT_EATT))
|
|
#define CF_NOTIFY_MULTI(_cfg) (_cfg->data[0] & BIT(CF_BIT_NOTIFY_MULTI))
|
|
|
|
struct gatt_cf_cfg {
|
|
uint8_t id;
|
|
bt_addr_le_t peer;
|
|
uint8_t data[CF_NUM_BYTES];
|
|
ATOMIC_DEFINE(flags, CF_NUM_FLAGS);
|
|
};
|
|
|
|
#if defined(CONFIG_BT_GATT_CACHING)
|
|
#define CF_CFG_MAX (CONFIG_BT_MAX_PAIRED + CONFIG_BT_MAX_CONN)
|
|
#else
|
|
#define CF_CFG_MAX 0
|
|
#endif /* CONFIG_BT_GATT_CACHING */
|
|
|
|
static struct gatt_cf_cfg cf_cfg[CF_CFG_MAX] = {};
|
|
|
|
static void clear_cf_cfg(struct gatt_cf_cfg *cfg)
|
|
{
|
|
bt_addr_le_copy(&cfg->peer, BT_ADDR_LE_ANY);
|
|
memset(cfg->data, 0, sizeof(cfg->data));
|
|
atomic_set(cfg->flags, 0);
|
|
}
|
|
|
|
enum delayed_store_flags {
|
|
DELAYED_STORE_CCC,
|
|
DELAYED_STORE_CF,
|
|
DELAYED_STORE_NUM_FLAGS
|
|
};
|
|
|
|
#if defined(CONFIG_BT_SETTINGS_DELAYED_STORE)
|
|
static void gatt_delayed_store_enqueue(uint8_t id, const bt_addr_le_t *peer_addr,
|
|
enum delayed_store_flags flag);
|
|
#endif
|
|
|
|
#if defined(CONFIG_BT_GATT_CACHING)
|
|
static bool set_change_aware_no_store(struct gatt_cf_cfg *cfg, bool aware)
|
|
{
|
|
bool changed;
|
|
|
|
if (aware) {
|
|
changed = !atomic_test_and_set_bit(cfg->flags, CF_CHANGE_AWARE);
|
|
} else {
|
|
changed = atomic_test_and_clear_bit(cfg->flags, CF_CHANGE_AWARE);
|
|
}
|
|
|
|
LOG_DBG("peer is now change-%saware", aware ? "" : "un");
|
|
|
|
return changed;
|
|
}
|
|
|
|
static void set_change_aware(struct gatt_cf_cfg *cfg, bool aware)
|
|
{
|
|
bool changed = set_change_aware_no_store(cfg, aware);
|
|
|
|
#if defined(CONFIG_BT_SETTINGS_DELAYED_STORE)
|
|
if (changed) {
|
|
gatt_delayed_store_enqueue(cfg->id, &cfg->peer, DELAYED_STORE_CF);
|
|
}
|
|
#else
|
|
(void)changed;
|
|
#endif
|
|
}
|
|
|
|
static int bt_gatt_store_cf(uint8_t id, const bt_addr_le_t *peer);
|
|
|
|
static void set_all_change_unaware(void)
|
|
{
|
|
#if defined(CONFIG_BT_SETTINGS)
|
|
/* Mark all bonded peers as change-unaware.
|
|
* - Can be called when not in a connection with said peers
|
|
* - Doesn't have any effect when no bonds are in memory. This is the
|
|
* case when the device has just booted and `settings_load` hasn't yet
|
|
* been called.
|
|
* - Expensive to call, as it will write the new status to settings
|
|
* right away.
|
|
*/
|
|
for (size_t i = 0; i < ARRAY_SIZE(cf_cfg); i++) {
|
|
struct gatt_cf_cfg *cfg = &cf_cfg[i];
|
|
|
|
if (!bt_addr_le_eq(&cfg->peer, BT_ADDR_LE_ANY)) {
|
|
set_change_aware_no_store(cfg, false);
|
|
bt_gatt_store_cf(cfg->id, &cfg->peer);
|
|
}
|
|
}
|
|
#endif /* CONFIG_BT_SETTINGS */
|
|
}
|
|
|
|
static struct gatt_cf_cfg *find_cf_cfg(struct bt_conn *conn)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(cf_cfg); i++) {
|
|
struct gatt_cf_cfg *cfg = &cf_cfg[i];
|
|
|
|
if (!conn) {
|
|
if (bt_addr_le_eq(&cfg->peer, BT_ADDR_LE_ANY)) {
|
|
return cfg;
|
|
}
|
|
} else if (bt_conn_is_peer_addr_le(conn, cfg->id, &cfg->peer)) {
|
|
return cfg;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static ssize_t cf_read(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
|
void *buf, uint16_t len, uint16_t offset)
|
|
{
|
|
struct gatt_cf_cfg *cfg;
|
|
uint8_t data[1] = {};
|
|
|
|
cfg = find_cf_cfg(conn);
|
|
if (cfg) {
|
|
memcpy(data, cfg->data, sizeof(data));
|
|
}
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, data,
|
|
sizeof(data));
|
|
}
|
|
|
|
static bool cf_set_value(struct gatt_cf_cfg *cfg, const uint8_t *value, uint16_t len)
|
|
{
|
|
uint16_t i;
|
|
|
|
/* Validate the bits */
|
|
for (i = 0U; i <= CF_BIT_LAST && (i / 8) < len; i++) {
|
|
if ((cfg->data[i / 8] & BIT(i % 8)) &&
|
|
!(value[i / 8] & BIT(i % 8))) {
|
|
/* A client shall never clear a bit it has set */
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Set the bits for each 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_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);
|
|
|
|
if (psa_import_key(&key_attr, key, 16, &(state->key)) != PSA_SUCCESS) {
|
|
LOG_ERR("Unable to import the key for AES CMAC");
|
|
return -EIO;
|
|
}
|
|
state->operation = psa_mac_operation_init();
|
|
if (psa_mac_sign_setup(&(state->operation), state->key,
|
|
PSA_ALG_CMAC) != PSA_SUCCESS) {
|
|
LOG_ERR("CMAC operation init failed");
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int db_hash_update(struct gen_hash_state *state, uint8_t *data, size_t len)
|
|
{
|
|
if (psa_mac_update(&(state->operation), data, len) != PSA_SUCCESS) {
|
|
LOG_ERR("CMAC update failed");
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int db_hash_finish(struct gen_hash_state *state)
|
|
{
|
|
size_t mac_length;
|
|
|
|
if (psa_mac_sign_finish(&(state->operation), db_hash.hash, 16,
|
|
&mac_length) != PSA_SUCCESS) {
|
|
LOG_ERR("CMAC finish failed");
|
|
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_addr_le_is_bonded(conn->id, &conn->le.dst)) {
|
|
clear_cf_cfg(cfg);
|
|
} else {
|
|
/* Update address in case it has changed */
|
|
bt_addr_le_copy(&cfg->peer, &conn->le.dst);
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_BT_EATT)
|
|
#define SF_BIT_EATT 0
|
|
#define SF_BIT_LAST SF_BIT_EATT
|
|
|
|
static ssize_t sf_read(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
|
void *buf, uint16_t len, uint16_t offset)
|
|
{
|
|
uint8_t value = BIT(SF_BIT_EATT);
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, &value,
|
|
sizeof(value));
|
|
}
|
|
#endif /* CONFIG_BT_EATT */
|
|
#endif /* CONFIG_BT_GATT_CACHING */
|
|
|
|
static struct gatt_cf_cfg *find_cf_cfg_by_addr(uint8_t id,
|
|
const bt_addr_le_t *addr);
|
|
|
|
static int bt_gatt_store_cf(uint8_t id, const bt_addr_le_t *peer)
|
|
{
|
|
#if defined(CONFIG_BT_GATT_CACHING)
|
|
struct gatt_cf_cfg *cfg;
|
|
char dst[CF_NUM_BYTES + CF_FLAGS_STORE_LEN];
|
|
char *str;
|
|
size_t len;
|
|
int err;
|
|
|
|
cfg = find_cf_cfg_by_addr(id, peer);
|
|
if (!cfg) {
|
|
/* No cfg found, just clear it */
|
|
LOG_DBG("No config for CF");
|
|
str = NULL;
|
|
len = 0;
|
|
} else {
|
|
str = (char *)cfg->data;
|
|
len = sizeof(cfg->data);
|
|
|
|
/* add the CF data to a temp array */
|
|
memcpy(dst, str, len);
|
|
|
|
/* add the change-aware flag */
|
|
bool is_change_aware = atomic_test_bit(cfg->flags, CF_CHANGE_AWARE);
|
|
|
|
dst[len] = 0;
|
|
WRITE_BIT(dst[len], CF_CHANGE_AWARE, is_change_aware);
|
|
len += CF_FLAGS_STORE_LEN;
|
|
|
|
str = dst;
|
|
}
|
|
|
|
err = bt_settings_store_cf(id, peer, str, len);
|
|
if (err) {
|
|
LOG_ERR("Failed to store Client Features (err %d)", err);
|
|
return err;
|
|
}
|
|
|
|
LOG_DBG("Stored CF for %s", bt_addr_le_str(peer));
|
|
LOG_HEXDUMP_DBG(str, len, "Saved data");
|
|
#endif /* CONFIG_BT_GATT_CACHING */
|
|
return 0;
|
|
|
|
}
|
|
|
|
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 *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_addr_le_is_bonded(conn->id, &conn->le.dst);
|
|
|
|
bt_gatt_foreach_attr(0x0001, 0xffff, convert_to_id_on_match, &user_data);
|
|
|
|
/* Store the ccc */
|
|
if (is_bonded) {
|
|
bt_gatt_store_ccc(conn->id, &conn->le.dst);
|
|
}
|
|
|
|
/* Update the cf addresses and store it if we get a match */
|
|
struct gatt_cf_cfg *cfg = find_cf_cfg_by_addr(conn->id, private_addr);
|
|
|
|
if (cfg) {
|
|
bt_addr_le_copy(&cfg->peer, id_addr);
|
|
if (is_bonded) {
|
|
bt_gatt_store_cf(conn->id, &conn->le.dst);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void bt_gatt_pairing_complete(struct bt_conn *conn, bool bonded)
|
|
{
|
|
if (bonded) {
|
|
/* Store the ccc and cf data */
|
|
bt_gatt_store_ccc(conn->id, &(conn->le.dst));
|
|
bt_gatt_store_cf(conn->id, &conn->le.dst);
|
|
}
|
|
}
|
|
#endif /* CONFIG_BT_SETTINGS && CONFIG_BT_SMP */
|
|
|
|
BT_GATT_SERVICE_DEFINE(_1_gatt_svc,
|
|
BT_GATT_PRIMARY_SERVICE(BT_UUID_GATT),
|
|
#if defined(CONFIG_BT_GATT_SERVICE_CHANGED)
|
|
/* Bluetooth 5.0, Vol3 Part G:
|
|
* The Service Changed characteristic Attribute Handle on the server
|
|
* shall not change if the server has a trusted relationship with any
|
|
* client.
|
|
*/
|
|
BT_GATT_CHARACTERISTIC(BT_UUID_GATT_SC, BT_GATT_CHRC_INDICATE,
|
|
BT_GATT_PERM_NONE, NULL, NULL, NULL),
|
|
BT_GATT_CCC_MANAGED(&sc_ccc, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
|
|
#if defined(CONFIG_BT_GATT_CACHING)
|
|
BT_GATT_CHARACTERISTIC(BT_UUID_GATT_CLIENT_FEATURES,
|
|
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
|
|
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
|
|
cf_read, cf_write, NULL),
|
|
BT_GATT_CHARACTERISTIC(BT_UUID_GATT_DB_HASH,
|
|
BT_GATT_CHRC_READ, BT_GATT_PERM_READ,
|
|
db_hash_read, NULL, NULL),
|
|
#if defined(CONFIG_BT_EATT)
|
|
BT_GATT_CHARACTERISTIC(BT_UUID_GATT_SERVER_FEATURES,
|
|
BT_GATT_CHRC_READ, BT_GATT_PERM_READ,
|
|
sf_read, NULL, NULL),
|
|
#endif /* CONFIG_BT_EATT */
|
|
#endif /* CONFIG_BT_GATT_CACHING */
|
|
#endif /* CONFIG_BT_GATT_SERVICE_CHANGED */
|
|
);
|
|
|
|
#if defined(CONFIG_BT_GATT_DYNAMIC_DB)
|
|
static uint8_t found_attr(const struct bt_gatt_attr *attr, uint16_t handle,
|
|
void *user_data)
|
|
{
|
|
const struct bt_gatt_attr **found = user_data;
|
|
|
|
*found = attr;
|
|
|
|
return BT_GATT_ITER_STOP;
|
|
}
|
|
|
|
static const struct bt_gatt_attr *find_attr(uint16_t handle)
|
|
{
|
|
const struct bt_gatt_attr *attr = NULL;
|
|
|
|
bt_gatt_foreach_attr(handle, handle, found_attr, &attr);
|
|
|
|
return attr;
|
|
}
|
|
|
|
static void gatt_insert(struct bt_gatt_service *svc, uint16_t last_handle)
|
|
{
|
|
struct bt_gatt_service *tmp, *prev = NULL;
|
|
|
|
if (last_handle == 0 || svc->attrs[0].handle > last_handle) {
|
|
sys_slist_append(&db, &svc->node);
|
|
return;
|
|
}
|
|
|
|
/* DB shall always have its service in ascending order */
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&db, tmp, node) {
|
|
if (tmp->attrs[0].handle > svc->attrs[0].handle) {
|
|
if (prev) {
|
|
sys_slist_insert(&db, &prev->node, &svc->node);
|
|
} else {
|
|
sys_slist_prepend(&db, &svc->node);
|
|
}
|
|
return;
|
|
}
|
|
|
|
prev = tmp;
|
|
}
|
|
}
|
|
|
|
static int gatt_register(struct bt_gatt_service *svc)
|
|
{
|
|
struct bt_gatt_service *last;
|
|
uint16_t handle, last_handle;
|
|
struct bt_gatt_attr *attrs = svc->attrs;
|
|
uint16_t count = svc->attr_count;
|
|
|
|
if (sys_slist_is_empty(&db)) {
|
|
handle = last_static_handle;
|
|
last_handle = 0;
|
|
goto populate;
|
|
}
|
|
|
|
last = SYS_SLIST_PEEK_TAIL_CONTAINER(&db, last, node);
|
|
handle = last->attrs[last->attr_count - 1].handle;
|
|
last_handle = handle;
|
|
|
|
populate:
|
|
/* Populate the handles and append them to the list */
|
|
for (; attrs && count; attrs++, count--) {
|
|
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_addr_le_is_bonded(id, peer_addr);
|
|
struct ds_peer *el = gatt_delayed_store_find(id, peer_addr);
|
|
|
|
if (bonded) {
|
|
if (el == NULL) {
|
|
el = gatt_delayed_store_alloc(id, peer_addr);
|
|
__ASSERT(el != NULL, "Can't save CF / CCC to flash");
|
|
}
|
|
|
|
atomic_set_bit(el->flags, flag);
|
|
|
|
k_work_reschedule(&gatt_delayed_store.work,
|
|
K_MSEC(CONFIG_BT_SETTINGS_DELAYED_STORE_MS));
|
|
}
|
|
}
|
|
|
|
static void delayed_store(struct k_work *work)
|
|
{
|
|
struct ds_peer *el;
|
|
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
|
|
struct gatt_delayed_store *store =
|
|
CONTAINER_OF(dwork, struct gatt_delayed_store, work);
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(gatt_delayed_store.peer_list); i++) {
|
|
el = &store->peer_list[i];
|
|
|
|
gatt_store_ccc_cf(el->id, &el->peer);
|
|
}
|
|
}
|
|
#endif /* CONFIG_BT_SETTINGS_DELAYED_STORE */
|
|
|
|
static void gatt_store_ccc_cf(uint8_t id, const bt_addr_le_t *peer_addr)
|
|
{
|
|
struct ds_peer *el = gatt_delayed_store_find(id, peer_addr);
|
|
|
|
if (bt_addr_le_is_bonded(id, peer_addr)) {
|
|
if (!IS_ENABLED(CONFIG_BT_SETTINGS_CCC_STORE_ON_WRITE) ||
|
|
(IS_ENABLED(CONFIG_BT_SETTINGS_CCC_STORE_ON_WRITE) && el &&
|
|
atomic_test_and_clear_bit(el->flags, DELAYED_STORE_CCC))) {
|
|
bt_gatt_store_ccc(id, peer_addr);
|
|
}
|
|
|
|
if (!IS_ENABLED(CONFIG_BT_SETTINGS_CF_STORE_ON_WRITE) ||
|
|
(IS_ENABLED(CONFIG_BT_SETTINGS_CF_STORE_ON_WRITE) && el &&
|
|
atomic_test_and_clear_bit(el->flags, DELAYED_STORE_CF))) {
|
|
bt_gatt_store_cf(id, peer_addr);
|
|
}
|
|
|
|
if (el && atomic_get(el->flags) == 0) {
|
|
gatt_delayed_store_free(el);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void bt_gatt_service_init(void)
|
|
{
|
|
if (atomic_test_and_set_bit(gatt_flags, GATT_SERVICE_INITIALIZED)) {
|
|
return;
|
|
}
|
|
|
|
STRUCT_SECTION_FOREACH(bt_gatt_service_static, svc) {
|
|
last_static_handle += svc->attr_count;
|
|
}
|
|
}
|
|
|
|
void bt_gatt_init(void)
|
|
{
|
|
if (atomic_test_and_set_bit(gatt_flags, GATT_INITIALIZED)) {
|
|
return;
|
|
}
|
|
|
|
bt_gatt_service_init();
|
|
|
|
#if defined(CONFIG_BT_GATT_CACHING)
|
|
k_work_init_delayable(&db_hash.work, db_hash_process);
|
|
|
|
/* Submit work to Generate initial hash as there could be static
|
|
* services already in the database.
|
|
*/
|
|
if (IS_ENABLED(CONFIG_BT_LONG_WQ)) {
|
|
bt_long_wq_schedule(&db_hash.work, DB_HASH_TIMEOUT);
|
|
} else {
|
|
k_work_schedule(&db_hash.work, DB_HASH_TIMEOUT);
|
|
}
|
|
#endif /* CONFIG_BT_GATT_CACHING */
|
|
|
|
#if defined(CONFIG_BT_GATT_SERVICE_CHANGED)
|
|
k_work_init_delayable(&gatt_sc.work, sc_process);
|
|
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
|
|
/* Make sure to not send SC indications until SC
|
|
* settings are loaded
|
|
*/
|
|
atomic_set_bit(gatt_sc.flags, SC_INDICATE_PENDING);
|
|
}
|
|
#endif /* defined(CONFIG_BT_GATT_SERVICE_CHANGED) */
|
|
|
|
#if defined(CONFIG_BT_SETTINGS_DELAYED_STORE)
|
|
k_work_init_delayable(&gatt_delayed_store.work, delayed_store);
|
|
#endif
|
|
|
|
#if defined(CONFIG_BT_SETTINGS) && defined(CONFIG_BT_SMP)
|
|
static struct bt_conn_auth_info_cb gatt_conn_auth_info_cb = {
|
|
.pairing_complete = bt_gatt_pairing_complete,
|
|
};
|
|
|
|
/* Register the gatt module for authentication info callbacks so it can
|
|
* be notified when pairing has completed. This is used to enable CCC
|
|
* and CF storage on pairing complete.
|
|
*/
|
|
bt_conn_auth_info_cb_register(&gatt_conn_auth_info_cb);
|
|
|
|
static struct bt_conn_cb gatt_conn_cb = {
|
|
.identity_resolved = bt_gatt_identity_resolved,
|
|
};
|
|
|
|
/* Also update the address of CCC or CF writes that happened before the
|
|
* identity resolution. Note that to increase security in the future, we
|
|
* might want to explicitly not do this and treat a bonded device as a
|
|
* brand-new peer.
|
|
*/
|
|
bt_conn_cb_register(&gatt_conn_cb);
|
|
#endif /* CONFIG_BT_SETTINGS && CONFIG_BT_SMP */
|
|
}
|
|
|
|
static void sc_indicate(uint16_t start, uint16_t end)
|
|
{
|
|
#if defined(CONFIG_BT_GATT_DYNAMIC_DB) || \
|
|
(defined(CONFIG_BT_GATT_CACHING) && defined(CONFIG_BT_SETTINGS))
|
|
LOG_DBG("start 0x%04x end 0x%04x", start, end);
|
|
|
|
if (!atomic_test_and_set_bit(gatt_sc.flags, SC_RANGE_CHANGED)) {
|
|
gatt_sc.start = start;
|
|
gatt_sc.end = end;
|
|
goto submit;
|
|
}
|
|
|
|
if (!update_range(&gatt_sc.start, &gatt_sc.end, start, end)) {
|
|
return;
|
|
}
|
|
|
|
submit:
|
|
if (atomic_test_bit(gatt_sc.flags, SC_INDICATE_PENDING)) {
|
|
LOG_DBG("indicate pending, waiting until complete...");
|
|
return;
|
|
}
|
|
|
|
/* Reschedule since the range has changed */
|
|
sc_work_submit(SC_TIMEOUT);
|
|
#endif /* BT_GATT_DYNAMIC_DB || (BT_GATT_CACHING && BT_SETTINGS) */
|
|
}
|
|
|
|
void bt_gatt_cb_register(struct bt_gatt_cb *cb)
|
|
{
|
|
sys_slist_append(&callback_list, &cb->node);
|
|
}
|
|
|
|
#if defined(CONFIG_BT_GATT_DYNAMIC_DB)
|
|
static void db_changed(void)
|
|
{
|
|
#if defined(CONFIG_BT_GATT_CACHING)
|
|
struct bt_conn *conn;
|
|
int i;
|
|
|
|
atomic_clear_bit(gatt_sc.flags, DB_HASH_VALID);
|
|
|
|
if (IS_ENABLED(CONFIG_BT_LONG_WQ)) {
|
|
bt_long_wq_reschedule(&db_hash.work, DB_HASH_TIMEOUT);
|
|
} else {
|
|
k_work_reschedule(&db_hash.work, DB_HASH_TIMEOUT);
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(cf_cfg); i++) {
|
|
struct gatt_cf_cfg *cfg = &cf_cfg[i];
|
|
|
|
if (bt_addr_le_eq(&cfg->peer, BT_ADDR_LE_ANY)) {
|
|
continue;
|
|
}
|
|
|
|
if (CF_ROBUST_CACHING(cfg)) {
|
|
/* Core Spec 5.1 | Vol 3, Part G, 2.5.2.1 Robust Caching
|
|
*... the database changes again before the client
|
|
* becomes change-aware in which case the error response
|
|
* shall be sent again.
|
|
*/
|
|
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cfg->peer);
|
|
if (conn) {
|
|
bt_att_clear_out_of_sync_sent(conn);
|
|
bt_conn_unref(conn);
|
|
}
|
|
|
|
atomic_clear_bit(cfg->flags, CF_DB_HASH_READ);
|
|
set_change_aware(cfg, false);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void gatt_unregister_ccc(struct _bt_gatt_ccc *ccc)
|
|
{
|
|
ccc->value = 0;
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(ccc->cfg); i++) {
|
|
struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i];
|
|
|
|
if (!bt_addr_le_eq(&cfg->peer, BT_ADDR_LE_ANY)) {
|
|
struct bt_conn *conn;
|
|
bool store = true;
|
|
|
|
conn = bt_conn_lookup_addr_le(cfg->id, &cfg->peer);
|
|
if (conn) {
|
|
if (conn->state == BT_CONN_CONNECTED) {
|
|
#if defined(CONFIG_BT_SETTINGS_CCC_STORE_ON_WRITE)
|
|
gatt_delayed_store_enqueue(conn->id,
|
|
&conn->le.dst,
|
|
DELAYED_STORE_CCC);
|
|
#endif
|
|
store = false;
|
|
}
|
|
|
|
bt_conn_unref(conn);
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_SETTINGS) && store &&
|
|
bt_addr_le_is_bonded(cfg->id, &cfg->peer)) {
|
|
bt_gatt_store_ccc(cfg->id, &cfg->peer);
|
|
}
|
|
|
|
clear_ccc_cfg(cfg);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int gatt_unregister(struct bt_gatt_service *svc)
|
|
{
|
|
if (!sys_slist_find_and_remove(&db, &svc->node)) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
for (uint16_t i = 0; i < svc->attr_count; i++) {
|
|
struct bt_gatt_attr *attr = &svc->attrs[i];
|
|
|
|
if (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 *ccc)
|
|
{
|
|
for (size_t i = 0; i < ARRAY_SIZE(ccc->cfg); i++) {
|
|
struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i];
|
|
|
|
if (conn) {
|
|
if (bt_conn_is_peer_addr_le(conn, cfg->id,
|
|
&cfg->peer)) {
|
|
return cfg;
|
|
}
|
|
} else if (bt_addr_le_eq(&cfg->peer, BT_ADDR_LE_ANY)) {
|
|
return cfg;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
ssize_t bt_gatt_attr_read_ccc(struct bt_conn *conn,
|
|
const struct bt_gatt_attr *attr, void *buf,
|
|
uint16_t len, uint16_t offset)
|
|
{
|
|
struct _bt_gatt_ccc *ccc = attr->user_data;
|
|
const struct bt_gatt_ccc_cfg *cfg;
|
|
uint16_t value;
|
|
|
|
cfg = find_ccc_cfg(conn, ccc);
|
|
if (cfg) {
|
|
value = sys_cpu_to_le16(cfg->value);
|
|
} else {
|
|
/* Default to disable if there is no cfg for the peer */
|
|
value = 0x0000;
|
|
}
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, &value,
|
|
sizeof(value));
|
|
}
|
|
|
|
static void gatt_ccc_changed(const struct bt_gatt_attr *attr,
|
|
struct _bt_gatt_ccc *ccc)
|
|
{
|
|
int i;
|
|
uint16_t value = 0x0000;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ccc->cfg); i++) {
|
|
/* `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 *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 *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 *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 *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_addr_le_is_bonded(conn->id, &conn->le.dst)) {
|
|
if (ccc == &sc_ccc) {
|
|
sc_clear(conn);
|
|
}
|
|
|
|
clear_ccc_cfg(cfg);
|
|
} else {
|
|
/* Update address in case it has changed */
|
|
bt_addr_le_copy(&cfg->peer, &conn->le.dst);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If all values are now disabled, reset value while disconnected */
|
|
if (!value_used) {
|
|
ccc->value = 0U;
|
|
if (ccc->cfg_changed) {
|
|
ccc->cfg_changed(attr, ccc->value);
|
|
}
|
|
|
|
LOG_DBG("ccc %p 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_addr_le_is_bonded(conn->id, &conn->le.dst) ||
|
|
(atomic_test_bit(params->flags,
|
|
BT_GATT_SUBSCRIBE_FLAG_VOLATILE))) {
|
|
/* Remove subscription */
|
|
params->value = 0U;
|
|
gatt_sub_remove(conn, sub, prev, params);
|
|
} else {
|
|
gatt_sub_update(conn, sub);
|
|
prev = ¶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;
|
|
|
|
req_len = sizeof(struct bt_att_prepare_write_req);
|
|
|
|
len = bt_att_get_mtu(conn) - req_len - 1;
|
|
len = MIN(params->length, len);
|
|
len += req_len;
|
|
|
|
return gatt_req_send(conn, gatt_prepare_write_rsp, params,
|
|
gatt_prepare_write_encode,
|
|
BT_ATT_OP_PREPARE_WRITE_REQ, len,
|
|
BT_ATT_CHAN_OPT(params));
|
|
}
|
|
|
|
static int gatt_write_encode(struct net_buf *buf, size_t len, void *user_data)
|
|
{
|
|
struct bt_gatt_write_params *params = user_data;
|
|
struct bt_att_write_req *req;
|
|
size_t write;
|
|
|
|
req = net_buf_add(buf, sizeof(*req));
|
|
req->handle = sys_cpu_to_le16(params->handle);
|
|
|
|
write = net_buf_append_bytes(buf, params->length, params->data,
|
|
K_NO_WAIT, NULL, NULL);
|
|
if (write != params->length) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_gatt_write(struct bt_conn *conn, struct bt_gatt_write_params *params)
|
|
{
|
|
size_t len;
|
|
|
|
__ASSERT(conn, "invalid parameters\n");
|
|
__ASSERT(params && params->func, "invalid parameters\n");
|
|
__ASSERT(params->handle, "invalid parameters\n");
|
|
|
|
if (conn->state != BT_CONN_CONNECTED) {
|
|
return -ENOTCONN;
|
|
}
|
|
|
|
len = sizeof(struct bt_att_write_req) + params->length;
|
|
|
|
/* Use Prepare Write if offset is set or Long Write is required */
|
|
if (params->offset || len > (bt_att_get_mtu(conn) - 1)) {
|
|
return gatt_prepare_write(conn, params);
|
|
}
|
|
|
|
LOG_DBG("handle 0x%04x length %u", params->handle, params->length);
|
|
|
|
return gatt_req_send(conn, gatt_write_rsp, params, gatt_write_encode,
|
|
BT_ATT_OP_WRITE_REQ, len, BT_ATT_CHAN_OPT(params));
|
|
}
|
|
|
|
static void gatt_write_ccc_rsp(struct bt_conn *conn, int err,
|
|
const void *pdu, uint16_t length,
|
|
void *user_data)
|
|
{
|
|
struct bt_gatt_subscribe_params *params = user_data;
|
|
uint8_t att_err;
|
|
|
|
LOG_DBG("err %d", err);
|
|
|
|
atomic_clear_bit(params->flags, BT_GATT_SUBSCRIBE_FLAG_WRITE_PENDING);
|
|
|
|
/* if write to CCC failed we remove subscription and notify app */
|
|
if (err) {
|
|
struct gatt_sub *sub;
|
|
sys_snode_t *node, *tmp, *prev;
|
|
|
|
sub = gatt_sub_find(conn);
|
|
if (!sub) {
|
|
return;
|
|
}
|
|
|
|
prev = NULL;
|
|
|
|
SYS_SLIST_FOR_EACH_NODE_SAFE(&sub->list, node, tmp) {
|
|
if (node == ¶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_addr_le_is_bonded(conn->id, &conn->le.dst)) {
|
|
return;
|
|
}
|
|
|
|
sub = gatt_sub_find(conn);
|
|
if (!sub) {
|
|
return;
|
|
}
|
|
|
|
/* Lookup existing subscriptions */
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&sub->list, params, node) {
|
|
if (!atomic_test_bit(params->flags,
|
|
BT_GATT_SUBSCRIBE_FLAG_SENT) &&
|
|
!atomic_test_bit(params->flags,
|
|
BT_GATT_SUBSCRIBE_FLAG_NO_RESUB)) {
|
|
int err;
|
|
|
|
/* Force write to CCC to workaround devices that don't
|
|
* track it properly.
|
|
*/
|
|
err = gatt_resub_ccc(conn, params);
|
|
if (err < 0) {
|
|
LOG_WRN("conn %p params %p resub failed (err %d)",
|
|
(void *)conn, params, err);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif /* CONFIG_BT_GATT_AUTO_RESUBSCRIBE */
|
|
|
|
#if defined(CONFIG_BT_GATT_AUTO_UPDATE_MTU)
|
|
static void gatt_exchange_mtu_func(struct bt_conn *conn, uint8_t err,
|
|
struct bt_gatt_exchange_params *params)
|
|
{
|
|
if (err) {
|
|
LOG_WRN("conn %p err 0x%02x", conn, err);
|
|
}
|
|
}
|
|
|
|
static struct bt_gatt_exchange_params gatt_exchange_params = {
|
|
.func = gatt_exchange_mtu_func,
|
|
};
|
|
#endif /* CONFIG_BT_GATT_AUTO_UPDATE_MTU */
|
|
#endif /* CONFIG_BT_GATT_CLIENT */
|
|
|
|
#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 *ccc,
|
|
const bt_addr_le_t *addr,
|
|
uint8_t id)
|
|
{
|
|
for (size_t i = 0; i < ARRAY_SIZE(ccc->cfg); i++) {
|
|
if (id == ccc->cfg[i].id &&
|
|
bt_addr_le_eq(&ccc->cfg[i].peer, addr)) {
|
|
return &ccc->cfg[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct addr_with_id {
|
|
const bt_addr_le_t *addr;
|
|
uint8_t id;
|
|
};
|
|
|
|
struct ccc_load {
|
|
struct addr_with_id addr_with_id;
|
|
struct ccc_store *entry;
|
|
size_t count;
|
|
};
|
|
|
|
static void ccc_clear(struct _bt_gatt_ccc *ccc,
|
|
const bt_addr_le_t *addr,
|
|
uint8_t id)
|
|
{
|
|
struct bt_gatt_ccc_cfg *cfg;
|
|
|
|
cfg = ccc_find_cfg(ccc, addr, id);
|
|
if (!cfg) {
|
|
LOG_DBG("Unable to clear CCC: cfg not found");
|
|
return;
|
|
}
|
|
|
|
clear_ccc_cfg(cfg);
|
|
}
|
|
|
|
static uint8_t ccc_load(const struct bt_gatt_attr *attr, uint16_t handle,
|
|
void *user_data)
|
|
{
|
|
struct ccc_load *load = user_data;
|
|
struct _bt_gatt_ccc *ccc;
|
|
struct bt_gatt_ccc_cfg *cfg;
|
|
|
|
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;
|
|
}
|
|
|
|
static int ccc_set_cb(const char *name, size_t len_rd, settings_read_cb read_cb,
|
|
void *cb_arg)
|
|
{
|
|
if (IS_ENABLED(CONFIG_BT_SETTINGS_CCC_LAZY_LOADING)) {
|
|
/* Only load CCCs on demand */
|
|
return 0;
|
|
}
|
|
|
|
return ccc_set(name, len_rd, read_cb, cb_arg);
|
|
}
|
|
|
|
BT_SETTINGS_DEFINE(ccc, "ccc", ccc_set_cb, NULL);
|
|
|
|
static int ccc_set_direct(const char *key, size_t len, settings_read_cb read_cb,
|
|
void *cb_arg, void *param)
|
|
{
|
|
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
|
|
const char *name;
|
|
|
|
LOG_DBG("key: %s", (const char *)param);
|
|
|
|
/* Only "bt/ccc" settings should ever come here */
|
|
if (!settings_name_steq((const char *)param, "bt/ccc", &name)) {
|
|
LOG_ERR("Invalid key");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return ccc_set(name, len, read_cb, cb_arg);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void bt_gatt_connected(struct bt_conn *conn)
|
|
{
|
|
struct conn_data data;
|
|
|
|
LOG_DBG("conn %p", conn);
|
|
|
|
data.conn = conn;
|
|
data.sec = BT_SECURITY_L1;
|
|
|
|
/* Load CCC settings from backend if bonded */
|
|
if (IS_ENABLED(CONFIG_BT_SETTINGS_CCC_LAZY_LOADING) &&
|
|
bt_addr_le_is_bonded(conn->id, &conn->le.dst)) {
|
|
char key[BT_SETTINGS_KEY_MAX];
|
|
|
|
if (conn->id) {
|
|
char id_str[4];
|
|
|
|
u8_to_dec(id_str, sizeof(id_str), conn->id);
|
|
bt_settings_encode_key(key, sizeof(key), "ccc",
|
|
&conn->le.dst, id_str);
|
|
} else {
|
|
bt_settings_encode_key(key, sizeof(key), "ccc",
|
|
&conn->le.dst, NULL);
|
|
}
|
|
|
|
settings_load_subtree_direct(key, ccc_set_direct, (void *)key);
|
|
}
|
|
|
|
bt_gatt_foreach_attr(0x0001, 0xffff, update_ccc, &data);
|
|
|
|
/* BLUETOOTH CORE SPECIFICATION Version 5.1 | Vol 3, Part C page 2192:
|
|
*
|
|
* 10.3.1.1 Handling of GATT indications and notifications
|
|
*
|
|
* A client “requests” a server to send indications and notifications
|
|
* by appropriately configuring the server via a Client Characteristic
|
|
* Configuration Descriptor. Since the configuration is persistent
|
|
* across a disconnection and reconnection, security requirements must
|
|
* be checked against the configuration upon a reconnection before
|
|
* sending indications or notifications. When a server reconnects to a
|
|
* client to send an indication or notification for which security is
|
|
* required, the server shall initiate or request encryption with the
|
|
* client prior to sending an indication or notification. If the client
|
|
* does not have an LTK indicating that the client has lost the bond,
|
|
* enabling encryption will fail.
|
|
*/
|
|
if (IS_ENABLED(CONFIG_BT_SMP) &&
|
|
(conn->role == BT_HCI_ROLE_CENTRAL ||
|
|
IS_ENABLED(CONFIG_BT_GATT_AUTO_SEC_REQ)) &&
|
|
bt_conn_get_security(conn) < data.sec) {
|
|
int err = bt_conn_set_security(conn, data.sec);
|
|
|
|
if (err) {
|
|
LOG_WRN("Failed to set security for bonded peer (%d)", err);
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_BT_GATT_AUTO_UPDATE_MTU)
|
|
int err;
|
|
|
|
err = bt_gatt_exchange_mtu(conn, &gatt_exchange_params);
|
|
if (err) {
|
|
LOG_WRN("MTU Exchange failed (err %d)", err);
|
|
}
|
|
#endif /* CONFIG_BT_GATT_AUTO_UPDATE_MTU */
|
|
}
|
|
|
|
void bt_gatt_att_max_mtu_changed(struct bt_conn *conn, uint16_t tx, uint16_t rx)
|
|
{
|
|
struct bt_gatt_cb *cb;
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&callback_list, cb, node) {
|
|
if (cb->att_mtu_updated) {
|
|
cb->att_mtu_updated(conn, tx, rx);
|
|
}
|
|
}
|
|
}
|
|
|
|
void bt_gatt_encrypt_change(struct bt_conn *conn)
|
|
{
|
|
struct conn_data data;
|
|
|
|
LOG_DBG("conn %p", conn);
|
|
|
|
data.conn = conn;
|
|
data.sec = BT_SECURITY_L1;
|
|
|
|
#if defined(CONFIG_BT_GATT_AUTO_RESUBSCRIBE)
|
|
add_subscriptions(conn);
|
|
#endif /* CONFIG_BT_GATT_AUTO_RESUBSCRIBE */
|
|
|
|
bt_gatt_foreach_attr(0x0001, 0xffff, update_ccc, &data);
|
|
|
|
if (!bt_gatt_change_aware(conn, false)) {
|
|
/* Send a Service Changed indication if the current peer is
|
|
* marked as change-unaware.
|
|
*/
|
|
sc_indicate(0x0001, 0xffff);
|
|
}
|
|
}
|
|
|
|
bool bt_gatt_change_aware(struct bt_conn *conn, bool req)
|
|
{
|
|
#if defined(CONFIG_BT_GATT_CACHING)
|
|
struct gatt_cf_cfg *cfg;
|
|
|
|
cfg = find_cf_cfg(conn);
|
|
if (!cfg || !CF_ROBUST_CACHING(cfg)) {
|
|
return true;
|
|
}
|
|
|
|
if (atomic_test_bit(cfg->flags, CF_CHANGE_AWARE)) {
|
|
return true;
|
|
}
|
|
|
|
/* BLUETOOTH CORE SPECIFICATION Version 5.1 | Vol 3, Part G page 2350:
|
|
* If a change-unaware client sends an ATT command, the server shall
|
|
* ignore it.
|
|
*/
|
|
if (!req) {
|
|
return false;
|
|
}
|
|
|
|
/* BLUETOOTH CORE SPECIFICATION Version 5.3 | Vol 3, Part G page 1475:
|
|
* 2.5.2.1 Robust Caching
|
|
* A change-unaware connected client becomes change-aware when it reads
|
|
* the Database Hash characteristic and then the server receives another
|
|
* ATT request from the client.
|
|
*/
|
|
if (atomic_test_and_clear_bit(cfg->flags, CF_DB_HASH_READ)) {
|
|
bt_att_clear_out_of_sync_sent(conn);
|
|
set_change_aware(cfg, true);
|
|
return true;
|
|
}
|
|
|
|
/* BLUETOOTH CORE SPECIFICATION Version 5.3 | Vol 3, Part G page 1476:
|
|
* 2.5.2.1 Robust Caching
|
|
* ... a change-unaware connected client using exactly one ATT bearer
|
|
* becomes change-aware when ...
|
|
* The server sends the client a response with the Error Code parameter
|
|
* set to Database Out Of Sync (0x12) and then the server receives
|
|
* another ATT request from the client.
|
|
*/
|
|
if (bt_att_fixed_chan_only(conn) && bt_att_out_of_sync_sent_on_fixed(conn)) {
|
|
atomic_clear_bit(cfg->flags, CF_DB_HASH_READ);
|
|
bt_att_clear_out_of_sync_sent(conn);
|
|
set_change_aware(cfg, true);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
static struct gatt_cf_cfg *find_cf_cfg_by_addr(uint8_t id,
|
|
const bt_addr_le_t *addr)
|
|
{
|
|
if (IS_ENABLED(CONFIG_BT_GATT_CACHING)) {
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(cf_cfg); i++) {
|
|
if (id == cf_cfg[i].id &&
|
|
bt_addr_le_eq(addr, &cf_cfg[i].peer)) {
|
|
return &cf_cfg[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#if defined(CONFIG_BT_SETTINGS)
|
|
|
|
struct ccc_save {
|
|
struct addr_with_id addr_with_id;
|
|
struct ccc_store store[CCC_STORE_MAX];
|
|
size_t count;
|
|
};
|
|
|
|
static uint8_t ccc_save(const struct bt_gatt_attr *attr, uint16_t handle,
|
|
void *user_data)
|
|
{
|
|
struct ccc_save *save = user_data;
|
|
struct _bt_gatt_ccc *ccc;
|
|
struct bt_gatt_ccc_cfg *cfg;
|
|
|
|
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 *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_addr_le_is_bonded(conn->id, &conn->le.dst)) {
|
|
struct addr_with_id addr_with_id = {
|
|
.addr = &conn->le.dst,
|
|
.id = conn->id,
|
|
};
|
|
bt_gatt_foreach_attr(0x0001, 0xffff,
|
|
remove_peer_from_attr,
|
|
&addr_with_id);
|
|
}
|
|
|
|
#if defined(CONFIG_BT_GATT_CLIENT)
|
|
remove_subscriptions(conn);
|
|
#endif /* CONFIG_BT_GATT_CLIENT */
|
|
|
|
#if defined(CONFIG_BT_GATT_CACHING)
|
|
remove_cf_cfg(conn);
|
|
#endif
|
|
}
|
|
|
|
void bt_gatt_req_set_mtu(struct bt_att_req *req, uint16_t mtu)
|
|
{
|
|
IF_ENABLED(CONFIG_BT_GATT_CLIENT, ({
|
|
if (req->func == gatt_read_rsp) {
|
|
struct bt_gatt_read_params *params = req->user_data;
|
|
|
|
__ASSERT_NO_MSG(params);
|
|
params->_att_mtu = mtu;
|
|
return;
|
|
}
|
|
}));
|
|
|
|
/* Otherwise: This request type does not have an `_att_mtu`
|
|
* params field or any other method to get this value, so we can
|
|
* just drop it here. Feel free to add this capability to other
|
|
* request types if needed.
|
|
*/
|
|
}
|