| /* 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 ((ccc_values[i].attr != NULL) && (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 == 0 || cp->handles_count > ARRAY_SIZE(handles)) { |
| return BTP_STATUS_FAILED; |
| } |
| |
| for (i = 0; i < cp->handles_count; 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 = cp->handles_count; |
| 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; |
| const 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 search_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, &search_uuid.uuid)) { |
| return BTP_STATUS_FAILED; |
| } |
| |
| bt_uuid_to_str(&search_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 = &search_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 const 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; |
| } |