Bluetooth: GATT: Add support for new PDUs

This adds support for ATT_MULTIPLE_HANDLE_VALUE_NTF,
ATT_READ_MULTIPLE_VARIABLE_REQ and ATT_READ_MULTIPLE_VARIABLE_RSP.

Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
This commit is contained in:
Luiz Augusto von Dentz 2020-02-26 17:23:55 -08:00 committed by Johan Hedberg
commit e0cbdf3b87
5 changed files with 406 additions and 3 deletions

View file

@ -876,7 +876,7 @@ int bt_gatt_notify_cb(struct bt_conn *conn,
*
* @param conn Connection object.
* @param num_params Number of notification parameters.
* @param params Notification parameters.
* @param params Array of notification parameters.
*
* @return 0 in case of success or negative value in case of error.
*/

View file

@ -102,6 +102,16 @@ config BT_GATT_CACHING
characteristics which can be used by clients to detect if anything has
changed on the GATT database.
if BT_GATT_CACHING
config BT_GATT_NOTIFY_MULTIPLE
bool "GATT Notify Multiple Characteristic Values support"
depends on BT_GATT_CACHING
default y
help
This option enables support for the GATT Notify Multiple
Characteristic Values procedure.
config BT_GATT_ENFORCE_CHANGE_UNAWARE
bool "GATT Enforce change-unaware state"
depends on BT_GATT_CACHING
@ -114,6 +124,8 @@ config BT_GATT_ENFORCE_CHANGE_UNAWARE
In case the service cannot deal with sudden errors (-EAGAIN) then it
shall not use this option.
endif # BT_GATT_CACHING
config BT_GATT_CLIENT
bool "GATT client support"
help

View file

@ -1239,6 +1239,94 @@ static u8_t att_read_mult_req(struct bt_att_chan *chan, struct net_buf *buf)
return 0;
}
#if defined(CONFIG_BT_EATT)
static u8_t read_vl_cb(const struct bt_gatt_attr *attr, void *user_data)
{
struct read_data *data = user_data;
struct bt_att_chan *chan = data->chan;
struct bt_conn *conn = chan->chan.chan.conn;
struct bt_att_read_mult_vl_rsp *rsp;
int read;
BT_DBG("handle 0x%04x", attr->handle);
data->rsp = net_buf_add(data->buf, sizeof(*data->rsp));
/*
* If any attribute is founded in handle range it means that error
* should be changed from pre-set: invalid handle error to no error.
*/
data->err = 0x00;
/* Check attribute permissions */
data->err = bt_gatt_check_perm(conn, attr, BT_GATT_PERM_READ_MASK);
if (data->err) {
return BT_GATT_ITER_STOP;
}
/* The Length Value Tuple List may be truncated within the first two
* octets of a tuple due to the size limits of the current ATT_MTU.
*/
if (chan->chan.tx.mtu - data->buf->len < 2) {
return BT_GATT_ITER_STOP;
}
rsp = net_buf_add(data->buf, sizeof(*rsp));
read = att_chan_read(chan, attr, data->buf, data->offset, NULL, NULL);
if (read < 0) {
data->err = err_to_att(read);
return BT_GATT_ITER_STOP;
}
rsp->len = read;
return BT_GATT_ITER_CONTINUE;
}
static u8_t att_read_mult_vl_req(struct bt_att_chan *chan, struct net_buf *buf)
{
struct bt_conn *conn = chan->chan.chan.conn;
struct read_data data;
u16_t handle;
(void)memset(&data, 0, sizeof(data));
data.buf = bt_att_create_pdu(conn, BT_ATT_OP_READ_MULT_VL_RSP, 0);
if (!data.buf) {
return BT_ATT_ERR_UNLIKELY;
}
data.chan = chan;
while (buf->len >= sizeof(u16_t)) {
handle = net_buf_pull_le16(buf);
BT_DBG("handle 0x%04x ", handle);
/* If handle is not valid then return invalid handle error.
* If handle is found error will be cleared by read_cb.
*/
data.err = BT_ATT_ERR_INVALID_HANDLE;
bt_gatt_foreach_attr(handle, handle, read_vl_cb, &data);
/* Stop reading in case of error */
if (data.err) {
net_buf_unref(data.buf);
/* Respond here since handle is set */
send_err_rsp(chan, BT_ATT_OP_READ_MULT_VL_REQ, handle,
data.err);
return 0;
}
}
(void)bt_att_chan_send(chan, data.buf, chan_rsp_sent);
return 0;
}
#endif /* CONFIG_BT_EATT */
#endif /* CONFIG_BT_GATT_READ_MULTIPLE */
struct read_group_data {
@ -1894,6 +1982,16 @@ static u8_t att_handle_read_mult_rsp(struct bt_att_chan *chan,
return att_handle_rsp(chan, buf->data, buf->len, 0);
}
#if defined(CONFIG_BT_EATT)
static u8_t att_handle_read_mult_vl_rsp(struct bt_att_chan *chan,
struct net_buf *buf)
{
BT_DBG("");
return att_handle_rsp(chan, buf->data, buf->len, 0);
}
#endif /* CONFIG_BT_EATT */
#endif /* CONFIG_BT_GATT_READ_MULTIPLE */
static u8_t att_handle_read_group_rsp(struct bt_att_chan *chan,
@ -1960,6 +2058,15 @@ static u8_t att_indicate(struct bt_att_chan *chan, struct net_buf *buf)
return 0;
}
static u8_t att_notify_mult(struct bt_att_chan *chan, struct net_buf *buf)
{
BT_DBG("chan %p", chan);
bt_gatt_mult_notification(chan->att->conn, buf->data, buf->len);
return 0;
}
#endif /* CONFIG_BT_GATT_CLIENT */
static u8_t att_confirm(struct bt_att_chan *chan, struct net_buf *buf)
@ -2004,6 +2111,12 @@ static const struct att_handler {
BT_ATT_READ_MULT_MIN_LEN_REQ,
ATT_REQUEST,
att_read_mult_req },
#if defined(CONFIG_BT_EATT)
{ BT_ATT_OP_READ_MULT_VL_REQ,
BT_ATT_READ_MULT_MIN_LEN_REQ,
ATT_REQUEST,
att_read_mult_vl_req },
#endif /* CONFIG_BT_EATT */
#endif /* CONFIG_BT_GATT_READ_MULTIPLE */
{ BT_ATT_OP_READ_GROUP_REQ,
sizeof(struct bt_att_read_group_req),
@ -2070,6 +2183,12 @@ static const struct att_handler {
sizeof(struct bt_att_read_mult_rsp),
ATT_RESPONSE,
att_handle_read_mult_rsp },
#if defined(CONFIG_BT_EATT)
{ BT_ATT_OP_READ_MULT_VL_RSP,
sizeof(struct bt_att_read_mult_vl_rsp),
ATT_RESPONSE,
att_handle_read_mult_vl_rsp },
#endif /* CONFIG_BT_EATT */
#endif /* CONFIG_BT_GATT_READ_MULTIPLE */
{ BT_ATT_OP_READ_GROUP_RSP,
sizeof(struct bt_att_read_group_rsp),
@ -2095,6 +2214,10 @@ static const struct att_handler {
sizeof(struct bt_att_indicate),
ATT_INDICATION,
att_indicate },
{ BT_ATT_OP_NOTIFY_MULT,
sizeof(struct bt_att_notify_mult),
ATT_NOTIFICATION,
att_notify_mult },
#endif /* CONFIG_BT_GATT_CLIENT */
};

View file

@ -404,12 +404,14 @@ enum {
#define CF_BIT_ROBUST_CACHING 0
#define CF_BIT_EATT 1
#define CF_BIT_LAST CF_BIT_EATT
#define CF_BIT_NOTIFY_MULTI 2
#define CF_BIT_LAST CF_BIT_NOTIFY_MULTI
#define CF_BYTE_LAST (CF_BIT_LAST % 8)
#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 {
u8_t id;
@ -490,7 +492,7 @@ static bool cf_set_value(struct gatt_cf_cfg *cfg, const u8_t *value, u16_t len)
/* Set the bits for each octect */
for (i = 0U; i < len && i < last_byte; i++) {
cfg->data[i] |= value[i] & ((1 << last_bit) - 1);
cfg->data[i] |= value[i] & (BIT(last_bit + 1) - 1);
BT_DBG("byte %u: data 0x%02x value 0x%02x", i, cfg->data[i],
value[i]);
}
@ -1621,6 +1623,113 @@ struct notify_data {
};
};
#if defined(CONFIG_BT_GATT_NOTIFY_MULTIPLE)
struct nfy_mult_data {
bt_gatt_complete_func_t func;
void *user_data;
};
#define nfy_mult_user_data(buf) \
((struct nfy_mult_data *)net_buf_user_data(buf))
#define nfy_mult_data_match(buf, _func, _user_data) \
((nfy_mult_user_data(buf)->func == _func) && \
(nfy_mult_user_data(buf)->user_data == _user_data))
static struct net_buf *nfy_mult[CONFIG_BT_MAX_CONN];
static int gatt_notify_mult_send(struct bt_conn *conn, struct net_buf **buf)
{
struct nfy_mult_data *data = nfy_mult_user_data(*buf);
int ret;
ret = bt_att_send(conn, *buf, data->func, data->user_data);
if (ret < 0) {
net_buf_unref(*buf);
}
*buf = NULL;
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);
bt_conn_unref(conn);
}
}
}
K_WORK_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_mult(struct bt_conn *conn, u16_t handle,
struct bt_gatt_notify_params *params)
{
struct net_buf **buf = &nfy_mult[bt_conn_index(conn)];
struct bt_att_notify_mult *nfy;
/* 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(*nfy) + params->len) ||
!nfy_mult_data_match(*buf, params->func, params->user_data))) {
int ret;
ret = gatt_notify_mult_send(conn, buf);
if (ret < 0) {
return ret;
}
}
if (!*buf) {
*buf = bt_att_create_pdu(conn, BT_ATT_OP_NOTIFY_MULT,
sizeof(*nfy) + params->len);
if (!*buf) {
BT_WARN("No buffer available to send notification");
return -ENOMEM;
}
/* Set user_data so it can be restored when sending */
nfy_mult_user_data(*buf)->func = params->func;
nfy_mult_user_data(*buf)->user_data = params->user_data;
}
BT_DBG("handle 0x%04x len %u", handle, params->len);
nfy = net_buf_add(*buf, sizeof(*nfy));
nfy->handle = sys_cpu_to_le16(handle);
nfy->len = sys_cpu_to_le16(params->len);
net_buf_add(*buf, params->len);
memcpy(nfy->value, params->data, params->len);
k_work_submit(&nfy_mult_work);
return 0;
}
#endif /* CONFIG_BT_GATT_NOTIFY_MULTIPLE */
static int gatt_notify(struct bt_conn *conn, u16_t handle,
struct bt_gatt_notify_params *params)
{
@ -1638,6 +1747,12 @@ static int gatt_notify(struct bt_conn *conn, u16_t handle,
}
#endif
#if defined(CONFIG_BT_GATT_NOTIFY_MULTIPLE)
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) {
@ -1903,6 +2018,27 @@ int bt_gatt_notify_cb(struct bt_conn *conn,
return data.err;
}
#if defined(CONFIG_BT_GATT_NOTIFY_MULTIPLE)
int bt_gatt_notify_multiple(struct bt_conn *conn, u16_t num_params,
struct bt_gatt_notify_params *params)
{
int i, ret;
__ASSERT(params, "invalid parameters\n");
__ASSERT(num_params, "invalid parameters\n");
__ASSERT(params->attr, "invalid parameters\n");
for (i = 0; i < num_params; i++) {
ret = bt_gatt_notify_cb(conn, &params[i]);
if (ret < 0) {
return ret;
}
}
return 0;
}
#endif /* CONFIG_BT_GATT_NOTIFY_MULTIPLE */
int bt_gatt_indicate(struct bt_conn *conn,
struct bt_gatt_indicate_params *params)
{
@ -2343,6 +2479,55 @@ void bt_gatt_notification(struct bt_conn *conn, u16_t handle,
}
}
void bt_gatt_mult_notification(struct bt_conn *conn, const void *data,
u16_t length)
{
struct bt_gatt_subscribe_params *params, *tmp;
const struct bt_att_notify_mult *nfy;
struct net_buf_simple buf;
struct gatt_sub *sub;
BT_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)) {
u16_t handle;
u16_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);
BT_DBG("handle 0x%02x len %u", handle, len);
if (len > buf.len) {
BT_ERR("Invalid data len %u > %u", len, length);
return;
}
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&sub->list, params, tmp,
node) {
if (handle != params->value_handle) {
continue;
}
if (params->notify(conn, params, nfy->value, len) ==
BT_GATT_ITER_STOP) {
bt_gatt_unsubscribe(conn, params);
}
}
net_buf_simple_pull_mem(&buf, len);
}
}
static void gatt_sub_update(struct bt_conn *conn, struct gatt_sub *sub)
{
if (sub->peer.type == BT_ADDR_LE_PUBLIC) {
@ -3333,12 +3518,83 @@ static int gatt_read_mult(struct bt_conn *conn,
return gatt_send(conn, buf, gatt_read_mult_rsp, params, NULL);
}
#if defined(CONFIG_BT_EATT)
static void gatt_read_mult_vl_rsp(struct bt_conn *conn, u8_t err,
const void *pdu, u16_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;
BT_DBG("err 0x%02x", err);
if (err || !length) {
if (err == BT_ATT_ERR_NOT_SUPPORTED) {
gatt_read_mult(conn, params);
}
params->func(conn, err, params, NULL, 0);
return;
}
net_buf_simple_init_with_data(&buf, (void *)pdu, length);
while (buf.len >= sizeof(*rsp)) {
u16_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(struct bt_conn *conn,
struct bt_gatt_read_params *params)
{
struct net_buf *buf;
u8_t i;
buf = bt_att_create_pdu(conn, BT_ATT_OP_READ_MULT_VL_REQ,
params->handle_count * sizeof(u16_t));
if (!buf) {
return -ENOMEM;
}
for (i = 0U; i < params->handle_count; i++) {
net_buf_add_le16(buf, params->handles[i]);
}
return gatt_send(conn, buf, gatt_read_mult_vl_rsp, params, NULL);
}
#endif /* CONFIG_BT_EATT */
#else
static int gatt_read_mult(struct bt_conn *conn,
struct bt_gatt_read_params *params)
{
return -ENOTSUP;
}
static int gatt_read_mult_vl(struct bt_conn *conn,
struct bt_gatt_read_params *params)
{
return -ENOTSUP;
}
#endif /* CONFIG_BT_GATT_READ_MULTIPLE */
int bt_gatt_read(struct bt_conn *conn, struct bt_gatt_read_params *params)
@ -3358,7 +3614,11 @@ int bt_gatt_read(struct bt_conn *conn, struct bt_gatt_read_params *params)
}
if (params->handle_count > 1) {
#if defined(CONFIG_BT_EATT)
return gatt_read_mult_vl(conn, params);
#else
return gatt_read_mult(conn, params);
#endif /* CONFIG_BT_EATT */
}
if (params->single.offset) {

View file

@ -36,11 +36,19 @@ int bt_gatt_clear(u8_t id, const bt_addr_le_t *addr);
#if defined(CONFIG_BT_GATT_CLIENT)
void bt_gatt_notification(struct bt_conn *conn, u16_t handle,
const void *data, u16_t length);
void bt_gatt_mult_notification(struct bt_conn *conn, const void *data,
u16_t length);
#else
static inline void bt_gatt_notification(struct bt_conn *conn, u16_t handle,
const void *data, u16_t length)
{
}
static inline void bt_gatt_mult_notification(struct bt_conn *conn,
const void *data, u16_t length)
{
}
#endif /* CONFIG_BT_GATT_CLIENT */
struct bt_gatt_attr;