This is exposed to allow client reading characteristic values
with known uuid.
---
src/shared/gatt-client.c | 263 +++++++++++++++++++++++++++++++++++++++++++++++
src/shared/gatt-client.h | 18 ++++
2 files changed, 281 insertions(+)
diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index cf93d4b..72ffed5 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
@@ -2009,6 +2009,269 @@ bool bt_gatt_client_read_long_value(struct bt_gatt_client *client,
return true;
}
+struct read_by_uuid_res {
+ uint16_t handle;
+ uint8_t length;
+ uint8_t *data;
+ struct read_by_uuid_res *next;
+};
+
+bool read_by_uuid_res_get(struct read_by_uuid_res *result, uint16_t *handle,
+ uint8_t *length, uint8_t **data)
+{
+ if (!result)
+ return false;
+
+ *length = result->length;
+ *handle = result->handle;
+ *data = result->data;
+
+ return true;
+}
+
+struct read_by_uuid_res *read_by_uuid_res_next(struct read_by_uuid_res *result)
+{
+ if (!result)
+ return NULL;
+
+ return result->next;
+}
+
+struct read_by_uuid_op {
+ struct bt_gatt_client *client;
+ bt_gatt_client_read_by_uuid_callback_t callback;
+ int ref_count;
+ uint16_t start_handle;
+ uint16_t end_handle;
+ bt_uuid_t uuid;
+ struct read_by_uuid_res *result_head;
+ struct read_by_uuid_res *result_tail;
+ void *user_data;
+ bt_gatt_client_destroy_func_t destroy;
+};
+
+static bool append_read_by_uuid_result(struct read_by_uuid_op *op,
+ uint16_t handle, uint16_t length,
+ const uint8_t *data)
+{
+ struct read_by_uuid_res *result;
+
+ result = new0(struct read_by_uuid_res, 1);
+ if (!result)
+ return false;
+
+ result->data = malloc(length);
+ if (!result->data) {
+ free(result);
+ return false;
+ }
+
+ result->handle = handle;
+ result->length = length;
+ memcpy(result->data, data, length);
+
+ if (op->result_head) {
+ op->result_tail->next = result;
+ op->result_tail = result;
+ } else {
+ op->result_head = op->result_tail = result;
+ }
+
+ return true;
+}
+
+static void destroy_read_by_uuid_result(void *data)
+{
+ struct read_by_uuid_res *result = data;
+
+ free(result->data);
+ free(result);
+}
+
+static struct read_by_uuid_op *read_by_uuid_op_ref(struct read_by_uuid_op *op)
+{
+ __sync_fetch_and_add(&op->ref_count, 1);
+
+ return op;
+}
+
+static void read_by_uuid_op_unref(void *data)
+{
+ struct read_by_uuid_res *next, *temp;
+ struct read_by_uuid_op *op = data;
+
+ if (__sync_sub_and_fetch(&op->ref_count, 1))
+ return;
+
+ if (op->destroy)
+ op->destroy(op->user_data);
+
+ for (temp = op->result_head; temp; temp = next) {
+ next = temp->next;
+ destroy_read_by_uuid_result(temp);
+ }
+
+ free(op);
+}
+
+static void read_by_uuid_cb(uint8_t opcode, const void *pdu,
+ uint16_t length, void *user_data)
+{
+ struct read_by_uuid_op *op = user_data;
+ size_t data_length;
+ uint8_t att_ecode;
+ uint16_t offset, last_handle;
+ const uint8_t *data;
+ bool success;
+
+ if (opcode == BT_ATT_OP_ERROR_RSP) {
+ att_ecode = process_error(pdu, length);
+ if (att_ecode == BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND)
+ success = true;
+ else
+ success = false;
+
+ goto done;
+ }
+
+ if (opcode != BT_ATT_OP_READ_BY_TYPE_RSP || (!pdu && length)) {
+ success = false;
+ att_ecode = 0;
+ goto done;
+ }
+
+ data_length = ((const uint8_t *) pdu)[0];
+ if ((length - 1) % data_length) {
+ success = false;
+ att_ecode = 0;
+ goto done;
+ }
+
+ for (offset = 1; offset < length; offset += data_length) {
+ data = pdu + offset;
+ if (!append_read_by_uuid_result(op, get_le16(data),
+ data_length - 2, data + 2)) {
+ success = false;
+ att_ecode = 0;
+ goto done;
+ }
+ }
+
+ last_handle = get_le16(pdu + length - data_length);
+ if (last_handle < op->end_handle) {
+ uint8_t pdu[20];
+
+ put_le16(last_handle + 1, pdu);
+ put_le16(op->end_handle, pdu + 2);
+
+ if (op->uuid.type == BT_UUID16) {
+ put_le16(op->uuid.value.u16, pdu + 4);
+ /*
+ * 2 octets - start handle
+ * 2 octets - end handle
+ * 2 octets - 16-bit uuid
+ */
+ length = 6;
+ } else {
+ bswap_128(&op->uuid.value.u128, pdu + 4);
+ /*
+ * 2 octets - start handle
+ * 2 octets - end handle
+ * 16 octets - 128-bit uuid
+ */
+ length = 20;
+ }
+
+ if (!bt_att_send(op->client->att, BT_ATT_OP_READ_BY_TYPE_REQ,
+ pdu, length, read_by_uuid_cb,
+ read_by_uuid_op_ref(op),
+ read_by_uuid_op_unref)) {
+ read_by_uuid_op_unref(op);
+ success = false;
+ att_ecode = 0;
+ goto done;
+ }
+
+ return;
+ }
+
+ success = true;
+ att_ecode = 0;
+
+done:
+ if (op->callback)
+ op->callback(success, att_ecode, op->result_head,
+ op->user_data);
+}
+
+bool bt_gatt_client_read_by_uuid(struct bt_gatt_client *client,
+ uint16_t start_handle, uint16_t end_handle,
+ const uint8_t uuid[BT_GATT_UUID_SIZE],
+ bt_gatt_client_read_by_uuid_callback_t callback,
+ void *user_data,
+ bt_gatt_client_destroy_func_t destroy)
+{
+ struct read_by_uuid_op *op;
+ bt_uuid_t uuid_16, uuid_128;
+ uint16_t length;
+ uint8_t pdu[20];
+ uint128_t u128;
+
+ if (!client)
+ return false;
+
+ if (start_handle > end_handle || start_handle == 0x0000)
+ return false;
+
+ op = new0(struct read_by_uuid_op, 1);
+ if (!op)
+ return false;
+
+ op->client = client;
+ op->start_handle = start_handle;
+ op->end_handle = end_handle;
+ op->destroy = destroy;
+ op->callback = callback;
+ op->user_data = user_data;
+
+ bt_uuid16_create(&uuid_16, ((uuid[2] << 8) + uuid[3]));
+ memcpy(u128.data, uuid, sizeof(u128.data));
+ bt_uuid128_create(&uuid_128, u128);
+
+ if (bt_uuid_cmp(&uuid_128, &uuid_16)) {
+ op->uuid = uuid_128;
+ bswap_128(&op->uuid.value.u128, pdu + 4);
+ /*
+ * 2 octets - start handle
+ * 2 octets - end handle
+ * 16 octets - 128-bit uuid
+ */
+ length = 20;
+ } else {
+ op->uuid = uuid_16;
+ put_le16(op->uuid.value.u16, pdu + 4);
+ /*
+ * 2 octets - start handle
+ * 2 octets - end handle
+ * 2 octets - 16-bit uuid
+ */
+ length = 6;
+ }
+
+ put_le16(start_handle, pdu);
+ put_le16(end_handle, pdu + 2);
+
+ if (!bt_att_send(client->att, BT_ATT_OP_READ_BY_TYPE_REQ, pdu,
+ length, read_by_uuid_cb,
+ read_by_uuid_op_ref(op),
+ read_by_uuid_op_unref)) {
+ free(op);
+ return false;
+ }
+
+ return true;
+}
+
bool bt_gatt_client_write_without_response(struct bt_gatt_client *client,
uint16_t value_handle,
bool signed_write,
diff --git a/src/shared/gatt-client.h b/src/shared/gatt-client.h
index adccfc5..921ed75 100644
--- a/src/shared/gatt-client.h
+++ b/src/shared/gatt-client.h
@@ -145,6 +145,24 @@ bool bt_gatt_client_read_long_value(struct bt_gatt_client *client,
void *user_data,
bt_gatt_client_destroy_func_t destroy);
+struct read_by_uuid_res;
+
+struct read_by_uuid_res *read_by_uuid_res_next(struct read_by_uuid_res *result);
+bool read_by_uuid_res_get(struct read_by_uuid_res *result, uint16_t *handle,
+ uint8_t *length, uint8_t **data);
+
+typedef void (*bt_gatt_client_read_by_uuid_callback_t)(bool success,
+ uint8_t att_ecode,
+ struct read_by_uuid_res *result,
+ void *user_data);
+
+bool bt_gatt_client_read_by_uuid(struct bt_gatt_client *client,
+ uint16_t start_handle, uint16_t end_handle,
+ const uint8_t uuid[BT_GATT_UUID_SIZE],
+ bt_gatt_client_read_by_uuid_callback_t callback,
+ void *user_data,
+ bt_gatt_client_destroy_func_t destroy);
+
bool bt_gatt_client_write_without_response(struct bt_gatt_client *client,
uint16_t value_handle,
bool signed_write,
--
1.9.3
Hi Marcin,
> On Mon, Nov 17, 2014 at 11:51 AM, Marcin Kraglak <[email protected]> wrote:
> This is exposed to allow client reading characteristic values
> with known uuid.
> ---
> src/shared/gatt-client.c | 263 +++++++++++++++++++++++++++++++++++++++++++++++
> src/shared/gatt-client.h | 18 ++++
> 2 files changed, 281 insertions(+)
>
> diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
> index cf93d4b..72ffed5 100644
> --- a/src/shared/gatt-client.c
> +++ b/src/shared/gatt-client.c
> @@ -2009,6 +2009,269 @@ bool bt_gatt_client_read_long_value(struct bt_gatt_client *client,
> return true;
> }
>
> +struct read_by_uuid_res {
> + uint16_t handle;
> + uint8_t length;
> + uint8_t *data;
> + struct read_by_uuid_res *next;
> +};
> +
> +bool read_by_uuid_res_get(struct read_by_uuid_res *result, uint16_t *handle,
> + uint8_t *length, uint8_t **data)
> +{
> + if (!result)
> + return false;
> +
> + *length = result->length;
> + *handle = result->handle;
> + *data = result->data;
> +
> + return true;
> +}
> +
> +struct read_by_uuid_res *read_by_uuid_res_next(struct read_by_uuid_res *result)
> +{
> + if (!result)
> + return NULL;
> +
> + return result->next;
> +}
> +
> +struct read_by_uuid_op {
> + struct bt_gatt_client *client;
> + bt_gatt_client_read_by_uuid_callback_t callback;
> + int ref_count;
> + uint16_t start_handle;
> + uint16_t end_handle;
> + bt_uuid_t uuid;
> + struct read_by_uuid_res *result_head;
> + struct read_by_uuid_res *result_tail;
> + void *user_data;
> + bt_gatt_client_destroy_func_t destroy;
> +};
> +
> +static bool append_read_by_uuid_result(struct read_by_uuid_op *op,
> + uint16_t handle, uint16_t length,
> + const uint8_t *data)
> +{
> + struct read_by_uuid_res *result;
> +
> + result = new0(struct read_by_uuid_res, 1);
> + if (!result)
> + return false;
> +
> + result->data = malloc(length);
> + if (!result->data) {
> + free(result);
> + return false;
> + }
> +
> + result->handle = handle;
> + result->length = length;
> + memcpy(result->data, data, length);
> +
> + if (op->result_head) {
> + op->result_tail->next = result;
> + op->result_tail = result;
> + } else {
> + op->result_head = op->result_tail = result;
> + }
> +
> + return true;
> +}
> +
> +static void destroy_read_by_uuid_result(void *data)
> +{
> + struct read_by_uuid_res *result = data;
> +
> + free(result->data);
> + free(result);
> +}
> +
> +static struct read_by_uuid_op *read_by_uuid_op_ref(struct read_by_uuid_op *op)
> +{
> + __sync_fetch_and_add(&op->ref_count, 1);
> +
> + return op;
> +}
> +
> +static void read_by_uuid_op_unref(void *data)
> +{
> + struct read_by_uuid_res *next, *temp;
> + struct read_by_uuid_op *op = data;
> +
> + if (__sync_sub_and_fetch(&op->ref_count, 1))
> + return;
> +
> + if (op->destroy)
> + op->destroy(op->user_data);
> +
> + for (temp = op->result_head; temp; temp = next) {
> + next = temp->next;
> + destroy_read_by_uuid_result(temp);
> + }
> +
> + free(op);
> +}
> +
> +static void read_by_uuid_cb(uint8_t opcode, const void *pdu,
> + uint16_t length, void *user_data)
> +{
> + struct read_by_uuid_op *op = user_data;
> + size_t data_length;
> + uint8_t att_ecode;
> + uint16_t offset, last_handle;
> + const uint8_t *data;
> + bool success;
> +
> + if (opcode == BT_ATT_OP_ERROR_RSP) {
> + att_ecode = process_error(pdu, length);
> + if (att_ecode == BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND)
> + success = true;
> + else
> + success = false;
> +
> + goto done;
> + }
> +
> + if (opcode != BT_ATT_OP_READ_BY_TYPE_RSP || (!pdu && length)) {
> + success = false;
> + att_ecode = 0;
> + goto done;
> + }
> +
> + data_length = ((const uint8_t *) pdu)[0];
> + if ((length - 1) % data_length) {
> + success = false;
> + att_ecode = 0;
> + goto done;
> + }
> +
> + for (offset = 1; offset < length; offset += data_length) {
> + data = pdu + offset;
> + if (!append_read_by_uuid_result(op, get_le16(data),
> + data_length - 2, data + 2)) {
> + success = false;
> + att_ecode = 0;
> + goto done;
> + }
> + }
> +
> + last_handle = get_le16(pdu + length - data_length);
> + if (last_handle < op->end_handle) {
> + uint8_t pdu[20];
> +
> + put_le16(last_handle + 1, pdu);
> + put_le16(op->end_handle, pdu + 2);
> +
> + if (op->uuid.type == BT_UUID16) {
> + put_le16(op->uuid.value.u16, pdu + 4);
> + /*
> + * 2 octets - start handle
> + * 2 octets - end handle
> + * 2 octets - 16-bit uuid
> + */
> + length = 6;
> + } else {
> + bswap_128(&op->uuid.value.u128, pdu + 4);
> + /*
> + * 2 octets - start handle
> + * 2 octets - end handle
> + * 16 octets - 128-bit uuid
> + */
> + length = 20;
> + }
> +
> + if (!bt_att_send(op->client->att, BT_ATT_OP_READ_BY_TYPE_REQ,
> + pdu, length, read_by_uuid_cb,
> + read_by_uuid_op_ref(op),
> + read_by_uuid_op_unref)) {
> + read_by_uuid_op_unref(op);
> + success = false;
> + att_ecode = 0;
> + goto done;
> + }
> +
> + return;
> + }
> +
> + success = true;
> + att_ecode = 0;
> +
> +done:
> + if (op->callback)
> + op->callback(success, att_ecode, op->result_head,
> + op->user_data);
> +}
> +
> +bool bt_gatt_client_read_by_uuid(struct bt_gatt_client *client,
> + uint16_t start_handle, uint16_t end_handle,
> + const uint8_t uuid[BT_GATT_UUID_SIZE],
> + bt_gatt_client_read_by_uuid_callback_t callback,
> + void *user_data,
> + bt_gatt_client_destroy_func_t destroy)
> +{
> + struct read_by_uuid_op *op;
> + bt_uuid_t uuid_16, uuid_128;
> + uint16_t length;
> + uint8_t pdu[20];
> + uint128_t u128;
> +
> + if (!client)
> + return false;
> +
> + if (start_handle > end_handle || start_handle == 0x0000)
> + return false;
> +
> + op = new0(struct read_by_uuid_op, 1);
> + if (!op)
> + return false;
> +
> + op->client = client;
> + op->start_handle = start_handle;
> + op->end_handle = end_handle;
> + op->destroy = destroy;
> + op->callback = callback;
> + op->user_data = user_data;
> +
> + bt_uuid16_create(&uuid_16, ((uuid[2] << 8) + uuid[3]));
> + memcpy(u128.data, uuid, sizeof(u128.data));
> + bt_uuid128_create(&uuid_128, u128);
> +
> + if (bt_uuid_cmp(&uuid_128, &uuid_16)) {
> + op->uuid = uuid_128;
> + bswap_128(&op->uuid.value.u128, pdu + 4);
> + /*
> + * 2 octets - start handle
> + * 2 octets - end handle
> + * 16 octets - 128-bit uuid
> + */
> + length = 20;
> + } else {
> + op->uuid = uuid_16;
> + put_le16(op->uuid.value.u16, pdu + 4);
> + /*
> + * 2 octets - start handle
> + * 2 octets - end handle
> + * 2 octets - 16-bit uuid
> + */
> + length = 6;
> + }
> +
> + put_le16(start_handle, pdu);
> + put_le16(end_handle, pdu + 2);
> +
> + if (!bt_att_send(client->att, BT_ATT_OP_READ_BY_TYPE_REQ, pdu,
> + length, read_by_uuid_cb,
> + read_by_uuid_op_ref(op),
> + read_by_uuid_op_unref)) {
> + free(op);
> + return false;
> + }
> +
> + return true;
> +}
> +
> bool bt_gatt_client_write_without_response(struct bt_gatt_client *client,
> uint16_t value_handle,
> bool signed_write,
> diff --git a/src/shared/gatt-client.h b/src/shared/gatt-client.h
> index adccfc5..921ed75 100644
> --- a/src/shared/gatt-client.h
> +++ b/src/shared/gatt-client.h
> @@ -145,6 +145,24 @@ bool bt_gatt_client_read_long_value(struct bt_gatt_client *client,
> void *user_data,
> bt_gatt_client_destroy_func_t destroy);
>
> +struct read_by_uuid_res;
> +
> +struct read_by_uuid_res *read_by_uuid_res_next(struct read_by_uuid_res *result);
> +bool read_by_uuid_res_get(struct read_by_uuid_res *result, uint16_t *handle,
> + uint8_t *length, uint8_t **data);
> +
> +typedef void (*bt_gatt_client_read_by_uuid_callback_t)(bool success,
> + uint8_t att_ecode,
> + struct read_by_uuid_res *result,
> + void *user_data);
> +
> +bool bt_gatt_client_read_by_uuid(struct bt_gatt_client *client,
> + uint16_t start_handle, uint16_t end_handle,
> + const uint8_t uuid[BT_GATT_UUID_SIZE],
> + bt_gatt_client_read_by_uuid_callback_t callback,
> + void *user_data,
> + bt_gatt_client_destroy_func_t destroy);
> +
I really think that this doesn't belong here. We already have several
discovery functions in shared/gatt-helpers that do the exact same
thing (which is sending an ATT Read By Type request) and provide PDU
iterators. So, we should think about how we can perhaps factor out the
Read By Type request into a generic function in shared/gatt-helpers
and internally turn bt_gatt_discover_characteristics and
bt_gatt_discover_included_services functions into special cases of
that.
> bool bt_gatt_client_write_without_response(struct bt_gatt_client *client,
> uint16_t value_handle,
> bool signed_write,
> --
> 1.9.3
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
Thanks,
Arman
Verify Generic Attribute Profile client behavior when the Read Using
Characteristic UUID procedure fails due to insufficient encryption
key size.
---
unit/test-gatt.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/unit/test-gatt.c b/unit/test-gatt.c
index cf87d8c..9aee599 100644
--- a/unit/test-gatt.c
+++ b/unit/test-gatt.c
@@ -802,6 +802,15 @@ const struct test_step test_read_by_uuid_6 = {
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}
};
+const struct test_step test_read_by_uuid_7 = {
+ .handle = 0x0001,
+ .end_handle = 0xffff,
+ .func = test_read_by_uuid,
+ .expected_att_ecode = 0x0c,
+ .uuid = {0x00, 0x00, 0x2b, 0x0d, 0x00, 0x00, 0x10, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}
+};
+
static void test_client(gconstpointer data)
{
struct context *context = create_context(512, data);
@@ -1030,5 +1039,11 @@ int main(int argc, char *argv[])
raw_pdu(0x08, 0x01, 0x00, 0xff, 0xff, 0x0d, 0x2b),
raw_pdu(0x01, 0x08, 0x0b, 0x00, 0x05));
+ define_test_client("/TP/GAR/CL/BI-11-C", test_client, service_data_2,
+ &test_read_by_uuid_7,
+ SERVICE_DATA_2_PDU,
+ raw_pdu(0x08, 0x01, 0x00, 0xff, 0xff, 0x0d, 0x2b),
+ raw_pdu(0x01, 0x08, 0x0b, 0x00, 0x0c));
+
return g_test_run();
}
--
1.9.3
Verify Generic Attribute Profile client behavior when the Read Using
Characteristic UUID procedure fails due to insufficient authentication
---
unit/test-gatt.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/unit/test-gatt.c b/unit/test-gatt.c
index d0e5a15..cf87d8c 100644
--- a/unit/test-gatt.c
+++ b/unit/test-gatt.c
@@ -793,6 +793,15 @@ const struct test_step test_read_by_uuid_5 = {
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}
};
+const struct test_step test_read_by_uuid_6 = {
+ .handle = 0x0001,
+ .end_handle = 0xffff,
+ .func = test_read_by_uuid,
+ .expected_att_ecode = 0x05,
+ .uuid = {0x00, 0x00, 0x2b, 0x0d, 0x00, 0x00, 0x10, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}
+};
+
static void test_client(gconstpointer data)
{
struct context *context = create_context(512, data);
@@ -1015,5 +1024,11 @@ int main(int argc, char *argv[])
raw_pdu(0x08, 0x01, 0x00, 0xff, 0xff, 0x0d, 0x2b),
raw_pdu(0x01, 0x08, 0x0b, 0x00, 0x08));
+ define_test_client("/TP/GAR/CL/BI-10-C", test_client, service_data_2,
+ &test_read_by_uuid_6,
+ SERVICE_DATA_2_PDU,
+ raw_pdu(0x08, 0x01, 0x00, 0xff, 0xff, 0x0d, 0x2b),
+ raw_pdu(0x01, 0x08, 0x0b, 0x00, 0x05));
+
return g_test_run();
}
--
1.9.3
Verify Generic Attribute Profile client behavior when the Read Using
Characteristic UUID procedure fails due to insufficient authorization.
---
unit/test-gatt.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/unit/test-gatt.c b/unit/test-gatt.c
index 8cfc4a0..d0e5a15 100644
--- a/unit/test-gatt.c
+++ b/unit/test-gatt.c
@@ -784,6 +784,15 @@ const struct test_step test_read_by_uuid_4 = {
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}
};
+const struct test_step test_read_by_uuid_5 = {
+ .handle = 0x0001,
+ .end_handle = 0xffff,
+ .func = test_read_by_uuid,
+ .expected_att_ecode = 0x08,
+ .uuid = {0x00, 0x00, 0x2b, 0x0d, 0x00, 0x00, 0x10, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}
+};
+
static void test_client(gconstpointer data)
{
struct context *context = create_context(512, data);
@@ -1000,5 +1009,11 @@ int main(int argc, char *argv[])
raw_pdu(0x08, 0x01, 0x00, 0xff, 0xff, 0x0d, 0x2b),
raw_pdu(0x01, 0x08, 0x0b, 0x00, 0x0a));
+ define_test_client("/TP/GAR/CL/BI-09-C", test_client, service_data_2,
+ &test_read_by_uuid_5,
+ SERVICE_DATA_2_PDU,
+ raw_pdu(0x08, 0x01, 0x00, 0xff, 0xff, 0x0d, 0x2b),
+ raw_pdu(0x01, 0x08, 0x0b, 0x00, 0x08));
+
return g_test_run();
}
--
1.9.3
Verify that a Generic Attribute Profile client can read
a Characteristic Value selected by UUID using a 128-bit UUID.
---
unit/test-gatt.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 72 insertions(+)
diff --git a/unit/test-gatt.c b/unit/test-gatt.c
index 3e94bc1..4f88ce5 100644
--- a/unit/test-gatt.c
+++ b/unit/test-gatt.c
@@ -179,6 +179,32 @@ struct context {
raw_pdu(0x04, 0x0b, 0x00, 0x0b, 0x00), \
raw_pdu(0x05, 0x01, 0x0b, 0x00, 0x02, 0x29)
+#define SERVICE_DATA_3_PDU \
+ raw_pdu(0x02, 0x00, 0x02), \
+ raw_pdu(0x03, 0x00, 0x02), \
+ raw_pdu(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28), \
+ raw_pdu(0x11, 0x06, 0x01, 0x00, 0x07, 0x00, 0x01, 0x18),\
+ raw_pdu(0x10, 0x08, 0x00, 0xff, 0xff, 0x00, 0x28), \
+ raw_pdu(0x01, 0x10, 0x08, 0x00, 0x0a), \
+ raw_pdu(0x10, 0x01, 0x00, 0xff, 0xff, 0x01, 0x28), \
+ raw_pdu(0x01, 0x10, 0x01, 0x00, 0x0a), \
+ raw_pdu(0x08, 0x01, 0x00, 0x07, 0x00, 0x02, 0x28), \
+ raw_pdu(0x01, 0x08, 0x01, 0x00, 0x0a), \
+ raw_pdu(0x08, 0x01, 0x00, 0x07, 0x00, 0x03, 0x28), \
+ raw_pdu(0x09, 0x15, 0x02, 0x00, 0x02, 0x03, 0x00, 0x0e, \
+ 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, \
+ 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, \
+ 0x01, 0x00, 0x00), \
+ raw_pdu(0x08, 0x03, 0x00, 0x07, 0x00, 0x03, 0x28), \
+ raw_pdu(0x09, 0x07, 0x05, 0x00, 0x02, 0x06, 0x00, 0x00, \
+ 0x2a), \
+ raw_pdu(0x08, 0x06, 0x00, 0x07, 0x00, 0x03, 0x28), \
+ raw_pdu(0x01, 0x08, 0x06, 0x00, 0x0a), \
+ raw_pdu(0x04, 0x04, 0x00, 0x04, 0x00), \
+ raw_pdu(0x05, 0x01, 0x04, 0x00, 0x01, 0x29), \
+ raw_pdu(0x04, 0x07, 0x00, 0x07, 0x00), \
+ raw_pdu(0x05, 0x01, 0x07, 0x00, 0x03, 0x29)
+
static bt_uuid_t uuid_16 = {
.type = BT_UUID16,
.value.u16 = 0x1800
@@ -309,9 +335,22 @@ const bt_gatt_characteristic_t characteristic_4 = {
.num_descs = 1
};
+const bt_gatt_characteristic_t characteristic_5 = {
+ .start_handle = 0x0002,
+ .end_handle = 0x0004,
+ .value_handle = 0x0003,
+ .properties = 0x02,
+ .uuid = {0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e},
+ .descs = &descriptor_1,
+ .num_descs = 1
+};
+
const bt_gatt_characteristic_t *characteristics_3[] = {&characteristic_1,
&characteristic_3};
const bt_gatt_characteristic_t *characteristics_4[] = {&characteristic_4};
+const bt_gatt_characteristic_t *characteristics_5[] = {&characteristic_5,
+ &characteristic_3};
const struct gatt_service gatt_service_3 = {
.service = &service_3,
@@ -325,9 +364,17 @@ const struct gatt_service gatt_service_4 = {
.chars = characteristics_4
};
+const struct gatt_service gatt_service_5 = {
+ .service = &service_3,
+ .num_chars = sizeof(characteristics_5) / sizeof(characteristics_5[0]),
+ .chars = characteristics_5
+};
+
const struct gatt_service *service_data_2[] = {&gatt_service_3,
&gatt_service_4};
+const struct gatt_service *service_data_3[] = {&gatt_service_5};
+
static void test_debug(const char *str, void *user_data)
{
const char *prefix = user_data;
@@ -708,6 +755,17 @@ const struct test_step test_read_by_uuid_1 = {
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}
};
+const struct test_step test_read_by_uuid_2 = {
+ .handle = 0x0001,
+ .end_handle = 0xffff,
+ .func = test_read_by_uuid,
+ .expected_att_ecode = 0x0a,
+ .value = read_data_1,
+ .length = 0x03,
+ .uuid = {0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e}
+};
+
static void test_client(gconstpointer data)
{
struct context *context = create_context(512, data);
@@ -898,5 +956,19 @@ int main(int argc, char *argv[])
raw_pdu(0x08, 0x0b, 0x00, 0xff, 0xff, 0x0d, 0x2b),
raw_pdu(0x01, 0x08, 0x0b, 0x00, 0x0a));
+ define_test_client("/TP/GAR/CL/BV-03-C-2", test_client, service_data_3,
+ &test_read_by_uuid_2,
+ SERVICE_DATA_3_PDU,
+ raw_pdu(0x08, 0x01, 0x00, 0xff, 0xff, 0x0e,
+ 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08,
+ 0x07, 0x06, 0x05, 0x04, 0x03, 0x02,
+ 0x01, 0x00, 0x00),
+ raw_pdu(0x09, 0x05, 0x0a, 0x00, 0x01, 0x02, 0x03),
+ raw_pdu(0x08, 0x0b, 0x00, 0xff, 0xff, 0x0e,
+ 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08,
+ 0x07, 0x06, 0x05, 0x04, 0x03, 0x02,
+ 0x01, 0x00, 0x00),
+ raw_pdu(0x01, 0x08, 0x0b, 0x00, 0x0a));
+
return g_test_run();
}
--
1.9.3
Verify Generic Attribute Profile client behavior when
the Read Using Characteristic UUID procedure fails due to
attribute not found.
---
unit/test-gatt.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/unit/test-gatt.c b/unit/test-gatt.c
index 02c0205..8cfc4a0 100644
--- a/unit/test-gatt.c
+++ b/unit/test-gatt.c
@@ -775,6 +775,15 @@ const struct test_step test_read_by_uuid_3 = {
0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}
};
+const struct test_step test_read_by_uuid_4 = {
+ .handle = 0x0001,
+ .end_handle = 0xffff,
+ .func = test_read_by_uuid,
+ .expected_att_ecode = 0x0a,
+ .uuid = {0x00, 0x00, 0x2b, 0x0d, 0x00, 0x00, 0x10, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}
+};
+
static void test_client(gconstpointer data)
{
struct context *context = create_context(512, data);
@@ -985,5 +994,11 @@ int main(int argc, char *argv[])
raw_pdu(0x08, 0x01, 0x00, 0xff, 0xff, 0x0d, 0x2b),
raw_pdu(0x01, 0x08, 0x0b, 0x00, 0x02));
+ define_test_client("/TP/GAR/CL/BI-07-C", test_client, service_data_2,
+ &test_read_by_uuid_4,
+ SERVICE_DATA_2_PDU,
+ raw_pdu(0x08, 0x01, 0x00, 0xff, 0xff, 0x0d, 0x2b),
+ raw_pdu(0x01, 0x08, 0x0b, 0x00, 0x0a));
+
return g_test_run();
}
--
1.9.3
Verify that a Generic Attribute Profile client can read a
Characteristic Value selected by UUID using a 16-bit UUID.
---
unit/test-gatt.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 157 insertions(+)
diff --git a/unit/test-gatt.c b/unit/test-gatt.c
index 9917ec9..3e94bc1 100644
--- a/unit/test-gatt.c
+++ b/unit/test-gatt.c
@@ -144,6 +144,41 @@ struct context {
raw_pdu(0x04, 0x08, 0x00, 0x08, 0x00), \
raw_pdu(0x05, 0x01, 0x08, 0x00, 0x01, 0x29)
+#define SERVICE_DATA_2_PDU \
+ raw_pdu(0x02, 0x00, 0x02), \
+ raw_pdu(0x03, 0x00, 0x02), \
+ raw_pdu(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28), \
+ raw_pdu(0x11, 0x06, 0x01, 0x00, 0x07, 0x00, 0x01, 0x18),\
+ raw_pdu(0x10, 0x08, 0x00, 0xff, 0xff, 0x00, 0x28), \
+ raw_pdu(0x11, 0x06, 0x08, 0x00, 0x0b, 0x00, 0x0d, 0x18),\
+ raw_pdu(0x10, 0x0c, 0x00, 0xff, 0xff, 0x00, 0x28), \
+ raw_pdu(0x01, 0x10, 0x0c, 0x00, 0x0a), \
+ raw_pdu(0x10, 0x01, 0x00, 0xff, 0xff, 0x01, 0x28), \
+ raw_pdu(0x01, 0x10, 0x01, 0x00, 0x0a), \
+ raw_pdu(0x08, 0x01, 0x00, 0x07, 0x00, 0x02, 0x28), \
+ raw_pdu(0x01, 0x08, 0x01, 0x00, 0x0a), \
+ raw_pdu(0x08, 0x08, 0x00, 0x0b, 0x00, 0x02, 0x28), \
+ raw_pdu(0x01, 0x08, 0x0b, 0x00, 0x0a), \
+ raw_pdu(0x08, 0x01, 0x00, 0x07, 0x00, 0x03, 0x28), \
+ raw_pdu(0x09, 0x07, 0x02, 0x00, 0x02, 0x03, 0x00, 0x00, \
+ 0x2a), \
+ raw_pdu(0x08, 0x03, 0x00, 0x07, 0x00, 0x03, 0x28), \
+ raw_pdu(0x09, 0x07, 0x05, 0x00, 0x02, 0x06, 0x00, 0x00, \
+ 0x2a), \
+ raw_pdu(0x08, 0x06, 0x00, 0x07, 0x00, 0x03, 0x28), \
+ raw_pdu(0x01, 0x08, 0x06, 0x00, 0x0a), \
+ raw_pdu(0x04, 0x04, 0x00, 0x04, 0x00), \
+ raw_pdu(0x05, 0x01, 0x04, 0x00, 0x01, 0x29), \
+ raw_pdu(0x04, 0x07, 0x00, 0x07, 0x00), \
+ raw_pdu(0x05, 0x01, 0x07, 0x00, 0x03, 0x29), \
+ raw_pdu(0x08, 0x08, 0x00, 0x0b, 0x00, 0x03, 0x28), \
+ raw_pdu(0x09, 0x07, 0x09, 0x00, 0x02, 0x0a, 0x00, 0x0d, \
+ 0x2b), \
+ raw_pdu(0x08, 0x0a, 0x00, 0x0b, 0x00, 0x03, 0x28), \
+ raw_pdu(0x01, 0x08, 0x0a, 0x00, 0x0a), \
+ raw_pdu(0x04, 0x0b, 0x00, 0x0b, 0x00), \
+ raw_pdu(0x05, 0x01, 0x0b, 0x00, 0x02, 0x29)
+
static bt_uuid_t uuid_16 = {
.type = BT_UUID16,
.value.u16 = 0x1800
@@ -223,6 +258,76 @@ const struct gatt_service gatt_service_2 = {
const struct gatt_service *service_data_1[] = {&gatt_service_1,
&gatt_service_2};
+const bt_gatt_service_t service_3 = {
+ .primary = true,
+ .start_handle = 0x0001,
+ .end_handle = 0x0007,
+ .uuid = {0x00, 0x00, 0x18, 0x01, 0x00, 0x00, 0x10, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb}
+};
+
+const bt_gatt_service_t service_4 = {
+ .primary = true,
+ .start_handle = 0x0008,
+ .end_handle = 0x000b,
+ .uuid = {0x00, 0x00, 0x18, 0x0d, 0x00, 0x00, 0x10, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb}
+};
+
+const bt_gatt_descriptor_t descriptor_3 = {
+ .handle = 0x0007,
+ .uuid = {0x00, 0x00, 0x29, 0x03, 0x00, 0x00, 0x10, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb}
+};
+
+
+const bt_gatt_characteristic_t characteristic_3 = {
+ .start_handle = 0x0005,
+ .end_handle = 0x0007,
+ .value_handle = 0x0006,
+ .properties = 0x02,
+ .uuid = {0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb},
+ .descs = &descriptor_3,
+ .num_descs = 1
+};
+
+const bt_gatt_descriptor_t descriptor_4 = {
+ .handle = 0x000b,
+ .uuid = {0x00, 0x00, 0x29, 0x02, 0x00, 0x00, 0x10, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb}
+};
+
+const bt_gatt_characteristic_t characteristic_4 = {
+ .start_handle = 0x0009,
+ .end_handle = 0x000b,
+ .value_handle = 0x000a,
+ .properties = 0x02,
+ .uuid = {0x00, 0x00, 0x2b, 0x0d, 0x00, 0x00, 0x10, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb},
+ .descs = &descriptor_4,
+ .num_descs = 1
+};
+
+const bt_gatt_characteristic_t *characteristics_3[] = {&characteristic_1,
+ &characteristic_3};
+const bt_gatt_characteristic_t *characteristics_4[] = {&characteristic_4};
+
+const struct gatt_service gatt_service_3 = {
+ .service = &service_3,
+ .num_chars = sizeof(characteristics_3) / sizeof(characteristics_3[0]),
+ .chars = characteristics_3
+};
+
+const struct gatt_service gatt_service_4 = {
+ .service = &service_4,
+ .num_chars = sizeof(characteristics_4) / sizeof(characteristics_4[0]),
+ .chars = characteristics_4
+};
+
+const struct gatt_service *service_data_2[] = {&gatt_service_3,
+ &gatt_service_4};
+
static void test_debug(const char *str, void *user_data)
{
const char *prefix = user_data;
@@ -559,6 +664,50 @@ const struct test_step test_read_4 = {
.expected_att_ecode = 0x08,
};
+static void read_by_uuid_cb(bool success, uint8_t att_ecode,
+ struct read_by_uuid_res *result,
+ void *user_data)
+{
+ struct context *context = user_data;
+ const struct test_step *step = context->data->step;
+
+ g_assert(att_ecode == step->expected_att_ecode);
+
+ if (success && step->value) {
+ uint16_t handle;
+ uint8_t length;
+ uint8_t *value;
+
+ read_by_uuid_res_get(result, &handle, &length, &value);
+ g_assert(length == step->length);
+ g_assert(memcmp(value, step->value, length) == 0);
+ }
+
+ g_assert(read_by_uuid_res_next(result) == NULL);
+
+ context_quit(context);
+}
+
+static void test_read_by_uuid(struct context *context)
+{
+ const struct test_step *step = context->data->step;
+
+ g_assert(bt_gatt_client_read_by_uuid(context->client, step->handle,
+ step->end_handle, step->uuid, read_by_uuid_cb, context,
+ NULL));
+}
+
+const struct test_step test_read_by_uuid_1 = {
+ .handle = 0x0001,
+ .end_handle = 0xffff,
+ .func = test_read_by_uuid,
+ .expected_att_ecode = 0x0a,
+ .value = read_data_1,
+ .length = 0x03,
+ .uuid = {0x00, 0x00, 0x2b, 0x0d, 0x00, 0x00, 0x10, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}
+};
+
static void test_client(gconstpointer data)
{
struct context *context = create_context(512, data);
@@ -741,5 +890,13 @@ int main(int argc, char *argv[])
raw_pdu(0x0a, 0x03, 0x00),
raw_pdu(0x01, 0x0a, 0x03, 0x00, 0x08));
+ define_test_client("/TP/GAR/CL/BV-03-C-1", test_client, service_data_2,
+ &test_read_by_uuid_1,
+ SERVICE_DATA_2_PDU,
+ raw_pdu(0x08, 0x01, 0x00, 0xff, 0xff, 0x0d, 0x2b),
+ raw_pdu(0x09, 0x05, 0x0a, 0x00, 0x01, 0x02, 0x03),
+ raw_pdu(0x08, 0x0b, 0x00, 0xff, 0xff, 0x0d, 0x2b),
+ raw_pdu(0x01, 0x08, 0x0b, 0x00, 0x0a));
+
return g_test_run();
}
--
1.9.3
Verify Generic Attribute Profile client behavior when the Read
Using Characteristic UUID procedure fails due to read not permitted.
---
unit/test-gatt.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/unit/test-gatt.c b/unit/test-gatt.c
index 4f88ce5..02c0205 100644
--- a/unit/test-gatt.c
+++ b/unit/test-gatt.c
@@ -766,6 +766,15 @@ const struct test_step test_read_by_uuid_2 = {
0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e}
};
+const struct test_step test_read_by_uuid_3 = {
+ .handle = 0x0001,
+ .end_handle = 0xffff,
+ .func = test_read_by_uuid,
+ .expected_att_ecode = 0x02,
+ .uuid = {0x00, 0x00, 0x2b, 0x0d, 0x00, 0x00, 0x10, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}
+};
+
static void test_client(gconstpointer data)
{
struct context *context = create_context(512, data);
@@ -970,5 +979,11 @@ int main(int argc, char *argv[])
0x01, 0x00, 0x00),
raw_pdu(0x01, 0x08, 0x0b, 0x00, 0x0a));
+ define_test_client("/TP/GAR/CL/BI-06-C", test_client, service_data_2,
+ &test_read_by_uuid_3,
+ SERVICE_DATA_2_PDU,
+ raw_pdu(0x08, 0x01, 0x00, 0xff, 0xff, 0x0d, 0x2b),
+ raw_pdu(0x01, 0x08, 0x0b, 0x00, 0x02));
+
return g_test_run();
}
--
1.9.3
Implement reading characteristics by UUID in btgatt-client tool.
---
tools/btgatt-client.c | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 84 insertions(+)
diff --git a/tools/btgatt-client.c b/tools/btgatt-client.c
index 7a1204f..f069189 100644
--- a/tools/btgatt-client.c
+++ b/tools/btgatt-client.c
@@ -401,6 +401,89 @@ static void cmd_services(struct client *cli, char *cmd_str)
services_usage();
}
+static void read_by_uuid_usage(void)
+{
+ printf("Usage: read-by-uuid <start_handle> <end_handle> <uuid>\n");
+}
+
+static void read_by_uuid_cb(bool success, uint8_t att_ecode,
+ struct read_by_uuid_res *result, void *user_data)
+{
+ uint16_t handle;
+ uint8_t length;
+ uint8_t *data;
+ int i;
+
+ if (!success) {
+ PRLOG("\nRead by UUID request failed: 0x%02x\n", att_ecode);
+ return;
+ }
+
+ if (!result) {
+ PRLOG("\nNo attribute found\n");
+ return;
+ }
+
+ for (; result; result = read_by_uuid_res_next(result)) {
+ read_by_uuid_res_get(result, &handle, &length, &data);
+ printf("\nHandle 0x%04X Read value", handle);
+
+ if (length == 0) {
+ PRLOG(": 0 bytes\n");
+ return;
+ }
+
+ printf(" (%u bytes): ", length);
+
+ for (i = 0; i < length; i++)
+ printf("%02x ", data[i]);
+
+ PRLOG("\n");
+ }
+}
+
+static void cmd_read_by_uuid(struct client *cli, char *cmd_str)
+{
+ char *argv[3];
+ int argc = 0;
+ uint16_t start_handle, end_handle;
+ bt_uuid_t tmp, uuid;
+ char *endptr = NULL;
+
+ if (!bt_gatt_client_is_ready(cli->gatt)) {
+ printf("GATT client not initialized\n");
+ return;
+ }
+
+ if (!parse_args(cmd_str, 3, argv, &argc) || argc != 3) {
+ read_by_uuid_usage();
+ return;
+ }
+
+ start_handle = strtol(argv[0], &endptr, 0);
+ if (!endptr || *endptr != '\0' || !start_handle) {
+ printf("Invalid value handle: %s\n", argv[0]);
+ return;
+ }
+
+ end_handle = strtol(argv[1], &endptr, 0);
+ if (!endptr || *endptr != '\0' || !end_handle) {
+ printf("Invalid value handle: %s\n", argv[1]);
+ return;
+ }
+
+ if (bt_string_to_uuid(&tmp, argv[2]) < 0) {
+ printf("Invalid UUID: %s\n", argv[2]);
+ return;
+ }
+
+ bt_uuid_to_uuid128(&tmp, &uuid);
+
+ if (!bt_gatt_client_read_by_uuid(cli->gatt, start_handle, end_handle,
+ uuid.value.u128.data, read_by_uuid_cb, NULL, NULL))
+ printf("Failed to initiate read value procedure\n");
+}
+
static void read_value_usage(void)
{
printf("Usage: read-value <value_handle>\n");
@@ -873,6 +956,7 @@ static struct {
"\tRead a characteristic or descriptor value" },
{ "read-long-value", cmd_read_long_value,
"\tRead a long characteristic or desctriptor value" },
+ { "read-by-uuid", cmd_read_by_uuid, "\tRead by UUID" },
{ "write-value", cmd_write_value,
"\tWrite a characteristic or descriptor value" },
{ "write-long-value", cmd_write_long_value,
--
1.9.3