/* gatt.c - Generic Attribute Profile handling */
/*
* Copyright (c) 2015-2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <string.h>
#include <errno.h>
#include <stdbool.h>
#include <stdlib.h>
#include <sys/atomic.h>
#include <sys/byteorder.h>
#include <sys/util.h>
#include <settings/settings.h>
#if defined(CONFIG_BT_GATT_CACHING)
#include <tinycrypt/constants.h>
#include <tinycrypt/utils.h>
#include <tinycrypt/aes.h>
#include <tinycrypt/cmac_mode.h>
#include <tinycrypt/ccm_mode.h>
#endif /* CONFIG_BT_GATT_CACHING */
#include <bluetooth/hci.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/conn.h>
#include <bluetooth/uuid.h>
#include <bluetooth/gatt.h>
#include <drivers/bluetooth/hci_driver.h>
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_GATT)
#define LOG_MODULE_NAME bt_gatt
#include "common/log.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"
#define SC_TIMEOUT K_MSEC(10)
#define CCC_STORE_DELAY K_SECONDS(1)
#define DB_HASH_TIMEOUT K_MSEC(10)
static u16_t last_static_handle;
/* Persistent storage format for GATT CCC */
struct ccc_store {
u16_t handle;
u16_t value;
};
struct gatt_sub {
u8_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 */
static struct gatt_sub subscriptions[SUB_MAX];
static const u16_t gap_appearance = CONFIG_BT_DEVICE_APPEARANCE;
#if defined(CONFIG_BT_GATT_DYNAMIC_DB)
static sys_slist_t db;
#endif /* CONFIG_BT_GATT_DYNAMIC_DB */
static atomic_t init;
static ssize_t read_name(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, u16_t len, u16_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, u16_t len, u16_t offset,
u8_t flags)
{
char value[CONFIG_BT_DEVICE_NAME_MAX] = {};
if (offset) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
if (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,
u16_t len, u16_t offset)
{
u16_t appearance = sys_cpu_to_le16(gap_appearance);
return bt_gatt_attr_read(conn, attr, buf, len, offset, &appearance,
sizeof(appearance));
}
#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));
static ssize_t read_ppcp(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, u16_t len, u16_t offset)
{
struct __packed {
u16_t min_int;
u16_t max_int;
u16_t latency;
u16_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_SLAVE_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,
u16_t len, u16_t offset)
{
u8_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 | BT_GATT_PERM_WRITE_ENCRYPT,
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, BT_GATT_CHRC_READ,
BT_GATT_PERM_READ, read_appearance, NULL, 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 {
u16_t start;
u16_t end;
} __packed;
struct gatt_sc_cfg {
u8_t id;
bt_addr_le_t peer;
struct {
u16_t start;
u16_t end;
} data;
};
#define SC_CFG_MAX (CONFIG_BT_MAX_PAIRED + CONFIG_BT_MAX_CONN)
static struct gatt_sc_cfg sc_cfg[SC_CFG_MAX];
BUILD_ASSERT(sizeof(struct sc_data) == sizeof(sc_cfg[0].data));
static struct gatt_sc_cfg *find_sc_cfg(u8_t id, bt_addr_le_t *addr)
{
BT_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_cmp(&sc_cfg[i].peer, addr)) {
return &sc_cfg[i];
}
}
return NULL;
}
static void sc_store(struct gatt_sc_cfg *cfg)
{
char key[BT_SETTINGS_KEY_MAX];
int err;
if (cfg->id) {
char id_str[4];
u8_to_dec(id_str, sizeof(id_str), cfg->id);
bt_settings_encode_key(key, sizeof(key), "sc",
&cfg->peer, id_str);
} else {
bt_settings_encode_key(key, sizeof(key), "sc",
&cfg->peer, NULL);
}
err = settings_save_one(key, (char *)&cfg->data, sizeof(cfg->data));
if (err) {
BT_ERR("failed to store SC (err %d)", err);
return;
}
BT_DBG("stored SC for %s (%s, 0x%04x-0x%04x)",
bt_addr_le_str(&cfg->peer), log_strdup(key), cfg->data.start,
cfg->data.end);
}
static void sc_clear(struct gatt_sc_cfg *cfg)
{
BT_DBG("peer %s", bt_addr_le_str(&cfg->peer));
if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
bool modified = false;
if (cfg->data.start || cfg->data.end) {
modified = true;
}
if (modified && bt_addr_le_is_bonded(cfg->id, &cfg->peer)) {
char key[BT_SETTINGS_KEY_MAX];
int err;
if (cfg->id) {
char id_str[4];
u8_to_dec(id_str, sizeof(id_str), cfg->id);
bt_settings_encode_key(key, sizeof(key), "sc",
&cfg->peer, id_str);
} else {
bt_settings_encode_key(key, sizeof(key), "sc",
&cfg->peer, NULL);
}
err = settings_delete(key);
if (err) {
BT_ERR("failed to delete SC (err %d)", err);
} else {
BT_DBG("deleted SC for %s (%s)",
bt_addr_le_str(&cfg->peer),
log_strdup(key));
}
}
}
memset(cfg, 0, sizeof(*cfg));
}
static void sc_reset(struct gatt_sc_cfg *cfg)
{
BT_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(u16_t *start, u16_t *end, u16_t new_start,
u16_t new_end)
{
BT_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(u8_t id, bt_addr_le_t *peer, u16_t start, u16_t end)
{
struct gatt_sc_cfg *cfg;
bool modified = false;
BT_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) {
BT_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 void sc_clear_by_conn(struct bt_conn *conn)
{
struct gatt_sc_cfg *cfg;
cfg = find_sc_cfg(conn->id, &conn->le.dst);
if (cfg) {
sc_clear(cfg);
}
}
static ssize_t sc_ccc_cfg_write(struct bt_conn *conn,
const struct bt_gatt_attr *attr, u16_t value)
{
BT_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 {
/* Clear SC configuration if unsubscribed */
sc_clear_by_conn(conn);
}
return sizeof(value);
}
static struct _bt_gatt_ccc sc_ccc = BT_GATT_CCC_INITIALIZER(NULL,
sc_ccc_cfg_write,
NULL);
enum {
CF_CHANGE_AWARE, /* Client is changed aware */
CF_OUT_OF_SYNC, /* Client is out of sync */
/* Total number of flags - must be at the end of the enum */
CF_NUM_FLAGS,
};
#define CF_ROBUST_CACHING(_cfg) (_cfg->data[0] & BIT(0))
struct gatt_cf_cfg {
u8_t id;
bt_addr_le_t peer;
u8_t data[1];
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);
}
#if defined(CONFIG_BT_GATT_CACHING)
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_cmp(&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, u16_t len, u16_t offset)
{
struct gatt_cf_cfg *cfg;
u8_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 u8_t *value, u16_t len)
{
u16_t i;
u8_t last_byte = 1U;
u8_t last_bit = 1U;
/* Validate the bits */
for (i = 0U; i < len && i < last_byte; i++) {
u8_t chg_bits = value[i] ^ cfg->data[i];
u8_t bit;
for (bit = 0U; bit < last_bit; bit++) {
/* A client shall never clear a bit it has set */
if ((BIT(bit) & chg_bits) &&
(BIT(bit) & cfg->data[i])) {
return false;
}
}
}
/* Set the bits for each octect */
for (i = 0U; i < len && i < last_byte; i++) {
cfg->data[i] |= value[i] & ((1 << last_bit) - 1);
BT_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, u16_t len, u16_t offset, u8_t flags)
{
struct gatt_cf_cfg *cfg;
const u8_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) {
BT_WARN("No space to store Client Supported Features");
return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES);
}
BT_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;
atomic_set_bit(cfg->flags, CF_CHANGE_AWARE);
return len;
}
static u8_t db_hash[16];
struct k_delayed_work db_hash_work;
struct gen_hash_state {
struct tc_cmac_struct state;
int err;
};
static u8_t gen_hash_m(const struct bt_gatt_attr *attr, void *user_data)
{
struct gen_hash_state *state = user_data;
struct bt_uuid_16 *u16;
u8_t data[16];
ssize_t len;
u16_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 0x2800: /* GATT Primary Service */
case 0x2801: /* GATT Secondary Service */
case 0x2802: /* GATT Include Service */
case 0x2803: /* GATT Characteristic */
case 0x2900: /* GATT Characteristic Extended Properties */
value = sys_cpu_to_le16(attr->handle);
if (tc_cmac_update(&state->state, (uint8_t *)&value,
sizeof(attr->handle)) == TC_CRYPTO_FAIL) {
state->err = -EINVAL;
return BT_GATT_ITER_STOP;
}
value = sys_cpu_to_le16(u16->val);
if (tc_cmac_update(&state->state, (uint8_t *)&value,
sizeof(u16->val)) == TC_CRYPTO_FAIL) {
state->err = -EINVAL;
return BT_GATT_ITER_STOP;
}
len = attr->read(NULL, attr, data, sizeof(data), 0);
if (len < 0) {
state->err = len;
return BT_GATT_ITER_STOP;
}
if (tc_cmac_update(&state->state, data, len) ==
TC_CRYPTO_FAIL) {
state->err = -EINVAL;
return BT_GATT_ITER_STOP;
}
break;
/* Attributes to hash: handle + UUID */
case 0x2901: /* GATT Characteristic User Descriptor */
case 0x2902: /* GATT Client Characteristic Configuration */
case 0x2903: /* GATT Server Characteristic Configuration */
case 0x2904: /* GATT Characteristic Presentation Format */
case 0x2905: /* GATT Characteristic Aggregated Format */
value = sys_cpu_to_le16(attr->handle);
if (tc_cmac_update(&state->state, (uint8_t *)&value,
sizeof(attr->handle)) == TC_CRYPTO_FAIL) {
state->err = -EINVAL;
return BT_GATT_ITER_STOP;
}
value = sys_cpu_to_le16(u16->val);
if (tc_cmac_update(&state->state, (uint8_t *)&value,
sizeof(u16->val)) == TC_CRYPTO_FAIL) {
state->err = -EINVAL;
return BT_GATT_ITER_STOP;
}
break;
default:
return BT_GATT_ITER_CONTINUE;
}
return BT_GATT_ITER_CONTINUE;
}
static void db_hash_store(void)
{
int err;
err = settings_save_one("bt/hash", &db_hash, sizeof(db_hash));
if (err) {
BT_ERR("Failed to save Database Hash (err %d)", err);
}
BT_DBG("Database Hash stored");
}
/* Once the db_hash work has started we cannot cancel it anymore, so the
* assumption is made that the in-progress work cannot be pre-empted.
* This assumption should hold as long as calculation does not make any calls
* that would make it unready.
* If this assumption is no longer true we will have to solve the case where
* k_delayed_work_cancel failed because the work was in-progress but pre-empted.
*/
static void db_hash_gen(bool store)
{
u8_t key[16] = {};
struct tc_aes_key_sched_struct sched;
struct gen_hash_state state;
if (tc_cmac_setup(&state.state, key, &sched) == TC_CRYPTO_FAIL) {
BT_ERR("Unable to setup AES CMAC");
return;
}
bt_gatt_foreach_attr(0x0001, 0xffff, gen_hash_m, &state);
if (tc_cmac_final(db_hash, &state.state) == TC_CRYPTO_FAIL) {
BT_ERR("Unable to calculate hash");
return;
}
/**
* Core 5.1 does not state the endianess of the hash.
* However Vol 3, Part F, 3.3.1 says that multi-octet Characteristic
* Values shall be LE unless otherwise defined. PTS expects hash to be
* in little endianess as well. bt_smp_aes_cmac calculates the hash in
* big endianess so we have to swap.
*/
sys_mem_swap(db_hash, sizeof(db_hash));
BT_HEXDUMP_DBG(db_hash, sizeof(db_hash), "Hash: ");
if (IS_ENABLED(CONFIG_BT_SETTINGS) && store) {
db_hash_store();
}
}
static void db_hash_process(struct k_work *work)
{
db_hash_gen(true);
}
static ssize_t db_hash_read(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
void *buf, u16_t len, u16_t offset)
{
int err;
/* Check if db_hash is already pending in which case it shall be
* generated immediately instead of waiting for the work to complete.
*/
err = k_delayed_work_cancel(&db_hash_work);
if (!err) {
db_hash_gen(true);
}
/* 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.
*/
bt_gatt_change_aware(conn, true);
return bt_gatt_attr_read(conn, attr, buf, len, offset, db_hash,
sizeof(db_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);
atomic_clear_bit(cfg->flags, CF_OUT_OF_SYNC);
}
}
#endif /* CONFIG_BT_GATT_CACHING */
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),
#endif /* CONFIG_BT_GATT_CACHING */
#endif /* CONFIG_BT_GATT_SERVICE_CHANGED */
);
#if defined(CONFIG_BT_GATT_DYNAMIC_DB)
static u8_t found_attr(const struct bt_gatt_attr *attr, 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, u16_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;
u16_t handle, last_handle;
struct bt_gatt_attr *attrs = svc->attrs;
u16_t count = svc->attr_count;
if (sys_slist_is_empty(&db)) {
handle = last_static_handle;
last_handle = 0;
goto populate;
}
last = SYS_SLIST_PEEK_TAIL_CONTAINER(&db, last, node);
handle = last->attrs[last->attr_count - 1].handle;
last_handle = handle;
populate:
/* Populate the handles and append them to the list */
for (; attrs && count; attrs++, count--) {
if (!attrs->handle) {
/* Allocate handle if not set already */
attrs->handle = ++handle;
} else if (attrs->handle > handle) {
/* Use existing handle if valid */
handle = attrs->handle;
} else if (find_attr(attrs->handle)) {
/* Service has conflicting handles */
BT_ERR("Unable to register handle 0x%04x",
attrs->handle);
return -EINVAL;
}
BT_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 */
enum {
SC_RANGE_CHANGED, /* SC range changed */
SC_INDICATE_PENDING, /* SC indicate pending */
/* Total number of flags - must be at the end of the enum */
SC_NUM_FLAGS,
};
static struct gatt_sc {
struct bt_gatt_indicate_params params;
u16_t start;
u16_t end;
struct k_delayed_work work;
ATOMIC_DEFINE(flags, SC_NUM_FLAGS);
} gatt_sc;
static void sc_indicate_rsp(struct bt_conn *conn,
const struct bt_gatt_attr *attr, u8_t err)
{
#if defined(CONFIG_BT_GATT_CACHING)
struct gatt_cf_cfg *cfg;
#endif
BT_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 */
k_delayed_work_submit(&gatt_sc.work, K_NO_WAIT);
}
#if defined(CONFIG_BT_GATT_CACHING)
/* 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 receives and confirms a Service Changed indication.
*/
cfg = find_cf_cfg(conn);
if (cfg && CF_ROBUST_CACHING(cfg)) {
atomic_set_bit(cfg->flags, CF_CHANGE_AWARE);
BT_DBG("%s change-aware", bt_addr_le_str(&cfg->peer));
}
#endif
}
static void sc_process(struct k_work *work)
{
struct gatt_sc *sc = CONTAINER_OF(work, struct gatt_sc, work);
u16_t sc_range[2];
__ASSERT(!atomic_test_bit(sc->flags, SC_INDICATE_PENDING),
"Indicate already pending");
BT_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 (bt_gatt_indicate(NULL, &sc->params)) {
/* No connections to indicate */
return;
}
atomic_set_bit(sc->flags, SC_INDICATE_PENDING);
}
#if defined(CONFIG_BT_SETTINGS_CCC_STORE_ON_WRITE)
static struct gatt_ccc_store {
struct bt_conn *conn_list[CONFIG_BT_MAX_CONN];
struct k_delayed_work work;
} gatt_ccc_store;
static bool gatt_ccc_conn_is_queued(struct bt_conn *conn)
{
return (conn == gatt_ccc_store.conn_list[bt_conn_index(conn)]);
}
static void gatt_ccc_conn_unqueue(struct bt_conn *conn)
{
u8_t index = bt_conn_index(conn);
if (gatt_ccc_store.conn_list[index] != NULL) {
bt_conn_unref(gatt_ccc_store.conn_list[index]);
gatt_ccc_store.conn_list[index] = NULL;
}
}
static bool gatt_ccc_conn_queue_is_empty(void)
{
for (size_t i = 0; i < CONFIG_BT_MAX_CONN; i++) {
if (gatt_ccc_store.conn_list[i]) {
return false;
}
}
return true;
}
static void ccc_delayed_store(struct k_work *work)
{
struct gatt_ccc_store *ccc_store =
CONTAINER_OF(work, struct gatt_ccc_store, work);
for (size_t i = 0; i < CONFIG_BT_MAX_CONN; i++) {
struct bt_conn *conn = ccc_store->conn_list[i];
if (!conn) {
continue;
}
if (bt_addr_le_is_bonded(conn->id, &conn->le.dst)) {
bt_gatt_store_ccc(conn->id, &conn->le.dst);
bt_conn_unref(conn);
ccc_store->conn_list[i] = NULL;
}
}
}
#endif
void bt_gatt_init(void)
{
if (!atomic_cas(&init, 0, 1)) {
return;
}
Z_STRUCT_SECTION_FOREACH(bt_gatt_service_static, svc) {
last_static_handle += svc->attr_count;
}
#if defined(CONFIG_BT_GATT_CACHING)
k_delayed_work_init(&db_hash_work, db_hash_process);
/* Submit work to Generate initial hash as there could be static
* services already in the database.
*/
k_delayed_work_submit(&db_hash_work, DB_HASH_TIMEOUT);
#endif /* CONFIG_BT_GATT_CACHING */
if (IS_ENABLED(CONFIG_BT_GATT_SERVICE_CHANGED)) {
k_delayed_work_init(&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);
}
}
#if defined(CONFIG_BT_SETTINGS_CCC_STORE_ON_WRITE)
k_delayed_work_init(&gatt_ccc_store.work, ccc_delayed_store);
#endif
}
#if defined(CONFIG_BT_GATT_DYNAMIC_DB) || \
(defined(CONFIG_BT_GATT_CACHING) && defined(CONFIG_BT_SETTINGS))
static void sc_indicate(u16_t start, u16_t end)
{
BT_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)) {
BT_DBG("indicate pending, waiting until complete...");
return;
}
/* Reschedule since the range has changed */
k_delayed_work_submit(&gatt_sc.work, SC_TIMEOUT);
}
#endif /* BT_GATT_DYNAMIC_DB || (BT_GATT_CACHING && BT_SETTINGS) */
#if defined(CONFIG_BT_GATT_DYNAMIC_DB)
static void db_changed(void)
{
#if defined(CONFIG_BT_GATT_CACHING)
int i;
k_delayed_work_submit(&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_cmp(&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.
*/
atomic_clear_bit(cfg->flags, CF_OUT_OF_SYNC);
if (atomic_test_and_clear_bit(cfg->flags,
CF_CHANGE_AWARE)) {
BT_DBG("%s change-unaware",
bt_addr_le_str(&cfg->peer));
}
}
}
#endif
}
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");
/* Init GATT core services */
bt_gatt_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;
}
err = gatt_register(svc);
if (err < 0) {
return err;
}
sc_indicate(svc->attrs[0].handle,
svc->attrs[svc->attr_count - 1].handle);
db_changed();
return 0;
}
int bt_gatt_service_unregister(struct bt_gatt_service *svc)
{
__ASSERT(svc, "invalid parameters\n");
if (!sys_slist_find_and_remove(&db, &svc->node)) {
return -ENOENT;
}
sc_indicate(svc->attrs[0].handle,
svc->attrs[svc->attr_count - 1].handle);
db_changed();
return 0;
}
#endif /* CONFIG_BT_GATT_DYNAMIC_DB */
ssize_t bt_gatt_attr_read(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, u16_t buf_len, u16_t offset,
const void *value, u16_t value_len)
{
u16_t len;
if (offset > value_len) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
len = MIN(buf_len, value_len - offset);
BT_DBG("handle 0x%04x offset %u length %u", attr->handle, offset,
len);
memcpy(buf, (u8_t *)value + offset, len);
return len;
}
ssize_t bt_gatt_attr_read_service(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
void *buf, u16_t len, u16_t offset)
{
struct bt_uuid *uuid = attr->user_data;
if (uuid->type == BT_UUID_TYPE_16) {
u16_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 {
u16_t start_handle;
u16_t end_handle;
u16_t uuid16;
} __packed;
static u8_t get_service_handles(const struct bt_gatt_attr *attr,
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 = attr->handle;
return BT_GATT_ITER_CONTINUE;
}
static u16_t find_static_attr(const struct bt_gatt_attr *attr)
{
u16_t handle = 1;
Z_STRUCT_SECTION_FOREACH(bt_gatt_service_static, static_svc) {
for (int 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, u16_t len, u16_t offset)
{
struct bt_gatt_attr *incl = attr->user_data;
u16_t handle = incl->handle ? : find_static_attr(incl);
struct bt_uuid *uuid = incl->user_data;
struct gatt_incl pdu;
u8_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 {
u8_t properties;
u16_t value_handle;
union {
u16_t uuid16;
u8_t uuid[16];
};
} __packed;
uint16_t bt_gatt_attr_value_handle(const struct bt_gatt_attr *attr)
{
u16_t handle = 0;
if ((attr != NULL)
&& (attr->read == bt_gatt_attr_read_chrc)) {
struct bt_gatt_chrc *chrc = attr->user_data;
handle = chrc->value_handle;
if (handle == 0) {
/* Fall back to Zephyr value handle policy */
handle = (attr->handle ? : find_static_attr(attr)) + 1U;
}
}
return handle;
}
ssize_t bt_gatt_attr_read_chrc(struct bt_conn *conn,
const struct bt_gatt_attr *attr, void *buf,
u16_t len, u16_t offset)
{
struct bt_gatt_chrc *chrc = attr->user_data;
struct gatt_chrc pdu;
u8_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 u8_t gatt_foreach_iter(const struct bt_gatt_attr *attr,
u16_t start_handle, u16_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)
{
u8_t result;
/* Stop if over the requested range */
if (attr->handle > end_handle) {
return BT_GATT_ITER_STOP;
}
/* Check if attribute handle is within range */
if (attr->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, user_data);
if (!*num_matches) {
return BT_GATT_ITER_STOP;
}
return result;
}
static void foreach_attr_type_dyndb(u16_t start_handle, u16_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)
int 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,
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(u16_t start_handle, u16_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)
{
int i;
if (!num_matches) {
num_matches = UINT16_MAX;
}
if (start_handle <= last_static_handle) {
u16_t handle = 1;
Z_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++) {
struct bt_gatt_attr attr;
memcpy(&attr, &static_svc->attrs[i],
sizeof(attr));
attr.handle = handle;
if (gatt_foreach_iter(&attr, 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 u8_t find_next(const struct bt_gatt_attr *attr, 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;
u16_t handle = attr->handle ? : find_static_attr(attr);
bt_gatt_foreach_attr(handle + 1, handle + 1, find_next, &next);
return next;
}
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 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_cmp(&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,
u16_t len, u16_t offset)
{
struct _bt_gatt_ccc *ccc = attr->user_data;
const struct bt_gatt_ccc_cfg *cfg;
u16_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;
u16_t value = 0x0000;
for (i = 0; i < ARRAY_SIZE(ccc->cfg); i++) {
if (ccc->cfg[i].value > value) {
value = ccc->cfg[i].value;
}
}
BT_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,
u16_t len, u16_t offset, u8_t flags)
{
struct _bt_gatt_ccc *ccc = attr->user_data;
struct bt_gatt_ccc_cfg *cfg;
u16_t value;
if (offset) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
if (!len || len > sizeof(u16_t)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
if (len < sizeof(u16_t)) {
value = *(u8_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
* behavioraly the same as no written CCC.
*/
if (!value) {
return len;
}
cfg = find_ccc_cfg(NULL, ccc);
if (!cfg) {
BT_WARN("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);
}
}
cfg->value = value;
BT_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 defined(CONFIG_BT_SETTINGS_CCC_STORE_ON_WRITE)
if ((!gatt_ccc_conn_is_queued(conn)) &&
bt_addr_le_is_bonded(conn->id, &conn->le.dst)) {
/* Store the connection with the same index it has in
* the conns array
*/
gatt_ccc_store.conn_list[bt_conn_index(conn)] =
bt_conn_ref(conn);
k_delayed_work_submit(&gatt_ccc_store.work,
CCC_STORE_DELAY);
}
#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,
u16_t len, u16_t offset)
{
const struct bt_gatt_cep *value = attr->user_data;
u16_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,
u16_t len, u16_t offset)
{
const char *value = attr->user_data;
return bt_gatt_attr_read(conn, attr, buf, len, offset, value,
strlen(value));
}
ssize_t bt_gatt_attr_read_cpf(struct bt_conn *conn,
const struct bt_gatt_attr *attr, void *buf,
u16_t len, u16_t offset)
{
const struct bt_gatt_cpf *value = attr->user_data;
return bt_gatt_attr_read(conn, attr, buf, len, offset, value,
sizeof(*value));
}
struct notify_data {
int err;
u16_t type;
union {
struct bt_gatt_notify_params *nfy_params;
struct bt_gatt_indicate_params *ind_params;
};
};
static int gatt_notify(struct bt_conn *conn, u16_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.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 (!bt_gatt_change_aware(conn, false)) {
return -EAGAIN;
}
#endif
buf = bt_att_create_pdu(conn, BT_ATT_OP_NOTIFY,
sizeof(*nfy) + params->len);
if (!buf) {
BT_WARN("No buffer available to send notification");
return -ENOMEM;
}
BT_DBG("conn %p handle 0x%04x", conn, handle);
nfy = net_buf_add(buf, sizeof(*nfy));
nfy->handle = sys_cpu_to_le16(handle);
net_buf_add(buf, params->len);
memcpy(nfy->value, params->data, params->len);
return bt_att_send(conn, buf, params->func, params->user_data);
}
static void gatt_indicate_rsp(struct bt_conn *conn, u8_t err,
const void *pdu, u16_t length, void *user_data)
{
struct bt_gatt_indicate_params *params = user_data;
params->func(conn, params->attr, err);
}
static int gatt_send(struct bt_conn *conn, struct net_buf *buf,
bt_att_func_t func, void *params,
bt_att_destroy_t destroy)
{
int err;
if (params) {
struct bt_att_req *req;
/* Allocate new request */
req = bt_att_req_alloc(BT_ATT_TIMEOUT);
if (!req) {
return -ENOMEM;
}
req->buf = buf;
req->func = func;
req->destroy = destroy;
req->user_data = params;
err = bt_att_req_send(conn, req);
if (err) {
bt_att_req_free(req);
}
} else {
err = bt_att_send(conn, buf, NULL, NULL);
}
if (err) {
BT_ERR("Error sending ATT PDU: %d", err);
}
return err;
}
static int gatt_indicate(struct bt_conn *conn, u16_t handle,
struct bt_gatt_indicate_params *params)
{
struct net_buf *buf;
struct bt_att_indicate *ind;
#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
buf = bt_att_create_pdu(conn, BT_ATT_OP_INDICATE,
sizeof(*ind) + params->len);
if (!buf) {
BT_WARN("No buffer available to send indication");
return -ENOMEM;
}
BT_DBG("conn %p handle 0x%04x", conn, handle);
ind = net_buf_add(buf, sizeof(*ind));
ind->handle = sys_cpu_to_le16(handle);
net_buf_add(buf, params->len);
memcpy(ind->value, params->data, params->len);
if (!params->func) {
return gatt_send(conn, buf, NULL, NULL, NULL);
}
return gatt_send(conn, buf, gatt_indicate_rsp, params, NULL);
}
static u8_t notify_cb(const struct bt_gatt_attr *attr, void *user_data)
{
struct notify_data *data = user_data;
struct _bt_gatt_ccc *ccc;
size_t i;
/* Check attribute user_data must be of type struct _bt_gatt_ccc */
if (attr->write != bt_gatt_attr_write_ccc) {
return BT_GATT_ITER_CONTINUE;
}
ccc = attr->user_data;
/* Save Service Changed data if peer is not connected */
if (IS_ENABLED(CONFIG_BT_GATT_SERVICE_CHANGED) && ccc == &sc_ccc) {
for (i = 0; i < ARRAY_SIZE(sc_cfg); i++) {
struct gatt_sc_cfg *cfg = &sc_cfg[i];
struct bt_conn *conn;
if (!bt_addr_le_cmp(&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;
}
if (data->type == BT_GATT_CCC_INDICATE) {
err = gatt_indicate(conn, attr->handle - 1,
data->ind_params);
} else {
err = gatt_notify(conn, attr->handle - 1,
data->nfy_params);
}
bt_conn_unref(conn);
if (err < 0) {
return BT_GATT_ITER_STOP;
}
data->err = 0;
}
return BT_GATT_ITER_CONTINUE;
}
static u8_t match_uuid(const struct bt_gatt_attr *attr, void *user_data)
{
const struct bt_gatt_attr **found = user_data;
*found = attr;
return BT_GATT_ITER_STOP;
}
int bt_gatt_notify_cb(struct bt_conn *conn,
struct bt_gatt_notify_params *params)
{
struct notify_data data;
const struct bt_gatt_attr *attr;
u16_t handle;
__ASSERT(params, "invalid parameters\n");
__ASSERT(params->attr, "invalid parameters\n");
if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) {
return -EAGAIN;
}
attr = params->attr;
if (conn && conn->state != BT_CONN_CONNECTED) {
return -ENOTCONN;
}
handle = attr->handle ? : find_static_attr(attr);
if (!handle) {
return -ENOENT;
}
/* Lookup UUID if it was given */
if (params->uuid) {
attr = NULL;
bt_gatt_foreach_attr_type(handle, 0xffff, params->uuid,
NULL, 1, match_uuid, &attr);
if (!attr) {
return -ENOENT;
}
handle = attr->handle ? : find_static_attr(attr);
if (!handle) {
return -ENOENT;
}
}
/* Check if attribute is a characteristic then adjust the handle */
if (!bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CHRC)) {
struct bt_gatt_chrc *chrc = attr->user_data;
if (!(chrc->properties & BT_GATT_CHRC_NOTIFY)) {
return -EINVAL;
}
handle = bt_gatt_attr_value_handle(attr);
}
if (conn) {
return gatt_notify(conn, handle, params);
}
data.err = -ENOTCONN;
data.type = BT_GATT_CCC_NOTIFY;
data.nfy_params = params;
bt_gatt_foreach_attr_type(handle, 0xffff, BT_UUID_GATT_CCC, NULL, 1,
notify_cb, &data);
return data.err;
}
int bt_gatt_indicate(struct bt_conn *conn,
struct bt_gatt_indicate_params *params)
{
struct notify_data data;
const struct bt_gatt_attr *attr;
u16_t handle;
__ASSERT(params, "invalid parameters\n");
__ASSERT(params->attr, "invalid parameters\n");
if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) {
return -EAGAIN;
}
attr = params->attr;
if (conn && conn->state != BT_CONN_CONNECTED) {
return -ENOTCONN;
}
handle = attr->handle ? : find_static_attr(attr);
if (!handle) {
return -ENOENT;
}
/* Lookup UUID if it was given */
if (params->uuid) {
attr = NULL;
bt_gatt_foreach_attr_type(handle, 0xffff, params->uuid,
NULL, 1, match_uuid, &attr);
if (!attr) {
return -ENOENT;
}
handle = attr->handle ? : find_static_attr(attr);
if (!handle) {
return -ENOENT;
}
}
/* Check if attribute is a characteristic then adjust the handle */
if (!bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CHRC)) {
struct bt_gatt_chrc *chrc = attr->user_data;
if (!(chrc->properties & BT_GATT_CHRC_INDICATE)) {
return -EINVAL;
}
handle = bt_gatt_attr_value_handle(attr);
}
if (conn) {
return gatt_indicate(conn, handle, params);
}
data.err = -ENOTCONN;
data.type = BT_GATT_CCC_INDICATE;
data.ind_params = params;
bt_gatt_foreach_attr_type(handle, 0xffff, BT_UUID_GATT_CCC, NULL, 1,
notify_cb, &data);
return data.err;
}
u16_t bt_gatt_get_mtu(struct bt_conn *conn)
{
return bt_att_get_mtu(conn);
}
u8_t bt_gatt_check_perm(struct bt_conn *conn, const struct bt_gatt_attr *attr,
u8_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;
}
mask &= attr->perm;
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_ENCRYPT_MASK)) {
#if defined(CONFIG_BT_SMP)
if (!conn->encrypt) {
return BT_ATT_ERR_INSUFFICIENT_ENCRYPTION;
}
#else
return BT_ATT_ERR_INSUFFICIENT_ENCRYPTION;
#endif /* CONFIG_BT_SMP */
}
return 0;
}
static void sc_restore_rsp(struct bt_conn *conn,
const struct bt_gatt_attr *attr, u8_t err)
{
#if defined(CONFIG_BT_GATT_CACHING)
struct gatt_cf_cfg *cfg;
#endif
BT_DBG("err 0x%02x", err);
#if defined(CONFIG_BT_GATT_CACHING)
/* 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 receives and confirms a Service Changed indication.
*/
cfg = find_cf_cfg(conn);
if (cfg && CF_ROBUST_CACHING(cfg)) {
atomic_set_bit(cfg->flags, CF_CHANGE_AWARE);
BT_DBG("%s change-aware", bt_addr_le_str(&cfg->peer));
}
#endif
}
static struct bt_gatt_indicate_params sc_restore_params[CONFIG_BT_MAX_CONN];
static void sc_restore(struct bt_conn *conn)
{
struct gatt_sc_cfg *cfg;
u16_t sc_range[2];
u8_t index;
cfg = find_sc_cfg(conn->id, &conn->le.dst);
if (!cfg) {
BT_DBG("no SC data found");
return;
}
if (!(cfg->data.start || cfg->data.end)) {
return;
}
BT_DBG("peer %s start 0x%04x end 0x%04x", bt_addr_le_str(&cfg->peer),
cfg->data.start, cfg->data.end);
sc_range[0] = sys_cpu_to_le16(cfg->data.start);
sc_range[1] = sys_cpu_to_le16(cfg->data.end);
index = bt_conn_index(conn);
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[0];
sc_restore_params[index].len = sizeof(sc_range);
if (bt_gatt_indicate(conn, &sc_restore_params[index])) {
BT_ERR("SC restore indication failed");
}
/* Reset config data */
sc_reset(cfg);
}
struct conn_data {
struct bt_conn *conn;
bt_security_t sec;
};
static u8_t update_ccc(const struct bt_gatt_attr *attr, void *user_data)
{
struct conn_data *data = user_data;
struct bt_conn *conn = data->conn;
struct _bt_gatt_ccc *ccc;
size_t i;
u8_t err;
/* Check attribute user_data must be of type struct _bt_gatt_ccc */
if (attr->write != bt_gatt_attr_write_ccc) {
return BT_GATT_ITER_CONTINUE;
}
ccc = attr->user_data;
for (i = 0; i < ARRAY_SIZE(ccc->cfg); i++) {
struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i];
/* Ignore configuration for different peer or not active */
if (!cfg->value ||
!bt_conn_is_peer_addr_le(conn, cfg->id, &cfg->peer)) {
continue;
}
/* Check if attribute requires encryption/authentication */
err = bt_gatt_check_perm(conn, attr, BT_GATT_PERM_WRITE_MASK);
if (err) {
bt_security_t sec;
if (err == BT_ATT_ERR_WRITE_NOT_PERMITTED) {
BT_WARN("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 u8_t disconnected_cb(const struct bt_gatt_attr *attr, void *user_data)
{
struct bt_conn *conn = user_data;
struct _bt_gatt_ccc *ccc;
bool value_used;
size_t i;
/* Check attribute user_data must be of type struct _bt_gatt_ccc */
if (attr->write != bt_gatt_attr_write_ccc) {
return BT_GATT_ITER_CONTINUE;
}
ccc = attr->user_data;
/* If already disabled skip */
if (!ccc->value) {
return BT_GATT_ITER_CONTINUE;
}
/* Checking if all values are disabled */
value_used = false;
for (i = 0; i < ARRAY_SIZE(ccc->cfg); i++) {
struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i];
/* Ignore configurations with disabled value */
if (!cfg->value) {
continue;
}
if (!bt_conn_is_peer_addr_le(conn, cfg->id, &cfg->peer)) {
struct bt_conn *tmp;
/* Skip if there is another peer connected */
tmp = bt_conn_lookup_addr_le(cfg->id, &cfg->peer);
if (tmp) {
if (tmp->state == BT_CONN_CONNECTED) {
value_used = true;
}
bt_conn_unref(tmp);
}
} else {
/* Clear value if not paired */
if (!bt_addr_le_is_bonded(conn->id, &conn->le.dst)) {
if (ccc == &sc_ccc) {
sc_clear_by_conn(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);
}
BT_DBG("ccc %p reseted", ccc);
}
return BT_GATT_ITER_CONTINUE;
}
bool bt_gatt_is_subscribed(struct bt_conn *conn,
const struct bt_gatt_attr *attr, u16_t ccc_value)
{
const struct _bt_gatt_ccc *ccc;
__ASSERT(conn, "invalid parameter\n");
__ASSERT(attr, "invalid parameter\n");
if (conn->state != BT_CONN_CONNECTED) {
return false;
}
/* Check if attribute is a characteristic declaration */
if (!bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CHRC)) {
struct bt_gatt_chrc *chrc = attr->user_data;
if (!(chrc->properties &
(BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE))) {
/* Characteristic doesn't support subscription */
return false;
}
attr = bt_gatt_attr_next(attr);
}
/* Check if attribute is a characteristic value */
if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CCC) != 0) {
attr = bt_gatt_attr_next(attr);
}
/* Check if the attribute is the CCC Descriptor */
if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CCC) != 0) {
return false;
}
ccc = attr->user_data;
/* Check if the connection is subscribed */
for (size_t i = 0; i < BT_GATT_CCC_MAX; i++) {
const struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i];
if (bt_conn_is_peer_addr_le(conn, cfg->id, &cfg->peer) &&
(ccc_value & ccc->cfg[i].value)) {
return true;
}
}
return false;
}
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 (sys_slist_is_empty(&sub->list)) {
/* Reset address if there are no subscription left */
bt_addr_le_copy(&sub->peer, BT_ADDR_LE_ANY);
}
}
#if defined(CONFIG_BT_GATT_CLIENT)
static struct gatt_sub *gatt_sub_find_free(struct bt_conn *conn,
struct gatt_sub **free_sub)
{
int i;
if (free_sub) {
*free_sub = NULL;
}
for (i = 0; i < ARRAY_SIZE(subscriptions); i++) {
struct gatt_sub *sub = &subscriptions[i];
if (bt_conn_is_peer_addr_le(conn, sub->id, &sub->peer)) {
return sub;
} else if (free_sub &&
!bt_addr_le_cmp(BT_ADDR_LE_ANY, &sub->peer)) {
*free_sub = sub;
}
}
return NULL;
}
#define gatt_sub_find(_conn) \
gatt_sub_find_free(_conn, NULL)
static struct gatt_sub *gatt_sub_add(struct bt_conn *conn)
{
struct gatt_sub *sub, *free_sub;
sub = gatt_sub_find_free(conn, &free_sub);
if (sub) {
return sub;
}
if (free_sub) {
bt_addr_le_copy(&free_sub->peer, &conn->le.dst);
free_sub->id = conn->id;
return free_sub;
}
return NULL;
}
void bt_gatt_notification(struct bt_conn *conn, u16_t handle,
const void *data, u16_t length)
{
struct bt_gatt_subscribe_params *params, *tmp;
struct gatt_sub *sub;
BT_DBG("handle 0x%04x length %u", handle, length);
sub = gatt_sub_find(conn);
if (!sub) {
return;
}
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&sub->list, params, tmp, node) {
if (handle != params->value_handle) {
continue;
}
if (params->notify(conn, params, data, length) ==
BT_GATT_ITER_STOP) {
bt_gatt_unsubscribe(conn, params);
}
}
}
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) {
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, u8_t err, const void *pdu,
u16_t length, void *user_data)
{
struct bt_gatt_exchange_params *params = user_data;
params->func(conn, err, params);
}
int bt_gatt_exchange_mtu(struct bt_conn *conn,
struct bt_gatt_exchange_params *params)
{
struct bt_att_exchange_mtu_req *req;
struct net_buf *buf;
u16_t mtu;
__ASSERT(conn, "invalid parameter\n");
__ASSERT(params && params->func, "invalid parameters\n");
if (conn->state != BT_CONN_CONNECTED) {
return -ENOTCONN;
}
buf = bt_att_create_pdu(conn, BT_ATT_OP_MTU_REQ, sizeof(*req));
if (!buf) {
return -ENOMEM;
}
mtu = BT_ATT_MTU;
BT_DBG("Client MTU %u", mtu);
req = net_buf_add(buf, sizeof(*req));
req->mtu = sys_cpu_to_le16(mtu);
return gatt_send(conn, buf, gatt_mtu_rsp, params, NULL);
}
static void gatt_discover_next(struct bt_conn *conn, u16_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 */