blob: aa8367b6fb90ae8670aa88ad10eb09992a7b6711 [file] [log] [blame]
/* gatt.c - Bluetooth GATT Server Tester */
/*
* Copyright (c) 2015-2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/types.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <zephyr/toolchain.h>
#include <zephyr/bluetooth/att.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/l2cap.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/net/buf.h>
#include <zephyr/logging/log.h>
#define LOG_MODULE_NAME bttester_gatt
LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_BTTESTER_LOG_LEVEL);
#include "btp/btp.h"
#define MAX_BUFFER_SIZE 2048
#define MAX_UUID_LEN 16
#define MAX_SUBSCRIPTIONS 2
#define UNUSED_SUBSCRIBE_CCC_HANDLE 0x0000
/* This masks Permission bits from GATT API */
#define GATT_PERM_MASK (BT_GATT_PERM_READ | \
BT_GATT_PERM_READ_AUTHEN | \
BT_GATT_PERM_READ_ENCRYPT | \
BT_GATT_PERM_WRITE | \
BT_GATT_PERM_WRITE_AUTHEN | \
BT_GATT_PERM_WRITE_ENCRYPT | \
BT_GATT_PERM_PREPARE_WRITE)
#define GATT_PERM_ENC_READ_MASK (BT_GATT_PERM_READ_ENCRYPT | \
BT_GATT_PERM_READ_AUTHEN)
#define GATT_PERM_ENC_WRITE_MASK (BT_GATT_PERM_WRITE_ENCRYPT | \
BT_GATT_PERM_WRITE_AUTHEN)
#define GATT_PERM_READ_AUTHORIZATION 0x40
#define GATT_PERM_WRITE_AUTHORIZATION 0x80
/* GATT server context */
#define SERVER_MAX_SERVICES 10
#define SERVER_MAX_ATTRIBUTES 50
#define SERVER_BUF_SIZE 2048
#define MAX_CCC_COUNT 2
/* bt_gatt_attr_next cannot be used on non-registered services */
#define NEXT_DB_ATTR(attr) (attr + 1)
#define LAST_DB_ATTR (server_db + (attr_count - 1))
#define server_buf_push(_len) net_buf_push(server_buf, ROUND_UP(_len, 4))
#define server_buf_pull(_len) net_buf_pull(server_buf, ROUND_UP(_len, 4))
static struct bt_gatt_service server_svcs[SERVER_MAX_SERVICES];
static struct bt_gatt_attr server_db[SERVER_MAX_ATTRIBUTES];
static struct net_buf *server_buf;
NET_BUF_POOL_DEFINE(server_pool, 1, SERVER_BUF_SIZE, 0, NULL);
static uint8_t attr_count;
static uint8_t svc_attr_count;
static uint8_t svc_count;
static bool ccc_added;
/*
* gatt_buf - cache used by a gatt client (to cache data read/discovered)
* and gatt server (to store attribute user_data).
* It is not intended to be used by client and server at the same time.
*/
static struct {
uint16_t len;
uint8_t buf[MAX_BUFFER_SIZE];
} gatt_buf;
struct get_attr_data {
struct net_buf_simple *buf;
struct bt_conn *conn;
};
struct ccc_value {
struct bt_gatt_attr *attr;
struct bt_gatt_attr *ccc;
uint8_t value;
};
static struct ccc_value ccc_values[MAX_CCC_COUNT];
static int ccc_find_by_attr(uint16_t handle)
{
for (int i = 0; i < MAX_CCC_COUNT; i++) {
if (handle == ccc_values[i].attr->handle) {
return i;
}
}
return -ENOENT;
}
static int ccc_find_by_ccc(const struct bt_gatt_attr *attr)
{
for (int i = 0; i < MAX_CCC_COUNT; i++) {
if (attr == ccc_values[i].ccc) {
return i;
}
}
return -ENOENT;
}
static void *gatt_buf_add(const void *data, size_t len)
{
void *ptr = gatt_buf.buf + gatt_buf.len;
if ((len + gatt_buf.len) > MAX_BUFFER_SIZE) {
return NULL;
}
if (data) {
memcpy(ptr, data, len);
} else {
(void)memset(ptr, 0, len);
}
gatt_buf.len += len;
LOG_DBG("%d/%d used", gatt_buf.len, MAX_BUFFER_SIZE);
return ptr;
}
static void *gatt_buf_reserve(size_t len)
{
return gatt_buf_add(NULL, len);
}
static void gatt_buf_clear(void)
{
(void)memset(&gatt_buf, 0, sizeof(gatt_buf));
}
union uuid {
struct bt_uuid uuid;
struct bt_uuid_16 u16;
struct bt_uuid_128 u128;
};
static struct bt_gatt_attr *gatt_db_add(const struct bt_gatt_attr *pattern,
size_t user_data_len)
{
static struct bt_gatt_attr *attr = server_db;
const union uuid *u = CONTAINER_OF(pattern->uuid, union uuid, uuid);
size_t uuid_size = u->uuid.type == BT_UUID_TYPE_16 ? sizeof(u->u16) :
sizeof(u->u128);
/* Return NULL if database is full */
if (attr == &server_db[SERVER_MAX_ATTRIBUTES - 1]) {
return NULL;
}
/* First attribute in db must be service */
if (!svc_count) {
return NULL;
}
memcpy(attr, pattern, sizeof(*attr));
/* Store the UUID. */
attr->uuid = server_buf_push(uuid_size);
memcpy((void *) attr->uuid, &u->uuid, uuid_size);
/* Copy user_data to the buffer. */
if (user_data_len) {
attr->user_data = server_buf_push(user_data_len);
memcpy(attr->user_data, pattern->user_data, user_data_len);
}
LOG_DBG("handle 0x%04x", attr->handle);
attr_count++;
svc_attr_count++;
return attr++;
}
/* Convert UUID from BTP command to bt_uuid */
static uint8_t btp2bt_uuid(const uint8_t *uuid, uint8_t len,
struct bt_uuid *bt_uuid)
{
uint16_t le16;
switch (len) {
case 0x02: /* UUID 16 */
bt_uuid->type = BT_UUID_TYPE_16;
memcpy(&le16, uuid, sizeof(le16));
BT_UUID_16(bt_uuid)->val = sys_le16_to_cpu(le16);
break;
case 0x10: /* UUID 128*/
bt_uuid->type = BT_UUID_TYPE_128;
memcpy(BT_UUID_128(bt_uuid)->val, uuid, 16);
break;
default:
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
static uint8_t supported_commands(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
struct btp_gatt_read_supported_commands_rp *rp = rsp;
/* octet 0 */
tester_set_bit(rp->data, BTP_GATT_READ_SUPPORTED_COMMANDS);
tester_set_bit(rp->data, BTP_GATT_ADD_SERVICE);
tester_set_bit(rp->data, BTP_GATT_ADD_CHARACTERISTIC);
tester_set_bit(rp->data, BTP_GATT_ADD_DESCRIPTOR);
tester_set_bit(rp->data, BTP_GATT_ADD_INCLUDED_SERVICE);
tester_set_bit(rp->data, BTP_GATT_SET_VALUE);
tester_set_bit(rp->data, BTP_GATT_START_SERVER);
/* octet 1 */
tester_set_bit(rp->data, BTP_GATT_SET_ENC_KEY_SIZE);
tester_set_bit(rp->data, BTP_GATT_EXCHANGE_MTU);
tester_set_bit(rp->data, BTP_GATT_DISC_ALL_PRIM);
tester_set_bit(rp->data, BTP_GATT_DISC_PRIM_UUID);
tester_set_bit(rp->data, BTP_GATT_FIND_INCLUDED);
tester_set_bit(rp->data, BTP_GATT_DISC_ALL_CHRC);
tester_set_bit(rp->data, BTP_GATT_DISC_CHRC_UUID);
/* octet 2 */
tester_set_bit(rp->data, BTP_GATT_DISC_ALL_DESC);
tester_set_bit(rp->data, BTP_GATT_READ);
tester_set_bit(rp->data, BTP_GATT_READ_LONG);
tester_set_bit(rp->data, BTP_GATT_READ_MULTIPLE);
tester_set_bit(rp->data, BTP_GATT_WRITE_WITHOUT_RSP);
tester_set_bit(rp->data, BTP_GATT_SIGNED_WRITE_WITHOUT_RSP);
tester_set_bit(rp->data, BTP_GATT_WRITE);
/* octet 3 */
tester_set_bit(rp->data, BTP_GATT_WRITE_LONG);
tester_set_bit(rp->data, BTP_GATT_CFG_NOTIFY);
tester_set_bit(rp->data, BTP_GATT_CFG_INDICATE);
tester_set_bit(rp->data, BTP_GATT_GET_ATTRIBUTES);
tester_set_bit(rp->data, BTP_GATT_GET_ATTRIBUTE_VALUE);
tester_set_bit(rp->data, BTP_GATT_CHANGE_DB);
tester_set_bit(rp->data, BTP_GATT_EATT_CONNECT);
/* octet 4 */
tester_set_bit(rp->data, BTP_GATT_READ_MULTIPLE_VAR);
tester_set_bit(rp->data, BTP_GATT_NOTIFY_MULTIPLE);
*rsp_len = sizeof(*rp) + 5;
return BTP_STATUS_SUCCESS;
}
static int register_service(void)
{
int err;
server_svcs[svc_count].attrs = server_db +
(attr_count - svc_attr_count);
server_svcs[svc_count].attr_count = svc_attr_count;
err = bt_gatt_service_register(&server_svcs[svc_count]);
if (!err) {
/* Service registered, reset the counter */
svc_attr_count = 0U;
}
return err;
}
static uint8_t add_service(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_add_service_cmd *cp = cmd;
struct btp_gatt_add_service_rp *rp = rsp;
struct bt_gatt_attr *attr_svc = NULL;
union uuid uuid;
size_t uuid_size;
if ((cmd_len < sizeof(*cp)) ||
(cmd_len != sizeof(*cp) + cp->uuid_length)) {
return BTP_STATUS_FAILED;
}
if (btp2bt_uuid(cp->uuid, cp->uuid_length, &uuid.uuid)) {
return BTP_STATUS_FAILED;
}
uuid_size = uuid.uuid.type == BT_UUID_TYPE_16 ? sizeof(uuid.u16) :
sizeof(uuid.u128);
/* Register last defined service */
if (svc_attr_count) {
if (register_service()) {
return BTP_STATUS_FAILED;
}
}
svc_count++;
switch (cp->type) {
case BTP_GATT_SERVICE_PRIMARY:
attr_svc = gatt_db_add(&(struct bt_gatt_attr)
BT_GATT_PRIMARY_SERVICE(&uuid.uuid),
uuid_size);
break;
case BTP_GATT_SERVICE_SECONDARY:
attr_svc = gatt_db_add(&(struct bt_gatt_attr)
BT_GATT_SECONDARY_SERVICE(&uuid.uuid),
uuid_size);
break;
}
if (!attr_svc) {
svc_count--;
return BTP_STATUS_FAILED;
}
rp->svc_id = sys_cpu_to_le16(attr_svc->handle);
*rsp_len = sizeof(*rp);
return BTP_STATUS_SUCCESS;
}
struct gatt_value {
uint16_t len;
uint8_t *data;
uint8_t enc_key_size;
uint8_t flags[1];
};
enum {
GATT_VALUE_CCC_FLAG,
GATT_VALUE_READ_AUTHOR_FLAG,
GATT_VALUE_WRITE_AUTHOR_FLAG,
};
static ssize_t read_value(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
const struct gatt_value *value = attr->user_data;
if (tester_test_bit(value->flags, GATT_VALUE_READ_AUTHOR_FLAG)) {
return BT_GATT_ERR(BT_ATT_ERR_AUTHORIZATION);
}
if ((attr->perm & GATT_PERM_ENC_READ_MASK) && (conn != NULL) &&
(value->enc_key_size > bt_conn_enc_key_size(conn))) {
return BT_GATT_ERR(BT_ATT_ERR_ENCRYPTION_KEY_SIZE);
}
return bt_gatt_attr_read(conn, attr, buf, len, offset, value->data,
value->len);
}
static void attr_value_changed_ev(uint16_t handle, const uint8_t *value, uint16_t len)
{
uint8_t buf[len + sizeof(struct btp_gatt_attr_value_changed_ev)];
struct btp_gatt_attr_value_changed_ev *ev = (void *) buf;
ev->handle = sys_cpu_to_le16(handle);
ev->data_length = sys_cpu_to_le16(len);
memcpy(ev->data, value, len);
tester_event(BTP_SERVICE_ID_GATT, BTP_GATT_EV_ATTR_VALUE_CHANGED,
buf, sizeof(buf));
}
static ssize_t write_value(struct bt_conn *conn,
const struct bt_gatt_attr *attr, const void *buf,
uint16_t len, uint16_t offset, uint8_t flags)
{
struct gatt_value *value = attr->user_data;
if (tester_test_bit(value->flags, GATT_VALUE_WRITE_AUTHOR_FLAG)) {
return BT_GATT_ERR(BT_ATT_ERR_AUTHORIZATION);
}
if ((attr->perm & GATT_PERM_ENC_WRITE_MASK) &&
(value->enc_key_size > bt_conn_enc_key_size(conn))) {
return BT_GATT_ERR(BT_ATT_ERR_ENCRYPTION_KEY_SIZE);
}
/* Don't write anything if prepare flag is set */
if (flags & BT_GATT_WRITE_FLAG_PREPARE) {
return 0;
}
if (offset > value->len) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
if (offset + len > value->len) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
memcpy(value->data + offset, buf, len);
value->len = len;
/* Maximum attribute value size is 512 bytes */
__ASSERT_NO_MSG(value->len < 512);
attr_value_changed_ev(attr->handle, value->data, value->len);
return len;
}
struct add_characteristic {
uint16_t char_id;
uint8_t properties;
uint8_t permissions;
const struct bt_uuid *uuid;
};
static int alloc_characteristic(struct add_characteristic *ch)
{
struct bt_gatt_attr *attr_chrc, *attr_value;
struct bt_gatt_chrc *chrc_data;
struct gatt_value value;
/* Add Characteristic Declaration */
attr_chrc = gatt_db_add(&(struct bt_gatt_attr)
BT_GATT_ATTRIBUTE(BT_UUID_GATT_CHRC,
BT_GATT_PERM_READ,
bt_gatt_attr_read_chrc, NULL,
(&(struct bt_gatt_chrc){})),
sizeof(*chrc_data));
if (!attr_chrc) {
return -EINVAL;
}
(void)memset(&value, 0, sizeof(value));
if (ch->permissions & GATT_PERM_READ_AUTHORIZATION) {
tester_set_bit(value.flags, GATT_VALUE_READ_AUTHOR_FLAG);
/* To maintain backward compatibility, set Read Permission */
if (!(ch->permissions & GATT_PERM_ENC_READ_MASK)) {
ch->permissions |= BT_GATT_PERM_READ;
}
}
if (ch->permissions & GATT_PERM_WRITE_AUTHORIZATION) {
tester_set_bit(value.flags, GATT_VALUE_WRITE_AUTHOR_FLAG);
/* To maintain backward compatibility, set Write Permission */
if (!(ch->permissions & GATT_PERM_ENC_WRITE_MASK)) {
ch->permissions |= BT_GATT_PERM_WRITE;
}
}
/* Allow prepare writes */
ch->permissions |= BT_GATT_PERM_PREPARE_WRITE;
/* Add Characteristic Value */
attr_value = gatt_db_add(&(struct bt_gatt_attr)
BT_GATT_ATTRIBUTE(ch->uuid,
ch->permissions & GATT_PERM_MASK,
read_value, write_value, &value),
sizeof(value));
if (!attr_value) {
server_buf_pull(sizeof(*chrc_data));
/* Characteristic attribute uuid has constant length */
server_buf_pull(sizeof(uint16_t));
return -EINVAL;
}
chrc_data = attr_chrc->user_data;
chrc_data->properties = ch->properties;
chrc_data->uuid = attr_value->uuid;
ch->char_id = attr_chrc->handle;
return 0;
}
static uint8_t add_characteristic(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_add_characteristic_cmd *cp = cmd;
struct btp_gatt_add_characteristic_rp *rp = rsp;
struct add_characteristic cmd_data;
union uuid uuid;
if ((cmd_len < sizeof(*cp)) ||
(cmd_len != sizeof(*cp) + cp->uuid_length)) {
return BTP_STATUS_FAILED;
}
/* Pre-set char_id */
cmd_data.char_id = 0U;
cmd_data.permissions = cp->permissions;
cmd_data.properties = cp->properties;
cmd_data.uuid = &uuid.uuid;
if (btp2bt_uuid(cp->uuid, cp->uuid_length, &uuid.uuid)) {
return BTP_STATUS_FAILED;
}
/* characteristic must be added only sequential */
if (cp->svc_id) {
return BTP_STATUS_FAILED;
}
if (alloc_characteristic(&cmd_data)) {
return BTP_STATUS_FAILED;
}
ccc_added = false;
rp->char_id = sys_cpu_to_le16(cmd_data.char_id);
*rsp_len = sizeof(*rp);
return BTP_STATUS_SUCCESS;
}
static void ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
int i = ccc_find_by_ccc(attr);
if (i >= 0) {
ccc_values[i].value = value;
}
}
static struct bt_gatt_attr ccc = BT_GATT_CCC(ccc_cfg_changed,
BT_GATT_PERM_READ |
BT_GATT_PERM_WRITE);
static struct bt_gatt_attr *add_ccc(struct bt_gatt_attr *attr)
{
struct bt_gatt_attr *attr_desc;
struct bt_gatt_chrc *chrc = attr->user_data;
struct gatt_value *value = NEXT_DB_ATTR(attr)->user_data;
int i;
/* Fail if another CCC already exist for this characteristic */
if (ccc_added) {
return NULL;
}
/* Check characteristic properties */
if (!(chrc->properties &
(BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE))) {
return NULL;
}
/* Add CCC descriptor to GATT database */
attr_desc = gatt_db_add(&ccc, 0);
if (!attr_desc) {
return NULL;
}
i = ccc_find_by_ccc(NULL);
if (i >= 0) {
ccc_values[i].attr = attr;
ccc_values[i].ccc = attr_desc;
ccc_values[i].value = 0;
}
tester_set_bit(value->flags, GATT_VALUE_CCC_FLAG);
ccc_added = true;
return attr_desc;
}
static struct bt_gatt_attr *add_cep(const struct bt_gatt_attr *attr_chrc)
{
struct bt_gatt_chrc *chrc = attr_chrc->user_data;
struct bt_gatt_cep cep_value;
/* Extended Properties bit shall be set */
if (!(chrc->properties & BT_GATT_CHRC_EXT_PROP)) {
return NULL;
}
cep_value.properties = 0x0000;
/* Add CEP descriptor to GATT database */
return gatt_db_add(&(struct bt_gatt_attr) BT_GATT_CEP(&cep_value),
sizeof(cep_value));
}
struct add_descriptor {
uint16_t desc_id;
uint8_t permissions;
const struct bt_uuid *uuid;
};
static int alloc_descriptor(struct bt_gatt_attr *attr,
struct add_descriptor *d)
{
struct bt_gatt_attr *attr_desc;
struct gatt_value value;
if (!bt_uuid_cmp(d->uuid, BT_UUID_GATT_CEP)) {
attr_desc = add_cep(attr);
} else if (!bt_uuid_cmp(d->uuid, BT_UUID_GATT_CCC)) {
attr_desc = add_ccc(attr);
} else {
(void)memset(&value, 0, sizeof(value));
if (d->permissions & GATT_PERM_READ_AUTHORIZATION) {
tester_set_bit(value.flags,
GATT_VALUE_READ_AUTHOR_FLAG);
/*
* To maintain backward compatibility,
* set Read Permission
*/
if (!(d->permissions & GATT_PERM_ENC_READ_MASK)) {
d->permissions |= BT_GATT_PERM_READ;
}
}
if (d->permissions & GATT_PERM_WRITE_AUTHORIZATION) {
tester_set_bit(value.flags,
GATT_VALUE_WRITE_AUTHOR_FLAG);
/*
* To maintain backward compatibility,
* set Write Permission
*/
if (!(d->permissions & GATT_PERM_ENC_WRITE_MASK)) {
d->permissions |= BT_GATT_PERM_WRITE;
}
}
/* Allow prepare writes */
d->permissions |= BT_GATT_PERM_PREPARE_WRITE;
attr_desc = gatt_db_add(&(struct bt_gatt_attr)
BT_GATT_DESCRIPTOR(d->uuid,
d->permissions & GATT_PERM_MASK,
read_value, write_value,
&value), sizeof(value));
}
if (!attr_desc) {
return -EINVAL;
}
d->desc_id = attr_desc->handle;
return 0;
}
static struct bt_gatt_attr *get_base_chrc(struct bt_gatt_attr *attr)
{
struct bt_gatt_attr *tmp;
for (tmp = attr; tmp > server_db; tmp--) {
/* Service Declaration cannot precede Descriptor declaration */
if (!bt_uuid_cmp(tmp->uuid, BT_UUID_GATT_PRIMARY) ||
!bt_uuid_cmp(tmp->uuid, BT_UUID_GATT_SECONDARY)) {
break;
}
if (!bt_uuid_cmp(tmp->uuid, BT_UUID_GATT_CHRC)) {
return tmp;
}
}
return NULL;
}
static uint8_t add_descriptor(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_add_descriptor_cmd *cp = cmd;
struct btp_gatt_add_descriptor_rp *rp = rsp;
struct add_descriptor cmd_data;
struct bt_gatt_attr *chrc;
union uuid uuid;
if ((cmd_len < sizeof(*cp)) ||
(cmd_len != sizeof(*cp) + cp->uuid_length)) {
return BTP_STATUS_FAILED;
}
/* Must be declared first svc or at least 3 attrs (svc+char+char val) */
if (!svc_count || attr_count < 3) {
return BTP_STATUS_FAILED;
}
/* Pre-set desc_id */
cmd_data.desc_id = 0U;
cmd_data.permissions = cp->permissions;
cmd_data.uuid = &uuid.uuid;
if (btp2bt_uuid(cp->uuid, cp->uuid_length, &uuid.uuid)) {
return BTP_STATUS_FAILED;
}
/* descriptor can be added only sequential */
if (cp->char_id) {
return BTP_STATUS_FAILED;
}
/* Lookup preceding Characteristic Declaration here */
chrc = get_base_chrc(LAST_DB_ATTR);
if (!chrc) {
return BTP_STATUS_FAILED;
}
if (alloc_descriptor(chrc, &cmd_data)) {
return BTP_STATUS_FAILED;
}
rp->desc_id = sys_cpu_to_le16(cmd_data.desc_id);
*rsp_len = sizeof(*rp);
return BTP_STATUS_SUCCESS;
}
static int alloc_included(struct bt_gatt_attr *attr,
uint16_t *included_service_id, uint16_t svc_handle)
{
struct bt_gatt_attr *attr_incl;
/*
* user_data_len is set to 0 to NOT allocate memory in server_buf for
* user_data, just to assign to it attr pointer.
*/
attr_incl = gatt_db_add(&(struct bt_gatt_attr)
BT_GATT_INCLUDE_SERVICE(attr), 0);
if (!attr_incl) {
return -EINVAL;
}
attr_incl->user_data = attr;
*included_service_id = attr_incl->handle;
return 0;
}
static uint8_t add_included(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_add_included_service_cmd *cp = cmd;
struct btp_gatt_add_included_service_rp *rp = rsp;
struct bt_gatt_attr *svc;
uint16_t svc_id;
uint16_t included_service_id = 0U;
if (!svc_count) {
return BTP_STATUS_FAILED;
}
svc_id = sys_le16_to_cpu(cp->svc_id);
if (svc_id == 0 || svc_id > SERVER_MAX_ATTRIBUTES) {
return BTP_STATUS_FAILED;
}
svc = &server_db[svc_id - 1];
/* Fail if attribute stored under requested handle is not a service */
if (bt_uuid_cmp(svc->uuid, BT_UUID_GATT_PRIMARY) &&
bt_uuid_cmp(svc->uuid, BT_UUID_GATT_SECONDARY)) {
return BTP_STATUS_FAILED;
}
if (alloc_included(svc, &included_service_id, svc_id)) {
return BTP_STATUS_FAILED;
}
rp->included_service_id = sys_cpu_to_le16(included_service_id);
*rsp_len = sizeof(*rp);
return BTP_STATUS_SUCCESS;
}
static uint8_t set_cep_value(struct bt_gatt_attr *attr, const void *value,
const uint16_t len)
{
struct bt_gatt_cep *cep_value = attr->user_data;
uint16_t properties;
if (len != sizeof(properties)) {
return BTP_STATUS_FAILED;
}
memcpy(&properties, value, len);
cep_value->properties = sys_le16_to_cpu(properties);
return BTP_STATUS_SUCCESS;
}
struct set_value {
const uint8_t *value;
uint16_t len;
};
struct bt_gatt_indicate_params indicate_params;
static void indicate_cb(struct bt_conn *conn,
struct bt_gatt_indicate_params *params, uint8_t err)
{
if (err != 0U) {
LOG_ERR("Indication fail");
} else {
LOG_DBG("Indication success");
}
}
static uint8_t alloc_value(struct bt_gatt_attr *attr, struct set_value *data)
{
struct gatt_value *value;
uint8_t ccc_value;
int i;
/* Value has been already set while adding CCC to the gatt_db */
if (!bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CCC)) {
return BTP_STATUS_SUCCESS;
}
/* Set CEP value */
if (!bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CEP)) {
return set_cep_value(attr, data->value, data->len);
}
value = attr->user_data;
/* Check if attribute value has been already set */
if (!value->len) {
value->data = server_buf_push(data->len);
value->len = data->len;
}
/* Fail if value length doesn't match */
if (value->len != data->len) {
return BTP_STATUS_FAILED;
}
memcpy(value->data, data->value, value->len);
/** Handle of attribute is 1 less that handle to its value */
i = ccc_find_by_attr(attr->handle - 1);
if (i < 0) {
ccc_value = 0;
} else {
ccc_value = ccc_values[i].value;
}
if (tester_test_bit(value->flags, GATT_VALUE_CCC_FLAG) && ccc_value) {
if (ccc_value == BT_GATT_CCC_NOTIFY) {
bt_gatt_notify(NULL, attr, value->data, value->len);
} else {
indicate_params.attr = attr;
indicate_params.data = value->data;
indicate_params.len = value->len;
indicate_params.func = indicate_cb;
indicate_params.destroy = NULL;
indicate_params.chan_opt = BT_ATT_CHAN_OPT_NONE;
bt_gatt_indicate(NULL, &indicate_params);
}
}
return BTP_STATUS_SUCCESS;
}
static uint8_t set_value(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_set_value_cmd *cp = cmd;
struct set_value cmd_data;
uint16_t attr_id;
uint8_t status;
if ((cmd_len < sizeof(*cp)) ||
(cmd_len != sizeof(*cp) + sys_le16_to_cpu(cp->len))) {
return BTP_STATUS_FAILED;
}
attr_id = sys_le16_to_cpu(cp->attr_id);
if (attr_id > SERVER_MAX_ATTRIBUTES) {
return BTP_STATUS_FAILED;
}
/* Pre-set btp_status */
cmd_data.value = cp->value;
cmd_data.len = sys_le16_to_cpu(cp->len);
if (attr_id == 0) {
status = alloc_value(LAST_DB_ATTR, &cmd_data);
} else {
/* set value of local attr, corrected by pre set attr handles */
status = alloc_value(&server_db[attr_id - server_db[0].handle],
&cmd_data);
}
return BTP_STATUS_SUCCESS;
}
static uint8_t start_server(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
struct btp_gatt_start_server_rp *rp = rsp;
/* Register last defined service */
if (svc_attr_count) {
if (register_service()) {
return BTP_STATUS_FAILED;
}
}
rp->db_attr_off = sys_cpu_to_le16(0); /* TODO*/
rp->db_attr_cnt = svc_attr_count;
*rsp_len = sizeof(*rp);
return BTP_STATUS_SUCCESS;
}
static int set_attr_enc_key_size(const struct bt_gatt_attr *attr,
uint8_t key_size)
{
struct gatt_value *value;
/* Fail if requested attribute is a service */
if (!bt_uuid_cmp(attr->uuid, BT_UUID_GATT_PRIMARY) ||
!bt_uuid_cmp(attr->uuid, BT_UUID_GATT_SECONDARY) ||
!bt_uuid_cmp(attr->uuid, BT_UUID_GATT_INCLUDE)) {
return -EINVAL;
}
/* Fail if permissions are not set */
if (!(attr->perm & (GATT_PERM_ENC_READ_MASK |
GATT_PERM_ENC_WRITE_MASK))) {
return -EINVAL;
}
value = attr->user_data;
value->enc_key_size = key_size;
return 0;
}
static uint8_t set_enc_key_size(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_set_enc_key_size_cmd *cp = cmd;
uint16_t attr_id;
int ret;
/* Fail if requested key size is invalid */
if (cp->key_size < 0x07 || cp->key_size > 0x0f) {
return BTP_STATUS_FAILED;
}
attr_id = sys_le16_to_cpu(cp->attr_id);
if (!attr_id) {
ret = set_attr_enc_key_size(LAST_DB_ATTR, cp->key_size);
} else {
/* set value of local attr, corrected by pre set attr handles */
ret = set_attr_enc_key_size(&server_db[attr_id -
server_db[0].handle], cp->key_size);
}
if (ret) {
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
static void exchange_func(struct bt_conn *conn, uint8_t err,
struct bt_gatt_exchange_params *params)
{
if (err != 0U) {
LOG_ERR("MTU exchange failed");
} else {
LOG_DBG("MTU exchange succeed");
}
}
static struct bt_gatt_exchange_params exchange_params;
static uint8_t exchange_mtu(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_exchange_mtu_cmd *cp = cmd;
struct bt_conn *conn;
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
return BTP_STATUS_FAILED;
}
exchange_params.func = exchange_func;
if (bt_gatt_exchange_mtu(conn, &exchange_params) < 0) {
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
bt_conn_unref(conn);
/* this BTP command is about initiating MTU exchange, no need to wait
* for procedure to complete.
*/
return BTP_STATUS_SUCCESS;
}
static struct bt_gatt_discover_params discover_params;
static union uuid uuid;
static uint8_t btp_opcode;
static void discover_destroy(struct bt_gatt_discover_params *params)
{
(void)memset(params, 0, sizeof(*params));
gatt_buf_clear();
}
static uint8_t disc_prim_cb(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
struct bt_gatt_service_val *data;
struct btp_gatt_disc_prim_rp *rp = (void *) gatt_buf.buf;
struct btp_gatt_service *service;
uint8_t uuid_length;
if (!attr) {
tester_rsp_full(BTP_SERVICE_ID_GATT, btp_opcode,
gatt_buf.buf, gatt_buf.len);
discover_destroy(params);
return BT_GATT_ITER_STOP;
}
data = attr->user_data;
uuid_length = data->uuid->type == BT_UUID_TYPE_16 ? 2 : 16;
service = gatt_buf_reserve(sizeof(*service) + uuid_length);
if (!service) {
tester_rsp(BTP_SERVICE_ID_GATT, btp_opcode, BTP_STATUS_FAILED);
discover_destroy(params);
return BT_GATT_ITER_STOP;
}
service->start_handle = sys_cpu_to_le16(attr->handle);
service->end_handle = sys_cpu_to_le16(data->end_handle);
service->uuid_length = uuid_length;
if (data->uuid->type == BT_UUID_TYPE_16) {
uint16_t u16 = sys_cpu_to_le16(BT_UUID_16(data->uuid)->val);
memcpy(service->uuid, &u16, uuid_length);
} else {
memcpy(service->uuid, BT_UUID_128(data->uuid)->val,
uuid_length);
}
rp->services_count++;
return BT_GATT_ITER_CONTINUE;
}
static uint8_t disc_all_prim(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_disc_all_prim_cmd *cp = cmd;
struct bt_conn *conn;
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
return BTP_STATUS_FAILED;
}
if (!gatt_buf_reserve(sizeof(struct btp_gatt_disc_prim_rp))) {
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
discover_params.uuid = NULL;
discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
discover_params.type = BT_GATT_DISCOVER_PRIMARY;
discover_params.func = disc_prim_cb;
discover_params.chan_opt = BT_ATT_CHAN_OPT_NONE;
btp_opcode = BTP_GATT_DISC_ALL_PRIM;
if (bt_gatt_discover(conn, &discover_params) < 0) {
discover_destroy(&discover_params);
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
bt_conn_unref(conn);
return BTP_STATUS_DELAY_REPLY;
}
static uint8_t disc_prim_uuid(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_disc_prim_uuid_cmd *cp = cmd;
struct bt_conn *conn;
if ((cmd_len < sizeof(*cp)) ||
(cmd_len != sizeof(*cp) + cp->uuid_length)) {
return BTP_STATUS_FAILED;
}
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
return BTP_STATUS_FAILED;
}
if (btp2bt_uuid(cp->uuid, cp->uuid_length, &uuid.uuid)) {
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
if (!gatt_buf_reserve(sizeof(struct btp_gatt_disc_prim_rp))) {
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
discover_params.uuid = &uuid.uuid;
discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
discover_params.type = BT_GATT_DISCOVER_PRIMARY;
discover_params.func = disc_prim_cb;
discover_params.chan_opt = BT_ATT_CHAN_OPT_NONE;
btp_opcode = BTP_GATT_DISC_PRIM_UUID;
if (bt_gatt_discover(conn, &discover_params) < 0) {
discover_destroy(&discover_params);
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
bt_conn_unref(conn);
return BTP_STATUS_DELAY_REPLY;
}
static uint8_t find_included_cb(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
struct bt_gatt_include *data;
struct btp_gatt_find_included_rp *rp = (void *) gatt_buf.buf;
struct btp_gatt_included *included;
uint8_t uuid_length;
if (!attr) {
tester_rsp_full(BTP_SERVICE_ID_GATT, BTP_GATT_FIND_INCLUDED,
gatt_buf.buf, gatt_buf.len);
discover_destroy(params);
return BT_GATT_ITER_STOP;
}
data = attr->user_data;
uuid_length = data->uuid->type == BT_UUID_TYPE_16 ? 2 : 16;
included = gatt_buf_reserve(sizeof(*included) + uuid_length);
if (!included) {
tester_rsp(BTP_SERVICE_ID_GATT, BTP_GATT_FIND_INCLUDED, BTP_STATUS_FAILED);
discover_destroy(params);
return BT_GATT_ITER_STOP;
}
included->included_handle = attr->handle;
included->service.start_handle = sys_cpu_to_le16(data->start_handle);
included->service.end_handle = sys_cpu_to_le16(data->end_handle);
included->service.uuid_length = uuid_length;
if (data->uuid->type == BT_UUID_TYPE_16) {
uint16_t u16 = sys_cpu_to_le16(BT_UUID_16(data->uuid)->val);
memcpy(included->service.uuid, &u16, uuid_length);
} else {
memcpy(included->service.uuid, BT_UUID_128(data->uuid)->val,
uuid_length);
}
rp->services_count++;
return BT_GATT_ITER_CONTINUE;
}
static uint8_t find_included(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_find_included_cmd *cp = cmd;
struct bt_conn *conn;
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
return BTP_STATUS_FAILED;
}
if (!gatt_buf_reserve(sizeof(struct btp_gatt_find_included_rp))) {
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
discover_params.start_handle = sys_le16_to_cpu(cp->start_handle);
discover_params.end_handle = sys_le16_to_cpu(cp->end_handle);
discover_params.type = BT_GATT_DISCOVER_INCLUDE;
discover_params.func = find_included_cb;
discover_params.chan_opt = BT_ATT_CHAN_OPT_NONE;
if (bt_gatt_discover(conn, &discover_params) < 0) {
discover_destroy(&discover_params);
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
bt_conn_unref(conn);
return BTP_STATUS_DELAY_REPLY;
}
static uint8_t disc_chrc_cb(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
struct bt_gatt_chrc *data;
struct btp_gatt_disc_chrc_rp *rp = (void *) gatt_buf.buf;
struct btp_gatt_characteristic *chrc;
uint8_t uuid_length;
if (!attr) {
tester_rsp_full(BTP_SERVICE_ID_GATT, btp_opcode,
gatt_buf.buf, gatt_buf.len);
discover_destroy(params);
return BT_GATT_ITER_STOP;
}
data = attr->user_data;
uuid_length = data->uuid->type == BT_UUID_TYPE_16 ? 2 : 16;
chrc = gatt_buf_reserve(sizeof(*chrc) + uuid_length);
if (!chrc) {
tester_rsp(BTP_SERVICE_ID_GATT, btp_opcode, BTP_STATUS_FAILED);
discover_destroy(params);
return BT_GATT_ITER_STOP;
}
chrc->characteristic_handle = sys_cpu_to_le16(attr->handle);
chrc->properties = data->properties;
chrc->value_handle = sys_cpu_to_le16(attr->handle + 1);
chrc->uuid_length = uuid_length;
if (data->uuid->type == BT_UUID_TYPE_16) {
uint16_t u16 = sys_cpu_to_le16(BT_UUID_16(data->uuid)->val);
memcpy(chrc->uuid, &u16, uuid_length);
} else {
memcpy(chrc->uuid, BT_UUID_128(data->uuid)->val, uuid_length);
}
rp->characteristics_count++;
return BT_GATT_ITER_CONTINUE;
}
static uint8_t disc_all_chrc(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_disc_all_chrc_cmd *cp = cmd;
struct bt_conn *conn;
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
return BTP_STATUS_FAILED;
}
if (!gatt_buf_reserve(sizeof(struct btp_gatt_disc_chrc_rp))) {
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
discover_params.start_handle = sys_le16_to_cpu(cp->start_handle);
discover_params.end_handle = sys_le16_to_cpu(cp->end_handle);
discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
discover_params.func = disc_chrc_cb;
discover_params.chan_opt = BT_ATT_CHAN_OPT_NONE;
/* TODO should be handled as user_data via CONTAINER_OF macro */
btp_opcode = BTP_GATT_DISC_ALL_CHRC;
if (bt_gatt_discover(conn, &discover_params) < 0) {
discover_destroy(&discover_params);
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
bt_conn_unref(conn);
return BTP_STATUS_DELAY_REPLY;
}
static uint8_t disc_chrc_uuid(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_disc_chrc_uuid_cmd *cp = cmd;
struct bt_conn *conn;
if ((cmd_len < sizeof(*cp)) ||
(cmd_len != sizeof(*cp) + cp->uuid_length)) {
return BTP_STATUS_FAILED;
}
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
return BTP_STATUS_FAILED;
}
if (btp2bt_uuid(cp->uuid, cp->uuid_length, &uuid.uuid)) {
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
if (!gatt_buf_reserve(sizeof(struct btp_gatt_disc_chrc_rp))) {
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
discover_params.uuid = &uuid.uuid;
discover_params.start_handle = sys_le16_to_cpu(cp->start_handle);
discover_params.end_handle = sys_le16_to_cpu(cp->end_handle);
discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
discover_params.func = disc_chrc_cb;
discover_params.chan_opt = BT_ATT_CHAN_OPT_NONE;
/* TODO should be handled as user_data via CONTAINER_OF macro */
btp_opcode = BTP_GATT_DISC_CHRC_UUID;
if (bt_gatt_discover(conn, &discover_params) < 0) {
discover_destroy(&discover_params);
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
bt_conn_unref(conn);
return BTP_STATUS_DELAY_REPLY;
}
static uint8_t disc_all_desc_cb(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
struct btp_gatt_disc_all_desc_rp *rp = (void *) gatt_buf.buf;
struct btp_gatt_descriptor *descriptor;
uint8_t uuid_length;
if (!attr) {
tester_rsp_full(BTP_SERVICE_ID_GATT, BTP_GATT_DISC_ALL_DESC,
gatt_buf.buf, gatt_buf.len);
discover_destroy(params);
return BT_GATT_ITER_STOP;
}
uuid_length = attr->uuid->type == BT_UUID_TYPE_16 ? 2 : 16;
descriptor = gatt_buf_reserve(sizeof(*descriptor) + uuid_length);
if (!descriptor) {
tester_rsp(BTP_SERVICE_ID_GATT, BTP_GATT_DISC_ALL_DESC, BTP_STATUS_FAILED);
discover_destroy(params);
return BT_GATT_ITER_STOP;
}
descriptor->descriptor_handle = sys_cpu_to_le16(attr->handle);
descriptor->uuid_length = uuid_length;
if (attr->uuid->type == BT_UUID_TYPE_16) {
uint16_t u16 = sys_cpu_to_le16(BT_UUID_16(attr->uuid)->val);
memcpy(descriptor->uuid, &u16, uuid_length);
} else {
memcpy(descriptor->uuid, BT_UUID_128(attr->uuid)->val,
uuid_length);
}
rp->descriptors_count++;
return BT_GATT_ITER_CONTINUE;
}
static uint8_t disc_all_desc(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_disc_all_desc_cmd *cp = cmd;
struct bt_conn *conn;
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
return BTP_STATUS_FAILED;
}
if (!gatt_buf_reserve(sizeof(struct btp_gatt_disc_all_desc_rp))) {
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
discover_params.start_handle = sys_le16_to_cpu(cp->start_handle);
discover_params.end_handle = sys_le16_to_cpu(cp->end_handle);
discover_params.type = BT_GATT_DISCOVER_DESCRIPTOR;
discover_params.func = disc_all_desc_cb;
discover_params.chan_opt = BT_ATT_CHAN_OPT_NONE;
if (bt_gatt_discover(conn, &discover_params) < 0) {
discover_destroy(&discover_params);
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
bt_conn_unref(conn);
return BTP_STATUS_DELAY_REPLY;
}
static struct bt_gatt_read_params read_params;
static void read_destroy(struct bt_gatt_read_params *params)
{
(void)memset(params, 0, sizeof(*params));
gatt_buf_clear();
}
static uint8_t read_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params, const void *data,
uint16_t length)
{
struct btp_gatt_read_rp *rp = (void *) gatt_buf.buf;
/* Respond to the Lower Tester with ATT Error received */
if (err) {
rp->att_response = err;
}
/* read complete */
if (!data) {
tester_rsp_full(BTP_SERVICE_ID_GATT, btp_opcode,
gatt_buf.buf, gatt_buf.len);
read_destroy(params);
return BT_GATT_ITER_STOP;
}
if (!gatt_buf_add(data, length)) {
tester_rsp(BTP_SERVICE_ID_GATT, btp_opcode, BTP_STATUS_FAILED);
read_destroy(params);
return BT_GATT_ITER_STOP;
}
rp->data_length += length;
return BT_GATT_ITER_CONTINUE;
}
static uint8_t read_uuid_cb(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params, const void *data,
uint16_t length)
{
struct btp_gatt_read_uuid_rp *rp = (void *)gatt_buf.buf;
struct btp_gatt_char_value value;
/* Respond to the Lower Tester with ATT Error received */
if (err) {
rp->att_response = err;
}
/* read complete */
if (!data) {
tester_rsp_full(BTP_SERVICE_ID_GATT, btp_opcode,
gatt_buf.buf, gatt_buf.len);
read_destroy(params);
return BT_GATT_ITER_STOP;
}
value.handle = params->by_uuid.start_handle;
value.data_len = length;
if (!gatt_buf_add(&value, sizeof(struct btp_gatt_char_value))) {
tester_rsp(BTP_SERVICE_ID_GATT, btp_opcode, BTP_STATUS_FAILED);
read_destroy(params);
return BT_GATT_ITER_STOP;
}
if (!gatt_buf_add(data, length)) {
tester_rsp(BTP_SERVICE_ID_GATT, btp_opcode, BTP_STATUS_FAILED);
read_destroy(params);
return BT_GATT_ITER_STOP;
}
rp->values_count++;
return BT_GATT_ITER_CONTINUE;
}
static uint8_t read_data(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_read_cmd *cp = cmd;
struct bt_conn *conn;
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
return BTP_STATUS_FAILED;
}
if (!gatt_buf_reserve(sizeof(struct btp_gatt_read_rp))) {
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
read_params.handle_count = 1;
read_params.single.handle = sys_le16_to_cpu(cp->handle);
read_params.single.offset = 0x0000;
read_params.func = read_cb;
read_params.chan_opt = BT_ATT_CHAN_OPT_NONE;
/* TODO should be handled as user_data via CONTAINER_OF macro */
btp_opcode = BTP_GATT_READ;
if (bt_gatt_read(conn, &read_params) < 0) {
read_destroy(&read_params);
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
bt_conn_unref(conn);
return BTP_STATUS_DELAY_REPLY;
}
static uint8_t read_uuid(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_read_uuid_cmd *cp = cmd;
struct bt_conn *conn;
if ((cmd_len < sizeof(*cp)) ||
(cmd_len != sizeof(*cp) + cp->uuid_length)) {
return BTP_STATUS_FAILED;
}
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
return BTP_STATUS_FAILED;
}
if (btp2bt_uuid(cp->uuid, cp->uuid_length, &uuid.uuid)) {
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
if (!gatt_buf_reserve(sizeof(struct btp_gatt_read_uuid_rp))) {
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
read_params.by_uuid.uuid = &uuid.uuid;
read_params.handle_count = 0;
read_params.by_uuid.start_handle = sys_le16_to_cpu(cp->start_handle);
read_params.by_uuid.end_handle = sys_le16_to_cpu(cp->end_handle);
read_params.func = read_uuid_cb;
read_params.chan_opt = BT_ATT_CHAN_OPT_NONE;
btp_opcode = BTP_GATT_READ_UUID;
if (bt_gatt_read(conn, &read_params) < 0) {
read_destroy(&read_params);
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
bt_conn_unref(conn);
return BTP_STATUS_DELAY_REPLY;
}
static uint8_t read_long(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_read_long_cmd *cp = cmd;
struct bt_conn *conn;
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
return BTP_STATUS_FAILED;
}
if (!gatt_buf_reserve(sizeof(struct btp_gatt_read_rp))) {
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
read_params.handle_count = 1;
read_params.single.handle = sys_le16_to_cpu(cp->handle);
read_params.single.offset = sys_le16_to_cpu(cp->offset);
read_params.func = read_cb;
read_params.chan_opt = BT_ATT_CHAN_OPT_NONE;
/* TODO should be handled as user_data via CONTAINER_OF macro */
btp_opcode = BTP_GATT_READ_LONG;
if (bt_gatt_read(conn, &read_params) < 0) {
read_destroy(&read_params);
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
bt_conn_unref(conn);
return BTP_STATUS_DELAY_REPLY;
}
static uint8_t read_multiple(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_read_multiple_cmd *cp = cmd;
uint16_t handles[5];
struct bt_conn *conn;
int i;
if ((cmd_len < sizeof(*cp)) ||
(cmd_len != sizeof(*cp) + (cp->handles_count * sizeof(cp->handles[0])))) {
return BTP_STATUS_FAILED;
}
if (cp->handles_count > ARRAY_SIZE(handles)) {
return BTP_STATUS_FAILED;
}
for (i = 0; i < ARRAY_SIZE(handles); i++) {
handles[i] = sys_le16_to_cpu(cp->handles[i]);
}
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
return BTP_STATUS_FAILED;
}
if (!gatt_buf_reserve(sizeof(struct btp_gatt_read_rp))) {
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
read_params.func = read_cb;
read_params.handle_count = i;
read_params.multiple.handles = handles; /* not used in read func */
read_params.multiple.variable = false;
read_params.chan_opt = BT_ATT_CHAN_OPT_NONE;
/* TODO should be handled as user_data via CONTAINER_OF macro */
btp_opcode = BTP_GATT_READ_MULTIPLE;
if (bt_gatt_read(conn, &read_params) < 0) {
gatt_buf_clear();
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
bt_conn_unref(conn);
return BTP_STATUS_DELAY_REPLY;
}
static uint8_t read_multiple_var(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_read_multiple_var_cmd *cp = cmd;
uint16_t handles[5];
struct bt_conn *conn;
int i;
if ((cmd_len < sizeof(*cp)) ||
(cmd_len != sizeof(*cp) + (cp->handles_count * sizeof(cp->handles[0])))) {
return BTP_STATUS_FAILED;
}
if (cp->handles_count > ARRAY_SIZE(handles)) {
return BTP_STATUS_FAILED;
}
for (i = 0; i < ARRAY_SIZE(handles); i++) {
handles[i] = sys_le16_to_cpu(cp->handles[i]);
}
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
return BTP_STATUS_FAILED;
}
if (!gatt_buf_reserve(sizeof(struct btp_gatt_read_rp))) {
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
read_params.func = read_cb;
read_params.handle_count = i;
read_params.multiple.handles = handles; /* not used in read func */
read_params.multiple.variable = true;
read_params.chan_opt = BT_ATT_CHAN_OPT_NONE;
/* TODO should be handled as user_data via CONTAINER_OF macro */
btp_opcode = BTP_GATT_READ_MULTIPLE_VAR;
if (bt_gatt_read(conn, &read_params) < 0) {
gatt_buf_clear();
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
bt_conn_unref(conn);
return BTP_STATUS_DELAY_REPLY;
}
static uint8_t write_without_rsp(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_write_without_rsp_cmd *cp = cmd;
struct bt_conn *conn;
if (cmd_len < sizeof(*cp) ||
cmd_len != sizeof(*cp) + sys_le16_to_cpu(cp->data_length)) {
return BTP_STATUS_FAILED;
}
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
return BTP_STATUS_FAILED;
}
if (bt_gatt_write_without_response(conn, sys_le16_to_cpu(cp->handle),
cp->data,
sys_le16_to_cpu(cp->data_length),
false) < 0) {
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
bt_conn_unref(conn);
return BTP_STATUS_SUCCESS;
}
static uint8_t write_signed_without_rsp(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_signed_write_without_rsp_cmd *cp = cmd;
struct bt_conn *conn;
if (cmd_len < sizeof(*cp) ||
cmd_len != sizeof(*cp) + sys_le16_to_cpu(cp->data_length)) {
return BTP_STATUS_FAILED;
}
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
return BTP_STATUS_FAILED;
}
if (bt_gatt_write_without_response(conn, sys_le16_to_cpu(cp->handle),
cp->data,
sys_le16_to_cpu(cp->data_length),
true) < 0) {
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
bt_conn_unref(conn);
return BTP_STATUS_SUCCESS;
}
static void write_rsp(struct bt_conn *conn, uint8_t err,
struct bt_gatt_write_params *params)
{
tester_rsp_full(BTP_SERVICE_ID_GATT, BTP_GATT_WRITE, &err, sizeof(err));
}
static struct bt_gatt_write_params write_params;
static uint8_t write_data(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_write_cmd *cp = cmd;
struct bt_conn *conn;
if (cmd_len < sizeof(*cp) ||
cmd_len != sizeof(*cp) + sys_le16_to_cpu(cp->data_length)) {
return BTP_STATUS_FAILED;
}
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
return BTP_STATUS_FAILED;
}
write_params.handle = sys_le16_to_cpu(cp->handle);
write_params.func = write_rsp;
write_params.offset = 0U;
write_params.data = cp->data;
write_params.length = sys_le16_to_cpu(cp->data_length);
write_params.chan_opt = BT_ATT_CHAN_OPT_NONE;
if (bt_gatt_write(conn, &write_params) < 0) {
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
bt_conn_unref(conn);
return BTP_STATUS_DELAY_REPLY;
}
static void write_long_rsp(struct bt_conn *conn, uint8_t err,
struct bt_gatt_write_params *params)
{
tester_rsp_full(BTP_SERVICE_ID_GATT, BTP_GATT_WRITE_LONG, &err, sizeof(err));
}
static uint8_t write_long(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_write_long_cmd *cp = cmd;
struct bt_conn *conn;
if (cmd_len < sizeof(*cp) ||
cmd_len != sizeof(*cp) + sys_le16_to_cpu(cp->data_length)) {
return BTP_STATUS_FAILED;
}
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
return BTP_STATUS_FAILED;
}
write_params.handle = sys_le16_to_cpu(cp->handle);
write_params.func = write_long_rsp;
write_params.offset = sys_le16_to_cpu(cp->offset);
write_params.data = cp->data;
write_params.length = sys_le16_to_cpu(cp->data_length);
write_params.chan_opt = BT_ATT_CHAN_OPT_NONE;
if (bt_gatt_write(conn, &write_params) < 0) {
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
bt_conn_unref(conn);
return BTP_STATUS_DELAY_REPLY;
}
static struct bt_gatt_subscribe_params subscriptions[MAX_SUBSCRIPTIONS];
static struct bt_gatt_subscribe_params *find_subscription(uint16_t ccc_handle)
{
for (int i = 0; i < MAX_SUBSCRIPTIONS; i++) {
if (subscriptions[i].ccc_handle == ccc_handle) {
return &subscriptions[i];
}
}
return NULL;
}
/* TODO there should be better way of determining max supported MTU */
#define MAX_NOTIF_DATA (MIN(BT_L2CAP_RX_MTU, BT_L2CAP_TX_MTU) - 3)
static uint8_t ev_buf[sizeof(struct btp_gatt_notification_ev) + MAX_NOTIF_DATA];
static uint8_t notify_func(struct bt_conn *conn,
struct bt_gatt_subscribe_params *params,
const void *data, uint16_t length)
{
struct btp_gatt_notification_ev *ev = (void *) ev_buf;
if (!conn || !data) {
LOG_DBG("Unsubscribed");
(void)memset(params, 0, sizeof(*params));
return BT_GATT_ITER_STOP;
}
ev->type = (uint8_t)params->value;
ev->handle = sys_cpu_to_le16(params->value_handle);
length = MIN(length, MAX_NOTIF_DATA);
ev->data_length = sys_cpu_to_le16(length);
memcpy(ev->data, data, length);
bt_addr_le_copy(&ev->address, bt_conn_get_dst(conn));
tester_event(BTP_SERVICE_ID_GATT, BTP_GATT_EV_NOTIFICATION,
ev, sizeof(*ev) + length);
return BT_GATT_ITER_CONTINUE;
}
static void discover_complete(struct bt_conn *conn,
struct bt_gatt_discover_params *params)
{
struct bt_gatt_subscribe_params *subscription;
uint8_t op, status;
subscription = find_subscription(discover_params.end_handle);
__ASSERT_NO_MSG(subscription);
/* If no value handle it means that chrc has not been found */
if (!subscription->value_handle) {
status = BTP_STATUS_FAILED;
goto fail;
}
subscription->chan_opt = BT_ATT_CHAN_OPT_NONE;
if (bt_gatt_subscribe(conn, subscription) < 0) {
status = BTP_STATUS_FAILED;
goto fail;
}
status = BTP_STATUS_SUCCESS;
fail:
op = subscription->value == BT_GATT_CCC_NOTIFY ? BTP_GATT_CFG_NOTIFY :
BTP_GATT_CFG_INDICATE;
if (status == BTP_STATUS_FAILED) {
(void)memset(subscription, 0, sizeof(*subscription));
}
tester_rsp(BTP_SERVICE_ID_GATT, op, status);
(void)memset(params, 0, sizeof(*params));
}
static uint8_t discover_func(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
struct bt_gatt_subscribe_params *subscription;
if (!attr) {
discover_complete(conn, params);
return BT_GATT_ITER_STOP;
}
subscription = find_subscription(discover_params.end_handle);
__ASSERT_NO_MSG(subscription);
/* Characteristic Value Handle is the next handle beyond declaration */
subscription->value_handle = attr->handle + 1;
/*
* Continue characteristic discovery to get last characteristic
* preceding this CCC descriptor
*/
return BT_GATT_ITER_CONTINUE;
}
static int enable_subscription(struct bt_conn *conn, uint16_t ccc_handle,
uint16_t value)
{
struct bt_gatt_subscribe_params *subscription;
/* find unused subscription */
subscription = find_subscription(UNUSED_SUBSCRIBE_CCC_HANDLE);
if (!subscription) {
return -ENOMEM;
}
/* if discovery is busy fail */
if (discover_params.start_handle) {
return -EBUSY;
}
/* Discover Characteristic Value this CCC Descriptor refers to */
discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
discover_params.end_handle = ccc_handle;
discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
discover_params.func = discover_func;
discover_params.chan_opt = BT_ATT_CHAN_OPT_NONE;
subscription->ccc_handle = ccc_handle;
subscription->value = value;
subscription->notify = notify_func;
/* require security level from time of subscription */
subscription->min_security = bt_conn_get_security(conn);
return bt_gatt_discover(conn, &discover_params);
}
static int disable_subscription(struct bt_conn *conn, uint16_t ccc_handle)
{
struct bt_gatt_subscribe_params *subscription;
/* Fail if CCC handle doesn't match */
subscription = find_subscription(ccc_handle);
if (!subscription) {
LOG_ERR("CCC handle doesn't match");
return -EINVAL;
}
if (bt_gatt_unsubscribe(conn, subscription) < 0) {
return -EBUSY;
}
(void)memset(subscription, 0, sizeof(*subscription));
return 0;
}
static uint8_t config_subscription_notif(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_cfg_notify_cmd *cp = cmd;
struct bt_conn *conn;
uint16_t ccc_handle = sys_le16_to_cpu(cp->ccc_handle);
uint8_t status;
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
return BTP_STATUS_FAILED;
}
if (cp->enable) {
/* on success response will be sent from callback */
if (enable_subscription(conn, ccc_handle, BT_GATT_CCC_NOTIFY) == 0) {
bt_conn_unref(conn);
return BTP_STATUS_DELAY_REPLY;
}
status = BTP_STATUS_FAILED;
} else {
if (disable_subscription(conn, ccc_handle) < 0) {
status = BTP_STATUS_FAILED;
} else {
status = BTP_STATUS_SUCCESS;
}
}
LOG_DBG("Config notification subscription status %u", status);
bt_conn_unref(conn);
return status;
}
static uint8_t config_subscription_ind(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_cfg_notify_cmd *cp = cmd;
struct bt_conn *conn;
uint16_t ccc_handle = sys_le16_to_cpu(cp->ccc_handle);
uint8_t status;
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
return BTP_STATUS_FAILED;
}
if (cp->enable) {
/* on success response will be sent from callback */
if (enable_subscription(conn, ccc_handle, BT_GATT_CCC_INDICATE) == 0) {
bt_conn_unref(conn);
return BTP_STATUS_DELAY_REPLY;
}
status = BTP_STATUS_FAILED;
} else {
if (disable_subscription(conn, ccc_handle) < 0) {
status = BTP_STATUS_FAILED;
} else {
status = BTP_STATUS_SUCCESS;
}
}
LOG_DBG("Config indication subscription status %u", status);
bt_conn_unref(conn);
return status;
}
#if defined(CONFIG_BT_GATT_NOTIFY_MULTIPLE)
static void notify_cb(struct bt_conn *conn, void *user_data)
{
LOG_DBG("Nofication sent");
}
static uint8_t notify_mult(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_cfg_notify_mult_cmd *cp = cmd;
const size_t max_cnt = CONFIG_BT_L2CAP_TX_BUF_COUNT;
struct bt_gatt_notify_params params[max_cnt];
struct bt_conn *conn;
const size_t min_cnt = 1U;
int err = 0;
uint16_t attr_data_len = 0;
if ((cmd_len < sizeof(*cp)) ||
(cmd_len != sizeof(*cp) + (cp->cnt * sizeof(cp->attr_id[0])))) {
return BTP_STATUS_FAILED;
}
if (!IN_RANGE(cp->cnt, min_cnt, max_cnt)) {
LOG_ERR("Invalid count value %d (range %zu to %zu)",
cp->cnt, min_cnt, max_cnt);
return BTP_STATUS_FAILED;
}
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
return BTP_STATUS_FAILED;
}
(void)memset(params, 0, sizeof(params));
for (uint16_t i = 0U; i < cp->cnt; i++) {
struct bt_gatt_attr attr = server_db[cp->attr_id[i] -
server_db[0].handle];
attr_data_len = strtoul(attr.user_data, NULL, 16);
params[i].uuid = 0;
params[i].attr = &attr;
params[i].data = &attr.user_data;
params[i].len = attr_data_len;
params[i].func = notify_cb;
params[i].user_data = NULL;
}
err = bt_gatt_notify_multiple(conn, cp->cnt, params);
if (err != 0) {
LOG_ERR("bt_gatt_notify_multiple failed: %d", err);
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
LOG_DBG("Send %u notifications", cp->cnt);
bt_conn_unref(conn);
return BTP_STATUS_SUCCESS;
}
#endif /* CONFIG_BT_GATT_NOTIFY_MULTIPLE */
struct get_attrs_foreach_data {
struct net_buf_simple *buf;
struct bt_uuid *uuid;
uint8_t count;
};
static uint8_t get_attrs_rp(const struct bt_gatt_attr *attr, uint16_t handle,
void *user_data)
{
struct get_attrs_foreach_data *foreach = user_data;
struct btp_gatt_attr *gatt_attr;
if (foreach->uuid && bt_uuid_cmp(foreach->uuid, attr->uuid)) {
return BT_GATT_ITER_CONTINUE;
}
gatt_attr = net_buf_simple_add(foreach->buf, sizeof(*gatt_attr));
gatt_attr->handle = sys_cpu_to_le16(handle);
gatt_attr->permission = attr->perm;
if (attr->uuid->type == BT_UUID_TYPE_16) {
gatt_attr->type_length = 2U;
net_buf_simple_add_le16(foreach->buf,
BT_UUID_16(attr->uuid)->val);
} else {
gatt_attr->type_length = 16U;
net_buf_simple_add_mem(foreach->buf,
BT_UUID_128(attr->uuid)->val,
gatt_attr->type_length);
}
foreach->count++;
return BT_GATT_ITER_CONTINUE;
}
static uint8_t get_attrs(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_get_attributes_cmd *cp = cmd;
struct btp_gatt_get_attributes_rp *rp = rsp;
struct net_buf_simple *buf = NET_BUF_SIMPLE(BTP_DATA_MAX_SIZE - sizeof(*rp));
struct get_attrs_foreach_data foreach;
uint16_t start_handle, end_handle;
union uuid uuid;
if ((cmd_len < sizeof(*cp)) ||
(cmd_len != sizeof(*cp) + cp->type_length)) {
return BTP_STATUS_FAILED;
}
start_handle = sys_le16_to_cpu(cp->start_handle);
end_handle = sys_le16_to_cpu(cp->end_handle);
if (cp->type_length) {
char uuid_str[BT_UUID_STR_LEN];
if (btp2bt_uuid(cp->type, cp->type_length, &uuid.uuid)) {
return BTP_STATUS_FAILED;
}
bt_uuid_to_str(&uuid.uuid, uuid_str, sizeof(uuid_str));
LOG_DBG("start 0x%04x end 0x%04x, uuid %s", start_handle,
end_handle, uuid_str);
foreach.uuid = &uuid.uuid;
} else {
LOG_DBG("start 0x%04x end 0x%04x", start_handle, end_handle);
foreach.uuid = NULL;
}
net_buf_simple_init(buf, 0);
foreach.buf = buf;
foreach.count = 0U;
bt_gatt_foreach_attr(start_handle, end_handle, get_attrs_rp, &foreach);
(void)memcpy(rp->attrs, buf->data, buf->len);
rp->attrs_count = foreach.count;
*rsp_len = sizeof(*rp) + buf->len;
return BTP_STATUS_SUCCESS;
}
static uint8_t err_to_att(int err)
{
if (err < 0 && err >= -0xff) {
return -err;
}
return BT_ATT_ERR_UNLIKELY;
}
static uint8_t get_attr_val_rp(const struct bt_gatt_attr *attr, uint16_t handle,
void *user_data)
{
struct get_attr_data *u_data = user_data;
struct net_buf_simple *buf = u_data->buf;
struct bt_conn *conn = u_data->conn;
struct btp_gatt_get_attribute_value_rp *rp;
ssize_t read, to_read;
rp = net_buf_simple_add(buf, sizeof(*rp));
rp->value_length = 0x0000;
rp->att_response = BT_ATT_ERR_SUCCESS;
do {
to_read = net_buf_simple_tailroom(buf);
if (!attr->read) {
rp->att_response = BT_ATT_ERR_READ_NOT_PERMITTED;
break;
}
read = attr->read(conn, attr, buf->data + buf->len, to_read,
rp->value_length);
if (read < 0) {
rp->att_response = err_to_att(read);
break;
}
rp->value_length += read;
net_buf_simple_add(buf, read);
} while (read == to_read);
/* use userdata only for tester own attributes */
if (IS_ARRAY_ELEMENT(server_db, attr)) {
const struct gatt_value *value = attr->user_data;
if ((rp->att_response == BT_ATT_ERR_SUCCESS) && (value->enc_key_size > 0)) {
/*
* If attribute has enc_key_size set to non-zero value
* it means that it is used for testing encryption key size
* error on GATT database access and we need to report it
* when local database is read.
*
* It is min key size and is used to trigger error on GATT operation
* when PTS pairs with small key size (typically it is set it to 16
* for specified test characteristics, while PTS pairs with keysize
* set to <16, but is can be of any 7-16 value)
*
* Depending on test, PTS may ask about handle during connection or
* prior to connection. If former we validate keysize against
* current connection, if latter we just report error status.
*
* Note that we report expected error and data as this is used for
* PTS validation and not actual GATT operation.
*/
if (conn) {
if (value->enc_key_size > bt_conn_enc_key_size(conn)) {
rp->att_response = BT_ATT_ERR_ENCRYPTION_KEY_SIZE;
}
} else {
rp->att_response = BT_ATT_ERR_ENCRYPTION_KEY_SIZE;
}
}
}
return BT_GATT_ITER_STOP;
}
static uint8_t get_attr_val(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_get_attribute_value_cmd *cp = cmd;
struct net_buf_simple *buf = NET_BUF_SIMPLE(BTP_DATA_MAX_SIZE);
uint16_t handle = sys_le16_to_cpu(cp->handle);
struct bt_conn *conn;
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
net_buf_simple_init(buf, 0);
struct get_attr_data cb_data = { .buf = buf, .conn = conn };
bt_gatt_foreach_attr(handle, handle, get_attr_val_rp, &cb_data);
if (buf->len) {
(void)memcpy(rsp, buf->data, buf->len);
*rsp_len = buf->len;
return BTP_STATUS_SUCCESS;
}
return BTP_STATUS_FAILED;
}
static struct bt_uuid_128 test_uuid = BT_UUID_INIT_128(
0x94, 0x99, 0xb6, 0xa9, 0xcd, 0x1c, 0x42, 0x95,
0xb2, 0x07, 0x2f, 0x7f, 0xec, 0xc0, 0xc7, 0x5b);
static struct bt_gatt_attr test_attrs[] = {
BT_GATT_PRIMARY_SERVICE(&test_uuid),
};
static struct bt_gatt_service test_service = BT_GATT_SERVICE(test_attrs);
static uint8_t change_database(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_change_db_cmd *cp = cmd;
static bool test_service_registered;
int err;
/* currently support only "any" handles */
if (cp->start_handle > 0 || cp->end_handle > 0) {
return BTP_STATUS_FAILED;
}
switch (cp->operation) {
case BTP_GATT_CHANGE_DB_ADD:
if (test_service_registered) {
return BTP_STATUS_FAILED;
}
err = bt_gatt_service_register(&test_service);
break;
case BTP_GATT_CHANGE_DB_REMOVE:
if (!test_service_registered) {
return BTP_STATUS_FAILED;
}
err = bt_gatt_service_unregister(&test_service);
break;
case BTP_GATT_CHANGE_DB_ANY:
if (test_service_registered) {
err = bt_gatt_service_unregister(&test_service);
} else {
err = bt_gatt_service_register(&test_service);
}
break;
default:
return BTP_STATUS_FAILED;
}
if (err) {
return BTP_STATUS_FAILED;
}
test_service_registered = !test_service_registered;
return BTP_STATUS_SUCCESS;
}
static uint8_t eatt_connect(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_gatt_eatt_connect_cmd *cp = cmd;
struct bt_conn *conn;
int err;
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
return BTP_STATUS_FAILED;
}
err = bt_eatt_connect(conn, cp->num_channels);
if (err) {
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
bt_conn_unref(conn);
return BTP_STATUS_SUCCESS;
}
static const struct btp_handler handlers[] = {
{
.opcode = BTP_GATT_READ_SUPPORTED_COMMANDS,
.index = BTP_INDEX_NONE,
.expect_len = 0,
.func = supported_commands,
},
{
.opcode = BTP_GATT_ADD_SERVICE,
.expect_len = BTP_HANDLER_LENGTH_VARIABLE,
.func = add_service,
},
{
.opcode = BTP_GATT_ADD_CHARACTERISTIC,
.expect_len = BTP_HANDLER_LENGTH_VARIABLE,
.func = add_characteristic,
},
{
.opcode = BTP_GATT_ADD_DESCRIPTOR,
.expect_len = BTP_HANDLER_LENGTH_VARIABLE,
.func = add_descriptor,
},
{
.opcode = BTP_GATT_ADD_INCLUDED_SERVICE,
.expect_len = sizeof(struct btp_gatt_add_included_service_cmd),
.func = add_included,
},
{
.opcode = BTP_GATT_SET_VALUE,
.expect_len = BTP_HANDLER_LENGTH_VARIABLE,
.func = set_value,
},
{
.opcode = BTP_GATT_START_SERVER,
.expect_len = 0,
.func = start_server,
},
{
.opcode = BTP_GATT_SET_ENC_KEY_SIZE,
.expect_len = sizeof(struct btp_gatt_set_enc_key_size_cmd),
.func = set_enc_key_size,
},
{
.opcode = BTP_GATT_EXCHANGE_MTU,
.expect_len = sizeof(struct btp_gatt_exchange_mtu_cmd),
.func = exchange_mtu,
},
{
.opcode = BTP_GATT_DISC_ALL_PRIM,
.expect_len = sizeof(struct btp_gatt_disc_all_prim_cmd),
.func = disc_all_prim,
},
{
.opcode = BTP_GATT_DISC_PRIM_UUID,
.expect_len = BTP_HANDLER_LENGTH_VARIABLE,
.func = disc_prim_uuid,
},
{
.opcode = BTP_GATT_FIND_INCLUDED,
.expect_len = sizeof(struct btp_gatt_find_included_cmd),
.func = find_included,
},
{
.opcode = BTP_GATT_DISC_ALL_CHRC,
.expect_len = sizeof(struct btp_gatt_disc_all_chrc_cmd),
.func = disc_all_chrc,
},
{
.opcode = BTP_GATT_DISC_CHRC_UUID,
.expect_len = BTP_HANDLER_LENGTH_VARIABLE,
.func = disc_chrc_uuid,
},
{
.opcode = BTP_GATT_DISC_ALL_DESC,
.expect_len = sizeof(struct btp_gatt_disc_all_desc_cmd),
.func = disc_all_desc,
},
{
.opcode = BTP_GATT_READ,
.expect_len = sizeof(struct btp_gatt_read_cmd),
.func = read_data,
},
{
.opcode = BTP_GATT_READ_UUID,
.expect_len = BTP_HANDLER_LENGTH_VARIABLE,
.func = read_uuid,
},
{
.opcode = BTP_GATT_READ_LONG,
.expect_len = sizeof(struct btp_gatt_read_long_cmd),
.func = read_long,
},
{
.opcode = BTP_GATT_READ_MULTIPLE,
.expect_len = BTP_HANDLER_LENGTH_VARIABLE,
.func = read_multiple,
},
{
.opcode = BTP_GATT_WRITE_WITHOUT_RSP,
.expect_len = BTP_HANDLER_LENGTH_VARIABLE,
.func = write_without_rsp,
},
{
.opcode = BTP_GATT_SIGNED_WRITE_WITHOUT_RSP,
.expect_len = BTP_HANDLER_LENGTH_VARIABLE,
.func = write_signed_without_rsp,
},
{
.opcode = BTP_GATT_WRITE,
.expect_len = BTP_HANDLER_LENGTH_VARIABLE,
.func = write_data,
},
{
.opcode = BTP_GATT_WRITE_LONG,
.expect_len = BTP_HANDLER_LENGTH_VARIABLE,
.func = write_long,
},
{
.opcode = BTP_GATT_CFG_NOTIFY,
.expect_len = sizeof(struct btp_gatt_cfg_notify_cmd),
.func = config_subscription_notif,
},
{
.opcode = BTP_GATT_CFG_INDICATE,
.expect_len = sizeof(struct btp_gatt_cfg_notify_cmd),
.func = config_subscription_ind,
},
{
.opcode = BTP_GATT_GET_ATTRIBUTES,
.expect_len = BTP_HANDLER_LENGTH_VARIABLE,
.func = get_attrs,
},
{
.opcode = BTP_GATT_GET_ATTRIBUTE_VALUE,
.expect_len = sizeof(struct btp_gatt_get_attribute_value_cmd),
.func = get_attr_val,
},
{
.opcode = BTP_GATT_CHANGE_DB,
.expect_len = sizeof(struct btp_gatt_change_db_cmd),
.func = change_database,
},
{
.opcode = BTP_GATT_EATT_CONNECT,
.expect_len = sizeof(struct btp_gatt_eatt_connect_cmd),
.func = eatt_connect,
},
{
.opcode = BTP_GATT_READ_MULTIPLE_VAR,
.expect_len = BTP_HANDLER_LENGTH_VARIABLE,
.func = read_multiple_var,
},
{
.opcode = BTP_GATT_NOTIFY_MULTIPLE,
.expect_len = BTP_HANDLER_LENGTH_VARIABLE,
.func = notify_mult,
},
};
uint8_t tester_init_gatt(void)
{
server_buf = net_buf_alloc(&server_pool, K_NO_WAIT);
if (!server_buf) {
return BTP_STATUS_FAILED;
}
net_buf_reserve(server_buf, SERVER_BUF_SIZE);
tester_register_command_handlers(BTP_SERVICE_ID_GATT, handlers,
ARRAY_SIZE(handlers));
return BTP_STATUS_SUCCESS;
}
uint8_t tester_unregister_gatt(void)
{
return BTP_STATUS_SUCCESS;
}