incubator-nuttx/wireless/bluetooth/bt_gatt.c

1241 lines
30 KiB
C

/****************************************************************************
* wireless/bluetooth/bt_gatt.c
* Generic Attribute Profile handling.
*
* Copyright (C) 2018 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* Ported from the Intel/Zephyr arduino101_firmware_source-v1.tar package
* where the code was released with a compatible 3-clause BSD license:
*
* Copyright (c) 2016, Intel Corporation
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/wireless/bluetooth/bt_hci.h>
#include <nuttx/wireless/bluetooth/bt_core.h>
#include <nuttx/wireless/bluetooth/bt_buf.h>
#include <nuttx/wireless/bluetooth/bt_uuid.h>
#include <nuttx/wireless/bluetooth/bt_gatt.h>
#include "bt_hcicore.h"
#include "bt_conn.h"
#include "bt_keys.h"
#include "bt_l2cap.h"
#include "bt_att.h"
/****************************************************************************
* Private Types
****************************************************************************/
begin_packed_struct struct gatt_incl_s
{
uint16_t start_handle;
uint16_t end_handle;
union
{
uint16_t uuid16;
uint8_t uuid[16];
} u;
} end_packed_struct;
begin_packed_struct struct gatt_chrc_s
{
uint8_t properties;
uint16_t value_handle;
union
{
uint16_t uuid16;
uint8_t uuid[16];
} u;
} end_packed_struct;
struct notify_data_s
{
FAR const void *data;
size_t len;
uint8_t handle;
};
/****************************************************************************
* Private Data
****************************************************************************/
static FAR const struct bt_gatt_attr_s *g_db = NULL;
static size_t g_attr_count = 0;
/****************************************************************************
* Public Functions
****************************************************************************/
void bt_gatt_register(FAR const struct bt_gatt_attr_s *attrs, size_t count)
{
g_db = attrs;
g_attr_count = count;
}
int bt_gatt_attr_read(FAR struct bt_conn_s *conn,
FAR const struct bt_gatt_attr_s *attr, FAR void *buf,
uint8_t buf_len, uint16_t offset,
FAR const void *value, uint8_t value_len)
{
uint8_t maxlen;
uint8_t len;
if (offset > value_len)
{
return -EINVAL;
}
len = buf_len;
maxlen = value_len - offset;
if (len > maxlen)
{
len = maxlen;
}
wlinfo("handle 0x%04x offset %u length %u\n", attr->handle, offset, len);
memcpy(buf, value + offset, len);
return len;
}
int bt_gatt_attr_read_service(FAR struct bt_conn_s *conn,
FAR const struct bt_gatt_attr_s *attr,
FAR void *buf, uint8_t len, uint16_t offset)
{
FAR struct bt_uuid_s *uuid = attr->user_data;
if (uuid->type == BT_UUID_16)
{
uint16_t uuid16 = BT_HOST2LE16(uuid->u.u16);
return bt_gatt_attr_read(conn, attr, buf, len, offset, &uuid16,
sizeof(uuid16));
}
return bt_gatt_attr_read(conn, attr, buf, len, offset, uuid->u.u128,
sizeof(uuid->u.u128));
}
int bt_gatt_attr_read_include(FAR struct bt_conn_s *conn,
FAR const struct bt_gatt_attr_s *attr,
FAR void *buf, uint8_t len, uint16_t offset)
{
FAR struct bt_gatt_include_s *incl = attr->user_data;
struct gatt_incl_s pdu;
uint8_t value_len;
pdu.start_handle = BT_HOST2LE16(incl->start_handle);
pdu.end_handle = BT_HOST2LE16(incl->end_handle);
value_len = sizeof(pdu.start_handle) + sizeof(pdu.end_handle);
if (incl->uuid->type == BT_UUID_16)
{
pdu.u.uuid16 = BT_HOST2LE16(incl->uuid->u.u16);
value_len += sizeof(pdu.u.uuid16);
}
else
{
memcpy(pdu.u.uuid, incl->uuid->u.u128, sizeof(incl->uuid->u.u128));
value_len += sizeof(incl->uuid->u.u128);
}
return bt_gatt_attr_read(conn, attr, buf, len, offset, &pdu, value_len);
}
int bt_gatt_attr_read_chrc(FAR struct bt_conn_s *conn,
FAR const struct bt_gatt_attr_s *attr,
FAR void *buf, uint8_t len, uint16_t offset)
{
FAR struct bt_gatt_chrc_s *chrc = attr->user_data;
struct gatt_chrc_s pdu;
uint8_t value_len;
pdu.properties = chrc->properties;
pdu.value_handle = BT_HOST2LE16(chrc->value_handle);
value_len = sizeof(pdu.properties) + sizeof(pdu.value_handle);
if (chrc->uuid->type == BT_UUID_16)
{
pdu.u.uuid16 = BT_HOST2LE16(chrc->uuid->u.u16);
value_len += sizeof(pdu.u.uuid16);
}
else
{
memcpy(pdu.u.uuid, chrc->uuid->u.u128, sizeof(chrc->uuid->u.u128));
value_len += sizeof(chrc->uuid->u.u128);
}
return bt_gatt_attr_read(conn, attr, buf, len, offset, &pdu, value_len);
}
void bt_gatt_foreach_attr(uint16_t start_handle, uint16_t end_handle,
bt_gatt_attr_func_t func, FAR void *user_data)
{
size_t i;
for (i = 0; i < g_attr_count; i++)
{
FAR const struct bt_gatt_attr_s *attr = &g_db[i];
/* Check if attribute handle is within range */
if (attr->handle < start_handle || attr->handle > end_handle)
{
continue;
}
if (func(attr, user_data) == BT_GATT_ITER_STOP)
{
break;
}
}
}
int bt_gatt_attr_read_ccc(FAR struct bt_conn_s *conn,
FAR const struct bt_gatt_attr_s *attr,
FAR void *buf, uint8_t len, uint16_t offset)
{
FAR struct _bt_gatt_ccc_s *ccc = attr->user_data;
uint16_t value;
size_t i;
for (i = 0; i < ccc->cfg_len; i++)
{
if (bt_addr_le_cmp(&ccc->cfg[i].peer, &conn->dst))
{
continue;
}
value = BT_HOST2LE16(ccc->cfg[i].value);
break;
}
/* Default to disable if there is no cfg for the peer */
if (i == ccc->cfg_len)
{
value = 0x0000;
}
return bt_gatt_attr_read(conn, attr, buf, len, offset, &value,
sizeof(value));
}
static void gatt_ccc_changed(FAR struct _bt_gatt_ccc_s *ccc)
{
uint16_t value = 0x0000;
int i;
for (i = 0; i < ccc->cfg_len; i++)
{
if (ccc->cfg[i].value > value)
{
value = ccc->cfg[i].value;
}
}
wlinfo("ccc %p value 0x%04x\n", ccc, value);
if (value != ccc->value)
{
ccc->value = value;
ccc->cfg_changed(value);
}
}
int bt_gatt_attr_write_ccc(FAR struct bt_conn_s *conn,
FAR const struct bt_gatt_attr_s *attr,
FAR const void *buf, uint8_t len,
uint16_t offset)
{
FAR struct _bt_gatt_ccc_s *ccc = attr->user_data;
FAR const uint16_t *data = buf;
bool bonded;
size_t i;
if (len != sizeof(*data) || offset)
{
return -EINVAL;
}
if (bt_keys_get_addr(&conn->dst))
{
bonded = true;
}
else
{
bonded = false;
}
for (i = 0; i < ccc->cfg_len; i++)
{
/* Check for existing configuration */
if (!bt_addr_le_cmp(&ccc->cfg[i].peer, &conn->dst))
{
break;
}
}
if (i == ccc->cfg_len)
{
for (i = 0; i < ccc->cfg_len; i++)
{
/* Check for unused configuration */
if (!ccc->cfg[i].valid)
{
bt_addr_le_copy(&ccc->cfg[i].peer, &conn->dst);
/* Only set valid if bonded */
ccc->cfg[i].valid = bonded;
break;
}
}
if (i == ccc->cfg_len)
{
wlwarn("No space to store CCC cfg\n");
return -ENOMEM;
}
}
ccc->cfg[i].value = BT_LE162HOST(*data);
wlinfo("handle 0x%04x value %u\n", attr->handle, ccc->cfg[i].value);
/* Update cfg if don't match */
if (ccc->cfg[i].value != ccc->value)
{
gatt_ccc_changed(ccc);
}
return len;
}
int bt_gatt_attr_read_cep(FAR struct bt_conn_s *conn,
FAR const struct bt_gatt_attr_s *attr,
FAR void *buf, uint8_t len, uint16_t offset)
{
FAR struct bt_gatt_cep_s *value = attr->user_data;
uint16_t props = BT_HOST2LE16(value->properties);
return bt_gatt_attr_read(conn, attr, buf, len, offset, &props,
sizeof(props));
}
static uint8_t notify_cb(FAR const struct bt_gatt_attr_s *attr,
FAR void *user_data)
{
FAR struct notify_data_s *data = user_data;
const struct bt_uuid_s uuid =
{
BT_UUID_16,
{
BT_UUID_GATT_CCC
}
};
const struct bt_uuid_s chrc =
{
BT_UUID_16,
{
BT_UUID_GATT_CHRC
}
};
FAR struct _bt_gatt_ccc_s *ccc;
size_t i;
if (bt_uuid_cmp(attr->uuid, &uuid))
{
/* Stop if we reach the next characteristic */
if (!bt_uuid_cmp(attr->uuid, &chrc))
{
return BT_GATT_ITER_STOP;
}
return BT_GATT_ITER_CONTINUE;
}
/* Check attribute user_data must be of type struct _bt_gatt_ccc_s */
if (attr->write != bt_gatt_attr_write_ccc)
{
return BT_GATT_ITER_CONTINUE;
}
ccc = attr->user_data;
/* Notify all peers configured */
for (i = 0; i < ccc->cfg_len; i++)
{
FAR struct bt_conn_s *conn;
FAR struct bt_buf_s *buf;
FAR struct bt_att_notify_s *nfy;
/* TODO: Handle indications */
if (ccc->value != BT_GATT_CCC_NOTIFY)
{
continue;
}
conn = bt_conn_lookup_addr_le(&ccc->cfg[i].peer);
if (!conn || conn->state != BT_CONN_CONNECTED)
{
continue;
}
buf = bt_att_create_pdu(conn, BT_ATT_OP_NOTIFY,
sizeof(*nfy) + data->len);
if (!buf)
{
wlwarn("No buffer available to send notification");
bt_conn_release(conn);
return BT_GATT_ITER_STOP;
}
wlinfo("conn %p handle 0x%04x\n", conn, data->handle);
nfy = bt_buf_extend(buf, sizeof(*nfy));
nfy->handle = BT_HOST2LE16(data->handle);
bt_buf_extend(buf, data->len);
memcpy(nfy->value, data->data, data->len);
bt_l2cap_send(conn, BT_L2CAP_CID_ATT, buf);
bt_conn_release(conn);
}
return BT_GATT_ITER_CONTINUE;
}
void bt_gatt_notify(uint16_t handle, FAR const void *data, size_t len)
{
struct notify_data_s nfy;
nfy.handle = handle;
nfy.data = data;
nfy.len = len;
bt_gatt_foreach_attr(handle, 0xffff, notify_cb, &nfy);
}
static uint8_t connected_cb(FAR const struct bt_gatt_attr_s *attr,
FAR void *user_data)
{
FAR struct bt_conn_s *conn = user_data;
FAR struct _bt_gatt_ccc_s *ccc;
size_t i;
/* Check attribute user_data must be of type struct _bt_gatt_ccc_s */
if (attr->write != bt_gatt_attr_write_ccc)
{
return BT_GATT_ITER_CONTINUE;
}
ccc = attr->user_data;
/* If already enabled skip */
if (ccc->value)
{
return BT_GATT_ITER_CONTINUE;
}
for (i = 0; i < ccc->cfg_len; i++)
{
/* Ignore configuration for different peer */
if (bt_addr_le_cmp(&conn->dst, &ccc->cfg[i].peer))
{
continue;
}
if (ccc->cfg[i].value)
{
gatt_ccc_changed(ccc);
return BT_GATT_ITER_CONTINUE;
}
}
return BT_GATT_ITER_CONTINUE;
}
void bt_gatt_connected(FAR struct bt_conn_s *conn)
{
wlinfo("conn %p\n", conn);
bt_gatt_foreach_attr(0x0001, 0xffff, connected_cb, conn);
}
static uint8_t disconnected_cb(FAR const struct bt_gatt_attr_s *attr,
FAR void *user_data)
{
FAR struct bt_conn_s *conn = user_data;
FAR struct _bt_gatt_ccc_s *ccc;
size_t i;
/* Check attribute user_data must be of type struct _bt_gatt_ccc_s */
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;
}
for (i = 0; i < ccc->cfg_len; i++)
{
/* Ignore configurations with disabled value */
if (!ccc->cfg[i].value)
{
continue;
}
if (bt_addr_le_cmp(&conn->dst, &ccc->cfg[i].peer))
{
FAR struct bt_conn_s *tmp;
/* Skip if there is another peer connected */
tmp = bt_conn_lookup_addr_le(&ccc->cfg[i].peer);
if (tmp && tmp->state == BT_CONN_CONNECTED)
{
bt_conn_release(tmp);
return BT_GATT_ITER_CONTINUE;
}
}
}
/* Reset value while disconnected */
memset(&ccc->value, 0, sizeof(ccc->value));
ccc->cfg_changed(ccc->value);
wlinfo("ccc %p reseted\n", ccc);
return BT_GATT_ITER_CONTINUE;
}
void bt_gatt_disconnected(FAR struct bt_conn_s *conn)
{
wlinfo("conn %p\n", conn);
bt_gatt_foreach_attr(0x0001, 0xffff, disconnected_cb, conn);
}
static void gatt_mtu_rsp(FAR struct bt_conn_s *conn, uint8_t err,
FAR const void *pdu, uint16_t length,
FAR void *user_data)
{
bt_gatt_rsp_func_t func = user_data;
func(conn, err);
}
static int gatt_send(FAR struct bt_conn_s *conn, FAR struct bt_buf_s *buf,
bt_att_func_t func, FAR void *user_data,
bt_att_destroy_t destroy)
{
int err;
err = bt_att_send(conn, buf, func, user_data, destroy);
if (err)
{
wlerr("ERROR: Error sending ATT PDU: %d\n", err);
bt_buf_release(buf);
}
return err;
}
int bt_gatt_exchange_mtu(FAR struct bt_conn_s *conn,
bt_gatt_rsp_func_t func)
{
FAR struct bt_att_exchange_mtu_req_s *req;
FAR struct bt_buf_s *buf;
uint16_t mtu;
if (!conn || !func)
{
return -EINVAL;
}
buf = bt_att_create_pdu(conn, BT_ATT_OP_MTU_REQ, sizeof(*req));
if (!buf)
{
return -ENOMEM;
}
/* Select MTU based on the amount of room we have in bt_buf_s including one
* extra byte for ATT header.
*/
mtu = bt_buf_tailroom(buf) + 1;
wlinfo("Client MTU %u\n", mtu);
req = bt_buf_extend(buf, sizeof(*req));
req->mtu = BT_HOST2LE16(mtu);
return gatt_send(conn, buf, gatt_mtu_rsp, func, NULL);
}
static void att_find_type_rsp(FAR struct bt_conn_s *conn, uint8_t err,
FAR const void *pdu, uint16_t length,
FAR void *user_data)
{
FAR const struct bt_att_find_type_rsp_s *rsp = pdu;
FAR struct bt_gatt_discover_params_s *params = user_data;
uint16_t end_handle = 0;
uint16_t start_handle;
uint8_t i;
wlinfo("err 0x%02x\n", err);
if (err)
{
goto done;
}
/* Parse attributes found */
for (i = 0; length >= sizeof(rsp->list[i]);
i++, length -= sizeof(rsp->list[i]))
{
FAR const struct bt_gatt_attr_s *attr;
start_handle = BT_LE162HOST(rsp->list[i].start_handle);
end_handle = BT_LE162HOST(rsp->list[i].end_handle);
wlinfo("start_handle 0x%04x end_handle 0x%04x\n", start_handle,
end_handle);
attr =
(&(struct bt_gatt_attr_s)BT_GATT_PRIMARY_SERVICE
(start_handle, params->uuid));
if (params->func(attr, params) == BT_GATT_ITER_STOP)
{
goto done;
}
}
/* Stop if could not parse the whole PDU */
if (length > 0)
{
goto done;
}
/* Stop if over the range or the requests */
if (end_handle >= params->end_handle)
{
goto done;
}
/* Continue for the last found handle */
params->start_handle = end_handle;
if (!bt_gatt_discover(conn, params))
{
return;
}
done:
if (params->destroy)
{
params->destroy(params);
}
}
/****************************************************************************
* Name: bt_gatt_discover
*
* Description:
* This function implements the SIOCBTDISCOVER ioctl command for the
* GATT discovery.
*
****************************************************************************/
int bt_gatt_discover(FAR struct bt_conn_s *conn,
FAR struct bt_gatt_discover_params_s *params)
{
FAR struct bt_buf_s *buf;
FAR struct bt_att_find_type_req_s *req;
FAR uint16_t *value;
if (!conn || !params->uuid || !params->func || !params->start_handle ||
!params->end_handle)
{
return -EINVAL;
}
buf = bt_att_create_pdu(conn, BT_ATT_OP_FIND_TYPE_REQ, sizeof(*req));
if (!buf)
{
return -ENOMEM;
}
req = bt_buf_extend(buf, sizeof(*req));
req->start_handle = BT_HOST2LE16(params->start_handle);
req->end_handle = BT_HOST2LE16(params->end_handle);
req->type = BT_HOST2LE16(BT_UUID_GATT_PRIMARY);
wlinfo("uuid 0x%04x start_handle 0x%04x end_handle 0x%04x\n",
params->uuid->u.u16, params->start_handle, params->end_handle);
switch (params->uuid->type)
{
case BT_UUID_16:
value = bt_buf_extend(buf, sizeof(*value));
*value = BT_HOST2LE16(params->uuid->u.u16);
break;
case BT_UUID_128:
bt_buf_extend(buf, sizeof(params->uuid->u.u128));
memcpy(req->value, params->uuid->u.u128, sizeof(params->uuid->u.u128));
break;
default:
wlerr("ERROR: Unkown UUID type %u\n", params->uuid->type);
bt_buf_release(buf);
return -EINVAL;
}
return gatt_send(conn, buf, att_find_type_rsp, params, NULL);
}
static void att_read_type_rsp(FAR struct bt_conn_s *conn, uint8_t err,
FAR const void *pdu, uint16_t length,
FAR void *user_data)
{
FAR const struct bt_att_read_type_rsp_s *rsp = pdu;
FAR struct bt_gatt_discover_params_s *params = user_data;
struct bt_uuid_s uuid;
struct bt_gatt_chrc_s value;
uint16_t handle = 0;
wlinfo("err 0x%02x\n", err);
if (err)
{
goto done;
}
/* Data can be either in UUID16 or UUID128 */
switch (rsp->len)
{
case 7: /* UUID16 */
uuid.type = BT_UUID_16;
break;
case 21: /* UUID128 */
uuid.type = BT_UUID_128;
break;
default:
wlerr("ERROR: Invalid data len %u\n", rsp->len);
goto done;
}
/* Parse characteristics found */
for (length--, pdu = rsp->data; length >= rsp->len;
length -= rsp->len, pdu += rsp->len)
{
FAR const struct bt_gatt_attr_s *attr;
FAR const struct bt_att_data_s *data = pdu;
FAR struct gatt_chrc_s *chrc = (FAR void *)data->value;
handle = BT_LE162HOST(data->handle);
/* Handle 0 is invalid */
if (!handle)
{
goto done;
}
/* Convert characteristic data, bt_gatt_chrc and gatt_chrc_s have different
* formats so the conversion have to be done field by field.
*/
value.properties = chrc->properties;
value.value_handle = BT_LE162HOST(chrc->value_handle);
value.uuid = &uuid;
switch (uuid.type)
{
case BT_UUID_16:
uuid.u.u16 = BT_LE162HOST(chrc->u.uuid16);
break;
case BT_UUID_128:
memcpy(uuid.u.u128, chrc->u.uuid, sizeof(chrc->u.uuid));
break;
}
wlinfo("handle 0x%04x properties 0x%02x value_handle 0x%04x\n",
handle, value.properties, value.value_handle);
/* Skip if UUID is set but doesn't match */
if (params->uuid && bt_uuid_cmp(&uuid, params->uuid))
{
continue;
}
attr = (&(struct bt_gatt_attr_s)BT_GATT_CHARACTERISTIC(handle, &value));
if (params->func(attr, params) == BT_GATT_ITER_STOP)
{
goto done;
}
}
/* Stop if could not parse the whole PDU */
if (length > 0)
{
goto done;
}
/* Stop if over the requested range */
if (params->start_handle >= params->end_handle)
{
goto done;
}
/* Continue to the next range */
if (!bt_gatt_discover_characteristic(conn, params))
{
return;
}
done:
if (params->destroy)
{
params->destroy(params);
}
}
/****************************************************************************
* Name: bt_gatt_discover_characteristic
*
* Description:
* This function implements the SIOCBTDISCOVER ioctl command for the
* GATT discover characteristics type.
*
****************************************************************************/
int bt_gatt_discover_characteristic(FAR struct bt_conn_s *conn,
FAR struct bt_gatt_discover_params_s *params)
{
FAR struct bt_buf_s *buf;
FAR struct bt_att_read_type_req_s *req;
FAR uint16_t *value;
if (!conn || !params->func || !params->start_handle || !params->end_handle)
{
return -EINVAL;
}
buf = bt_att_create_pdu(conn, BT_ATT_OP_READ_TYPE_REQ, sizeof(*req));
if (!buf)
{
return -ENOMEM;
}
req = bt_buf_extend(buf, sizeof(*req));
req->start_handle = BT_HOST2LE16(params->start_handle);
req->end_handle = BT_HOST2LE16(params->end_handle);
value = bt_buf_extend(buf, sizeof(*value));
*value = BT_HOST2LE16(BT_UUID_GATT_CHRC);
wlinfo("start_handle 0x%04x end_handle 0x%04x\n", params->start_handle,
params->end_handle);
return gatt_send(conn, buf, att_read_type_rsp, params, NULL);
}
static void att_find_info_rsp(FAR struct bt_conn_s *conn, uint8_t err,
FAR const void *pdu, uint16_t length,
FAR void *user_data)
{
FAR const struct bt_att_find_info_rsp_s *rsp = pdu;
FAR struct bt_gatt_discover_params_s *params = user_data;
struct bt_uuid_s uuid;
uint16_t handle = 0;
uint8_t len;
union
{
FAR const struct bt_att_info_16_s *i16;
FAR const struct bt_att_info_128_s *i128;
} info;
wlinfo("err 0x%02x\n", err);
if (err)
{
goto done;
}
/* Data can be either in UUID16 or UUID128 */
switch (rsp->format)
{
case BT_ATT_INFO_16:
uuid.type = BT_UUID_16;
len = sizeof(info.i16);
break;
case BT_ATT_INFO_128:
uuid.type = BT_UUID_128;
len = sizeof(info.i128);
break;
default:
wlerr("ERROR: Invalid format %u\n", rsp->format);
goto done;
}
/* Parse descriptors found */
for (length--, pdu = rsp->info; length >= len; length -= len, pdu += len)
{
FAR const struct bt_gatt_attr_s *attr;
info.i16 = pdu;
handle = BT_LE162HOST(info.i16->handle);
switch (uuid.type)
{
case BT_UUID_16:
uuid.u.u16 = BT_LE162HOST(info.i16->uuid);
break;
case BT_UUID_128:
memcpy(uuid.u.u128, info.i128->uuid, sizeof(uuid.u.u128));
break;
}
wlinfo("handle 0x%04x\n", handle);
/* Skip if UUID is set but doesn't match */
if (params->uuid && bt_uuid_cmp(&uuid, params->uuid))
{
continue;
}
attr =
(&(struct bt_gatt_attr_s)BT_GATT_DESCRIPTOR
(handle, &uuid, 0, NULL, NULL, NULL));
if (params->func(attr, params) == BT_GATT_ITER_STOP)
{
goto done;
}
}
/* Stop if could not parse the whole PDU */
if (length > 0)
{
goto done;
}
/* Next characteristic shall be after current value handle */
params->start_handle = handle;
if (params->start_handle < UINT16_MAX)
{
params->start_handle++;
}
/* Stop if over the requested range */
if (params->start_handle >= params->end_handle)
{
goto done;
}
/* Continue to the next range */
if (!bt_gatt_discover_descriptor(conn, params))
{
return;
}
done:
if (params->destroy)
{
params->destroy(params);
}
}
/****************************************************************************
* Name: bt_gatt_discover_descriptor
*
* Description:
* This function implements the SIOCBTDISCOVER ioctl command for the
* GATT discover descriptor type.
*
****************************************************************************/
int bt_gatt_discover_descriptor(FAR struct bt_conn_s *conn,
FAR struct bt_gatt_discover_params_s *params)
{
FAR struct bt_buf_s *buf;
FAR struct bt_att_find_info_req_s *req;
if (!conn || !params->func || !params->start_handle || !params->end_handle)
{
return -EINVAL;
}
buf = bt_att_create_pdu(conn, BT_ATT_OP_FIND_INFO_REQ, sizeof(*req));
if (!buf)
{
return -ENOMEM;
}
req = bt_buf_extend(buf, sizeof(*req));
req->start_handle = BT_HOST2LE16(params->start_handle);
req->end_handle = BT_HOST2LE16(params->end_handle);
wlinfo("start_handle 0x%04x end_handle 0x%04x\n", params->start_handle,
params->end_handle);
return gatt_send(conn, buf, att_find_info_rsp, params, NULL);
}
static void att_read_rsp(FAR struct bt_conn_s *conn, uint8_t err,
FAR const void *pdu, uint16_t length,
FAR void *user_data)
{
bt_gatt_read_func_t func = user_data;
wlinfo("err 0x%02x\n", err);
if (err)
{
func(conn, err, NULL, 0);
return;
}
func(conn, 0, pdu, length);
}
static int gatt_read_blob(FAR struct bt_conn_s *conn, uint16_t handle,
uint16_t offset, bt_gatt_read_func_t func)
{
FAR struct bt_buf_s *buf;
FAR struct bt_att_read_blob_req_s *req;
buf = bt_att_create_pdu(conn, BT_ATT_OP_READ_BLOB_REQ, sizeof(*req));
if (!buf)
{
return -ENOMEM;
}
req = bt_buf_extend(buf, sizeof(*req));
req->handle = BT_HOST2LE16(handle);
req->offset = BT_HOST2LE16(offset);
wlinfo("handle 0x%04x offset 0x%04x\n", handle, offset);
return gatt_send(conn, buf, att_read_rsp, func, NULL);
}
int bt_gatt_read(FAR struct bt_conn_s *conn, uint16_t handle,
uint16_t offset, bt_gatt_read_func_t func)
{
FAR struct bt_buf_s *buf;
FAR struct bt_att_read_req_s *req;
if (!conn || !handle || !func)
{
return -EINVAL;
}
if (offset)
{
return gatt_read_blob(conn, handle, offset, func);
}
buf = bt_att_create_pdu(conn, BT_ATT_OP_READ_REQ, sizeof(*req));
if (!buf)
{
return -ENOMEM;
}
req = bt_buf_extend(buf, sizeof(*req));
req->handle = BT_HOST2LE16(handle);
wlinfo("handle 0x%04x\n", handle);
return gatt_send(conn, buf, att_read_rsp, func, NULL);
}
static void att_write_rsp(FAR struct bt_conn_s *conn, uint8_t err,
FAR const void *pdu, uint16_t length,
FAR void *user_data)
{
bt_gatt_rsp_func_t func = user_data;
wlinfo("err 0x%02x\n", err);
func(conn, err);
}
static int gatt_write_cmd(FAR struct bt_conn_s *conn, uint16_t handle,
FAR const void *data, uint16_t length)
{
FAR struct bt_buf_s *buf;
FAR struct bt_att_write_cmd_s *cmd;
buf = bt_att_create_pdu(conn, BT_ATT_OP_WRITE_CMD, sizeof(*cmd) + length);
if (!buf)
{
return -ENOMEM;
}
cmd = bt_buf_extend(buf, sizeof(*cmd));
cmd->handle = BT_HOST2LE16(handle);
memcpy(cmd->value, data, length);
bt_buf_extend(buf, length);
wlinfo("handle 0x%04x length %u\n", handle, length);
return gatt_send(conn, buf, NULL, NULL, NULL);
}
int bt_gatt_write(FAR struct bt_conn_s *conn, uint16_t handle,
FAR const void *data, uint16_t length,
bt_gatt_rsp_func_t func)
{
FAR struct bt_buf_s *buf;
FAR struct bt_att_write_req_s *req;
if (!conn || !handle)
{
return -EINVAL;
}
if (!func)
{
return gatt_write_cmd(conn, handle, data, length);
}
buf = bt_att_create_pdu(conn, BT_ATT_OP_WRITE_REQ, sizeof(*req) + length);
if (!buf)
{
return -ENOMEM;
}
req = bt_buf_extend(buf, sizeof(*req));
req->handle = BT_HOST2LE16(handle);
memcpy(req->value, data, length);
bt_buf_extend(buf, length);
wlinfo("handle 0x%04x length %u\n", handle, length);
return gatt_send(conn, buf, att_write_rsp, func, NULL);
}
void bt_gatt_cancel(FAR struct bt_conn_s *conn)
{
bt_att_cancel(conn);
}
int bt_gatt_read_multiple(FAR struct bt_conn_s *conn,
FAR const uint16_t *handles, size_t count,
bt_gatt_read_func_t func)
{
FAR struct bt_buf_s *buf;
uint8_t i;
if (!conn && conn->state != BT_CONN_CONNECTED)
{
return -ENOTCONN;
}
if (!handles || count < 2 || !func)
{
return -EINVAL;
}
buf = bt_att_create_pdu(conn, BT_ATT_OP_READ_MULT_REQ,
count * sizeof(*handles));
if (!buf)
{
return -ENOMEM;
}
for (i = 0; i < count; i++)
{
bt_buf_put_le16(buf, handles[i]);
}
return gatt_send(conn, buf, att_read_rsp, func, NULL);
}