| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2014 Google Inc. |
| * |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include "src/shared/queue.h" |
| #include "src/shared/att.h" |
| #include "lib/bluetooth.h" |
| #include "lib/uuid.h" |
| #include "src/shared/gatt-helpers.h" |
| #include "src/shared/util.h" |
| |
| #ifndef MIN |
| #define MIN(a, b) ((a) < (b) ? (a) : (b)) |
| #endif |
| |
| struct bt_gatt_result { |
| uint8_t opcode; |
| void *pdu; |
| uint16_t pdu_len; |
| uint16_t data_len; |
| |
| void *op; /* Discovery operation data */ |
| |
| struct bt_gatt_result *next; |
| }; |
| |
| static struct bt_gatt_result *result_create(uint8_t opcode, const void *pdu, |
| uint16_t pdu_len, |
| uint16_t data_len, |
| void *op) |
| { |
| struct bt_gatt_result *result; |
| |
| result = new0(struct bt_gatt_result, 1); |
| result->pdu = malloc(pdu_len); |
| if (!result->pdu) { |
| free(result); |
| return NULL; |
| } |
| |
| result->opcode = opcode; |
| result->pdu_len = pdu_len; |
| result->data_len = data_len; |
| result->op = op; |
| |
| memcpy(result->pdu, pdu, pdu_len); |
| |
| return result; |
| } |
| |
| static void result_destroy(struct bt_gatt_result *result) |
| { |
| struct bt_gatt_result *next; |
| |
| while (result) { |
| next = result->next; |
| |
| free(result->pdu); |
| free(result); |
| |
| result = next; |
| } |
| } |
| |
| static unsigned int result_element_count(struct bt_gatt_result *result) |
| { |
| unsigned int count = 0; |
| struct bt_gatt_result *cur; |
| |
| cur = result; |
| |
| while (cur) { |
| count += cur->pdu_len / cur->data_len; |
| cur = cur->next; |
| } |
| |
| return count; |
| } |
| |
| unsigned int bt_gatt_result_service_count(struct bt_gatt_result *result) |
| { |
| if (!result) |
| return 0; |
| |
| if (result->opcode != BT_ATT_OP_READ_BY_GRP_TYPE_RSP && |
| result->opcode != BT_ATT_OP_FIND_BY_TYPE_RSP) |
| return 0; |
| |
| return result_element_count(result); |
| } |
| |
| unsigned int bt_gatt_result_characteristic_count(struct bt_gatt_result *result) |
| { |
| if (!result) |
| return 0; |
| |
| if (result->opcode != BT_ATT_OP_READ_BY_TYPE_RSP) |
| return 0; |
| |
| /* |
| * Data length contains 7 or 21 octets: |
| * 2 octets: Attribute handle |
| * 1 octet: Characteristic properties |
| * 2 octets: Characteristic value handle |
| * 2 or 16 octets: characteristic UUID |
| */ |
| if (result->data_len != 21 && result->data_len != 7) |
| return 0; |
| |
| return result_element_count(result); |
| } |
| |
| unsigned int bt_gatt_result_descriptor_count(struct bt_gatt_result *result) |
| { |
| if (!result) |
| return 0; |
| |
| if (result->opcode != BT_ATT_OP_FIND_INFO_RSP) |
| return 0; |
| |
| return result_element_count(result); |
| } |
| |
| unsigned int bt_gatt_result_included_count(struct bt_gatt_result *result) |
| { |
| struct bt_gatt_result *cur; |
| unsigned int count = 0; |
| |
| if (!result) |
| return 0; |
| |
| if (result->opcode != BT_ATT_OP_READ_BY_TYPE_RSP) |
| return 0; |
| |
| /* |
| * Data length can be of length 6 or 8 octets: |
| * 2 octets - include service handle |
| * 2 octets - start handle of included service |
| * 2 octets - end handle of included service |
| * 2 octets (optionally) - 16 bit Bluetooth UUID |
| */ |
| if (result->data_len != 6 && result->data_len != 8) |
| return 0; |
| |
| for (cur = result; cur; cur = cur->next) |
| if (cur->opcode == BT_ATT_OP_READ_BY_TYPE_RSP) |
| count += cur->pdu_len / cur->data_len; |
| |
| return count; |
| } |
| |
| bool bt_gatt_iter_init(struct bt_gatt_iter *iter, struct bt_gatt_result *result) |
| { |
| if (!iter || !result) |
| return false; |
| |
| iter->result = result; |
| iter->pos = 0; |
| |
| return true; |
| } |
| |
| static const uint8_t bt_base_uuid[16] = { |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, |
| 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB |
| }; |
| |
| static bool convert_uuid_le(const uint8_t *src, size_t len, uint8_t dst[16]) |
| { |
| if (len == 16) { |
| bswap_128(src, dst); |
| return true; |
| } |
| |
| if (len != 2) |
| return false; |
| |
| memcpy(dst, bt_base_uuid, sizeof(bt_base_uuid)); |
| dst[2] = src[1]; |
| dst[3] = src[0]; |
| |
| return true; |
| } |
| |
| struct bt_gatt_request { |
| struct bt_att *att; |
| unsigned int id; |
| uint16_t start_handle; |
| uint16_t end_handle; |
| int ref_count; |
| bt_uuid_t uuid; |
| uint16_t service_type; |
| struct bt_gatt_result *result_head; |
| struct bt_gatt_result *result_tail; |
| bt_gatt_request_callback_t callback; |
| void *user_data; |
| bt_gatt_destroy_func_t destroy; |
| }; |
| |
| static struct bt_gatt_result *result_append(uint8_t opcode, const void *pdu, |
| uint16_t pdu_len, |
| uint16_t data_len, |
| struct bt_gatt_request *op) |
| { |
| struct bt_gatt_result *result; |
| |
| result = result_create(opcode, pdu, pdu_len, data_len, op); |
| if (!result) |
| return NULL; |
| |
| if (!op->result_head) |
| op->result_head = op->result_tail = result; |
| else { |
| op->result_tail->next = result; |
| op->result_tail = result; |
| } |
| |
| return result; |
| } |
| |
| bool bt_gatt_iter_next_included_service(struct bt_gatt_iter *iter, |
| uint16_t *handle, uint16_t *start_handle, |
| uint16_t *end_handle, uint8_t uuid[16]) |
| { |
| struct bt_gatt_result *read_result; |
| struct bt_gatt_request *op; |
| const void *pdu_ptr; |
| int i = 0; |
| |
| if (!iter || !iter->result || !handle || !start_handle || !end_handle |
| || !uuid) |
| return false; |
| |
| |
| if (iter->result->opcode != BT_ATT_OP_READ_BY_TYPE_RSP) |
| return false; |
| |
| /* UUID in discovery_op is set in read_by_type and service_discovery */ |
| op = iter->result->op; |
| if (op->uuid.type != BT_UUID_UNSPEC) |
| return false; |
| /* |
| * iter->result points to READ_BY_TYPE_RSP with data length containing: |
| * 2 octets - include service handle |
| * 2 octets - start handle of included service |
| * 2 octets - end handle of included service |
| * optional 2 octets - Bluetooth UUID |
| */ |
| if (iter->result->data_len != 8 && iter->result->data_len != 6) |
| return false; |
| |
| pdu_ptr = iter->result->pdu + iter->pos; |
| |
| /* This result contains 16 bit UUID */ |
| if (iter->result->data_len == 8) { |
| *handle = get_le16(pdu_ptr); |
| *start_handle = get_le16(pdu_ptr + 2); |
| *end_handle = get_le16(pdu_ptr + 4); |
| convert_uuid_le(pdu_ptr + 6, 2, uuid); |
| |
| iter->pos += iter->result->data_len; |
| |
| if (iter->pos == iter->result->pdu_len) { |
| iter->result = iter->result->next; |
| iter->pos = 0; |
| } |
| |
| return true; |
| } |
| |
| *handle = get_le16(pdu_ptr); |
| *start_handle = get_le16(pdu_ptr + 2); |
| *end_handle = get_le16(pdu_ptr + 4); |
| read_result = iter->result; |
| |
| /* |
| * Find READ_RSP with include service UUID. |
| * If number of current data set in READ_BY_TYPE_RSP is n, then we must |
| * go to n'th PDU next to current item->result |
| */ |
| for (read_result = read_result->next; read_result; i++) { |
| if (i >= (iter->pos / iter->result->data_len)) |
| break; |
| |
| read_result = read_result->next; |
| } |
| |
| if (!read_result) |
| return false; |
| |
| convert_uuid_le(read_result->pdu, read_result->data_len, uuid); |
| iter->pos += iter->result->data_len; |
| if (iter->pos == iter->result->pdu_len) { |
| iter->result = read_result->next; |
| iter->pos = 0; |
| } |
| |
| return true; |
| } |
| |
| bool bt_gatt_iter_next_service(struct bt_gatt_iter *iter, |
| uint16_t *start_handle, uint16_t *end_handle, |
| uint8_t uuid[16]) |
| { |
| struct bt_gatt_request *op; |
| const void *pdu_ptr; |
| bt_uuid_t tmp; |
| |
| if (!iter || !iter->result || !start_handle || !end_handle || !uuid) |
| return false; |
| |
| op = iter->result->op; |
| pdu_ptr = iter->result->pdu + iter->pos; |
| |
| switch (iter->result->opcode) { |
| case BT_ATT_OP_READ_BY_GRP_TYPE_RSP: |
| *start_handle = get_le16(pdu_ptr); |
| *end_handle = get_le16(pdu_ptr + 2); |
| convert_uuid_le(pdu_ptr + 4, iter->result->data_len - 4, uuid); |
| break; |
| case BT_ATT_OP_FIND_BY_TYPE_RSP: |
| *start_handle = get_le16(pdu_ptr); |
| *end_handle = get_le16(pdu_ptr + 2); |
| |
| bt_uuid_to_uuid128(&op->uuid, &tmp); |
| memcpy(uuid, tmp.value.u128.data, 16); |
| break; |
| default: |
| return false; |
| } |
| |
| |
| iter->pos += iter->result->data_len; |
| if (iter->pos == iter->result->pdu_len) { |
| iter->result = iter->result->next; |
| iter->pos = 0; |
| } |
| |
| return true; |
| } |
| |
| bool bt_gatt_iter_next_characteristic(struct bt_gatt_iter *iter, |
| uint16_t *start_handle, uint16_t *end_handle, |
| uint16_t *value_handle, uint8_t *properties, |
| uint8_t uuid[16]) |
| { |
| struct bt_gatt_request *op; |
| const void *pdu_ptr; |
| |
| if (!iter || !iter->result || !start_handle || !end_handle || |
| !value_handle || !properties || !uuid) |
| return false; |
| |
| if (iter->result->opcode != BT_ATT_OP_READ_BY_TYPE_RSP) |
| return false; |
| |
| /* UUID in discovery_op is set in read_by_type and service_discovery */ |
| op = iter->result->op; |
| if (op->uuid.type != BT_UUID_UNSPEC) |
| return false; |
| /* |
| * Data length contains 7 or 21 octets: |
| * 2 octets: Attribute handle |
| * 1 octet: Characteristic properties |
| * 2 octets: Characteristic value handle |
| * 2 or 16 octets: characteristic UUID |
| */ |
| if (iter->result->data_len != 21 && iter->result->data_len != 7) |
| return false; |
| |
| pdu_ptr = iter->result->pdu + iter->pos; |
| |
| *start_handle = get_le16(pdu_ptr); |
| *properties = ((uint8_t *) pdu_ptr)[2]; |
| *value_handle = get_le16(pdu_ptr + 3); |
| convert_uuid_le(pdu_ptr + 5, iter->result->data_len - 5, uuid); |
| |
| iter->pos += iter->result->data_len; |
| if (iter->pos == iter->result->pdu_len) { |
| iter->result = iter->result->next; |
| iter->pos = 0; |
| } |
| |
| if (!iter->result) { |
| *end_handle = op->end_handle; |
| return true; |
| } |
| |
| *end_handle = get_le16(iter->result->pdu + iter->pos) - 1; |
| |
| return true; |
| } |
| |
| bool bt_gatt_iter_next_descriptor(struct bt_gatt_iter *iter, uint16_t *handle, |
| uint8_t uuid[16]) |
| { |
| const void *pdu_ptr; |
| |
| if (!iter || !iter->result || !handle || !uuid) |
| return false; |
| |
| if (iter->result->opcode != BT_ATT_OP_FIND_INFO_RSP) |
| return false; |
| |
| pdu_ptr = iter->result->pdu + iter->pos; |
| |
| *handle = get_le16(pdu_ptr); |
| convert_uuid_le(pdu_ptr + 2, iter->result->data_len - 2, uuid); |
| |
| iter->pos += iter->result->data_len; |
| if (iter->pos == iter->result->pdu_len) { |
| iter->result = iter->result->next; |
| iter->pos = 0; |
| } |
| |
| return true; |
| } |
| |
| bool bt_gatt_iter_next_read_by_type(struct bt_gatt_iter *iter, |
| uint16_t *handle, uint16_t *length, |
| const uint8_t **value) |
| { |
| struct bt_gatt_request *op; |
| const void *pdu_ptr; |
| |
| if (!iter || !iter->result || !handle || !length || !value) |
| return false; |
| |
| if (iter->result->opcode != BT_ATT_OP_READ_BY_TYPE_RSP) |
| return false; |
| |
| /* |
| * Check if UUID is set, otherwise results can contain characteristic |
| * discovery service or included service discovery results |
| */ |
| op = iter->result->op; |
| if (op->uuid.type == BT_UUID_UNSPEC) |
| return false; |
| |
| pdu_ptr = iter->result->pdu + iter->pos; |
| |
| *handle = get_le16(pdu_ptr); |
| *length = iter->result->data_len - 2; |
| *value = pdu_ptr + 2; |
| |
| iter->pos += iter->result->data_len; |
| if (iter->pos == iter->result->pdu_len) { |
| iter->result = iter->result->next; |
| iter->pos = 0; |
| } |
| |
| return true; |
| } |
| |
| struct mtu_op { |
| struct bt_att *att; |
| uint16_t client_rx_mtu; |
| bt_gatt_result_callback_t callback; |
| void *user_data; |
| bt_gatt_destroy_func_t destroy; |
| }; |
| |
| static void destroy_mtu_op(void *user_data) |
| { |
| struct mtu_op *op = user_data; |
| |
| if (op->destroy) |
| op->destroy(op->user_data); |
| |
| free(op); |
| } |
| |
| static uint8_t process_error(const void *pdu, uint16_t length) |
| { |
| const struct bt_att_pdu_error_rsp *error_pdu; |
| |
| if (!pdu || length != sizeof(struct bt_att_pdu_error_rsp)) |
| return 0; |
| |
| error_pdu = pdu; |
| |
| return error_pdu->ecode; |
| } |
| |
| static void mtu_cb(uint8_t opcode, const void *pdu, uint16_t length, |
| void *user_data) |
| { |
| struct mtu_op *op = user_data; |
| bool success = true; |
| uint8_t att_ecode = 0; |
| uint16_t server_rx_mtu; |
| |
| if (opcode == BT_ATT_OP_ERROR_RSP) { |
| success = false; |
| att_ecode = process_error(pdu, length); |
| goto done; |
| } |
| |
| if (opcode != BT_ATT_OP_MTU_RSP || !pdu || length != 2) { |
| success = false; |
| goto done; |
| } |
| |
| server_rx_mtu = get_le16(pdu); |
| bt_att_set_mtu(op->att, MIN(op->client_rx_mtu, server_rx_mtu)); |
| |
| done: |
| if (op->callback) |
| op->callback(success, att_ecode, op->user_data); |
| } |
| |
| unsigned int bt_gatt_exchange_mtu(struct bt_att *att, uint16_t client_rx_mtu, |
| bt_gatt_result_callback_t callback, |
| void *user_data, |
| bt_gatt_destroy_func_t destroy) |
| { |
| struct mtu_op *op; |
| uint8_t pdu[2]; |
| unsigned int id; |
| |
| if (!att || !client_rx_mtu) |
| return false; |
| |
| op = new0(struct mtu_op, 1); |
| op->att = att; |
| op->client_rx_mtu = client_rx_mtu; |
| op->callback = callback; |
| op->user_data = user_data; |
| op->destroy = destroy; |
| |
| put_le16(client_rx_mtu, pdu); |
| |
| id = bt_att_send(att, BT_ATT_OP_MTU_REQ, pdu, sizeof(pdu), mtu_cb, op, |
| destroy_mtu_op); |
| if (!id) |
| free(op); |
| |
| return id; |
| } |
| |
| static inline int get_uuid_len(const bt_uuid_t *uuid) |
| { |
| if (!uuid) |
| return 0; |
| |
| return (uuid->type == BT_UUID16) ? 2 : 16; |
| } |
| |
| struct bt_gatt_request *bt_gatt_request_ref(struct bt_gatt_request *req) |
| { |
| if (!req) |
| return NULL; |
| |
| __sync_fetch_and_add(&req->ref_count, 1); |
| |
| return req; |
| } |
| |
| void bt_gatt_request_unref(struct bt_gatt_request *req) |
| { |
| if (!req) |
| return; |
| |
| if (__sync_sub_and_fetch(&req->ref_count, 1)) |
| return; |
| |
| bt_gatt_request_cancel(req); |
| |
| if (req->destroy) |
| req->destroy(req->user_data); |
| |
| result_destroy(req->result_head); |
| |
| free(req); |
| } |
| |
| void bt_gatt_request_cancel(struct bt_gatt_request *req) |
| { |
| if (!req) |
| return; |
| |
| if (!req->id) |
| return; |
| |
| bt_att_cancel(req->att, req->id); |
| req->id = 0; |
| } |
| |
| static void async_req_unref(void *data) |
| { |
| struct bt_gatt_request *req = data; |
| |
| bt_gatt_request_unref(req); |
| } |
| |
| static void discovery_op_complete(struct bt_gatt_request *op, bool success, |
| uint8_t ecode) |
| { |
| /* Reset success if there is some result to report */ |
| if (ecode == BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND && op->result_head) |
| success = true; |
| |
| if (op->callback) |
| op->callback(success, ecode, success ? op->result_head : NULL, |
| op->user_data); |
| |
| if (!op->id) |
| async_req_unref(op); |
| else |
| op->id = 0; |
| |
| } |
| |
| static void read_by_grp_type_cb(uint8_t opcode, const void *pdu, |
| uint16_t length, void *user_data) |
| { |
| struct bt_gatt_request *op = user_data; |
| bool success; |
| uint8_t att_ecode = 0; |
| struct bt_gatt_result *cur_result; |
| size_t data_length; |
| size_t list_length; |
| uint16_t last_end; |
| |
| if (opcode == BT_ATT_OP_ERROR_RSP) { |
| success = false; |
| att_ecode = process_error(pdu, length); |
| goto done; |
| } |
| |
| /* PDU must contain at least the following (sans opcode): |
| * - Attr Data Length (1 octet) |
| * - Attr Data List (at least 6 octets): |
| * -- 2 octets: Attribute handle |
| * -- 2 octets: End group handle |
| * -- 2 or 16 octets: service UUID |
| */ |
| if (opcode != BT_ATT_OP_READ_BY_GRP_TYPE_RSP || !pdu || length < 7) { |
| success = false; |
| goto done; |
| } |
| |
| data_length = ((uint8_t *) pdu)[0]; |
| list_length = length - 1; |
| |
| if ((data_length != 6 && data_length != 20) || |
| (list_length % data_length)) { |
| success = false; |
| goto done; |
| } |
| |
| /* PDU is correctly formatted. Get the last end handle to process the |
| * next request and store the PDU. |
| */ |
| cur_result = result_append(opcode, pdu + 1, list_length, data_length, |
| op); |
| if (!cur_result) { |
| success = false; |
| goto done; |
| } |
| |
| last_end = get_le16(pdu + length - data_length + 2); |
| |
| /* |
| * If last handle is lower from previous start handle then it is smth |
| * wrong. Let's stop search, otherwise we might enter infinite loop. |
| */ |
| if (last_end < op->start_handle) { |
| success = false; |
| goto done; |
| } |
| |
| op->start_handle = last_end + 1; |
| |
| if (last_end < op->end_handle) { |
| uint8_t pdu[6]; |
| |
| put_le16(op->start_handle, pdu); |
| put_le16(op->end_handle, pdu + 2); |
| put_le16(op->service_type, pdu + 4); |
| |
| op->id = bt_att_send(op->att, BT_ATT_OP_READ_BY_GRP_TYPE_REQ, |
| pdu, sizeof(pdu), |
| read_by_grp_type_cb, |
| bt_gatt_request_ref(op), |
| async_req_unref); |
| if (op->id) |
| return; |
| |
| success = false; |
| goto done; |
| } |
| |
| /* Some devices incorrectly return 0xffff as the end group handle when |
| * the read-by-group-type request is performed within a smaller range. |
| * Manually set the end group handle that we report in the result to the |
| * end handle in the original request. |
| */ |
| if (last_end == 0xffff && last_end != op->end_handle) |
| put_le16(op->end_handle, |
| cur_result->pdu + length - data_length + 1); |
| |
| success = true; |
| |
| done: |
| discovery_op_complete(op, success, att_ecode); |
| } |
| |
| static void find_by_type_val_cb(uint8_t opcode, const void *pdu, |
| uint16_t length, void *user_data) |
| { |
| struct bt_gatt_request *op = user_data; |
| bool success; |
| uint8_t att_ecode = 0; |
| uint16_t last_end; |
| |
| if (opcode == BT_ATT_OP_ERROR_RSP) { |
| success = false; |
| att_ecode = process_error(pdu, length); |
| goto done; |
| } |
| |
| /* PDU must contain 4 bytes and it must be a multiple of 4, where each |
| * 4 bytes contain the 16-bit attribute and group end handles. |
| */ |
| if (opcode != BT_ATT_OP_FIND_BY_TYPE_RSP || !pdu || !length || |
| length % 4) { |
| success = false; |
| goto done; |
| } |
| |
| if (!result_append(opcode, pdu, length, 4, op)) { |
| success = false; |
| goto done; |
| } |
| |
| /* |
| * Each data set contains: |
| * 2 octets with start handle |
| * 2 octets with end handle |
| * last_end is end handle of last data set |
| */ |
| last_end = get_le16(pdu + length - 2); |
| |
| /* |
| * If last handle is lower from previous start handle then it is smth |
| * wrong. Let's stop search, otherwise we might enter infinite loop. |
| */ |
| if (last_end < op->start_handle) { |
| success = false; |
| goto done; |
| } |
| |
| op->start_handle = last_end + 1; |
| |
| if (last_end < op->end_handle) { |
| uint8_t pdu[6 + get_uuid_len(&op->uuid)]; |
| |
| put_le16(op->start_handle, pdu); |
| put_le16(op->end_handle, pdu + 2); |
| put_le16(op->service_type, pdu + 4); |
| bt_uuid_to_le(&op->uuid, pdu + 6); |
| |
| op->id = bt_att_send(op->att, BT_ATT_OP_FIND_BY_TYPE_REQ, |
| pdu, sizeof(pdu), |
| find_by_type_val_cb, |
| bt_gatt_request_ref(op), |
| async_req_unref); |
| if (op->id) |
| return; |
| |
| success = false; |
| goto done; |
| } |
| |
| success = false; |
| |
| done: |
| discovery_op_complete(op, success, att_ecode); |
| } |
| |
| static struct bt_gatt_request *discover_services(struct bt_att *att, |
| bt_uuid_t *uuid, |
| uint16_t start, uint16_t end, |
| bt_gatt_request_callback_t callback, |
| void *user_data, |
| bt_gatt_destroy_func_t destroy, |
| bool primary) |
| { |
| struct bt_gatt_request *op; |
| |
| if (!att) |
| return NULL; |
| |
| op = new0(struct bt_gatt_request, 1); |
| op->att = att; |
| op->start_handle = start; |
| op->end_handle = end; |
| op->callback = callback; |
| op->user_data = user_data; |
| op->destroy = destroy; |
| /* set service uuid to primary or secondary */ |
| op->service_type = primary ? GATT_PRIM_SVC_UUID : GATT_SND_SVC_UUID; |
| |
| /* If UUID is NULL, then discover all primary services */ |
| if (!uuid) { |
| uint8_t pdu[6]; |
| |
| put_le16(start, pdu); |
| put_le16(end, pdu + 2); |
| put_le16(op->service_type, pdu + 4); |
| |
| op->id = bt_att_send(att, BT_ATT_OP_READ_BY_GRP_TYPE_REQ, |
| pdu, sizeof(pdu), |
| read_by_grp_type_cb, |
| bt_gatt_request_ref(op), |
| async_req_unref); |
| } else { |
| uint8_t pdu[6 + get_uuid_len(uuid)]; |
| |
| if (uuid->type == BT_UUID_UNSPEC) { |
| free(op); |
| return NULL; |
| } |
| |
| /* Discover by UUID */ |
| op->uuid = *uuid; |
| |
| put_le16(start, pdu); |
| put_le16(end, pdu + 2); |
| put_le16(op->service_type, pdu + 4); |
| bt_uuid_to_le(&op->uuid, pdu + 6); |
| |
| op->id = bt_att_send(att, BT_ATT_OP_FIND_BY_TYPE_REQ, |
| pdu, sizeof(pdu), |
| find_by_type_val_cb, |
| bt_gatt_request_ref(op), |
| async_req_unref); |
| } |
| |
| if (!op->id) { |
| free(op); |
| return NULL; |
| } |
| |
| return bt_gatt_request_ref(op); |
| } |
| |
| struct bt_gatt_request *bt_gatt_discover_all_primary_services( |
| struct bt_att *att, bt_uuid_t *uuid, |
| bt_gatt_request_callback_t callback, |
| void *user_data, |
| bt_gatt_destroy_func_t destroy) |
| { |
| return bt_gatt_discover_primary_services(att, uuid, 0x0001, 0xffff, |
| callback, user_data, |
| destroy); |
| } |
| |
| struct bt_gatt_request *bt_gatt_discover_primary_services( |
| struct bt_att *att, bt_uuid_t *uuid, |
| uint16_t start, uint16_t end, |
| bt_gatt_request_callback_t callback, |
| void *user_data, |
| bt_gatt_destroy_func_t destroy) |
| { |
| return discover_services(att, uuid, start, end, callback, user_data, |
| destroy, true); |
| } |
| |
| struct bt_gatt_request *bt_gatt_discover_secondary_services( |
| struct bt_att *att, bt_uuid_t *uuid, |
| uint16_t start, uint16_t end, |
| bt_gatt_request_callback_t callback, |
| void *user_data, |
| bt_gatt_destroy_func_t destroy) |
| { |
| return discover_services(att, uuid, start, end, callback, user_data, |
| destroy, false); |
| } |
| |
| struct read_incl_data { |
| struct bt_gatt_request *op; |
| struct bt_gatt_result *result; |
| int pos; |
| int ref_count; |
| }; |
| |
| static struct read_incl_data *new_read_included(struct bt_gatt_result *res) |
| { |
| struct read_incl_data *data; |
| |
| data = new0(struct read_incl_data, 1); |
| data->op = bt_gatt_request_ref(res->op); |
| data->result = res; |
| |
| return data; |
| }; |
| |
| static struct read_incl_data *read_included_ref(struct read_incl_data *data) |
| { |
| __sync_fetch_and_add(&data->ref_count, 1); |
| |
| return data; |
| } |
| |
| static void read_included_unref(void *data) |
| { |
| struct read_incl_data *read_data = data; |
| |
| if (__sync_sub_and_fetch(&read_data->ref_count, 1)) |
| return; |
| |
| async_req_unref(read_data->op); |
| |
| free(read_data); |
| } |
| |
| static void discover_included_cb(uint8_t opcode, const void *pdu, |
| uint16_t length, void *user_data); |
| |
| static void read_included_cb(uint8_t opcode, const void *pdu, |
| uint16_t length, void *user_data) |
| { |
| struct read_incl_data *data = user_data; |
| struct bt_gatt_request *op = data->op; |
| uint8_t att_ecode = 0; |
| uint8_t read_pdu[2]; |
| bool success; |
| |
| if (opcode == BT_ATT_OP_ERROR_RSP) { |
| success = false; |
| att_ecode = process_error(pdu, length); |
| goto done; |
| } |
| |
| if (opcode != BT_ATT_OP_READ_RSP || (!pdu && length)) { |
| success = false; |
| goto done; |
| } |
| |
| /* |
| * UUID should be in 128 bit format, as it couldn't be read in |
| * READ_BY_TYPE request |
| */ |
| if (length != 16) { |
| success = false; |
| goto done; |
| } |
| |
| if (!result_append(opcode, pdu, length, length, op)) { |
| success = false; |
| goto done; |
| } |
| |
| if (data->pos == data->result->pdu_len) { |
| uint16_t last_handle; |
| uint8_t pdu[6]; |
| |
| last_handle = get_le16(data->result->pdu + data->pos - |
| data->result->data_len); |
| if (last_handle == op->end_handle) { |
| success = true; |
| goto done; |
| } |
| |
| put_le16(last_handle + 1, pdu); |
| put_le16(op->end_handle, pdu + 2); |
| put_le16(GATT_INCLUDE_UUID, pdu + 4); |
| |
| op->id = bt_att_send(op->att, BT_ATT_OP_READ_BY_TYPE_REQ, |
| pdu, sizeof(pdu), |
| discover_included_cb, |
| bt_gatt_request_ref(op), |
| async_req_unref); |
| if (op->id) |
| return; |
| |
| success = false; |
| goto done; |
| } |
| |
| memcpy(read_pdu, data->result->pdu + data->pos + 2, sizeof(uint16_t)); |
| |
| data->pos += data->result->data_len; |
| |
| if (bt_att_send(op->att, BT_ATT_OP_READ_REQ, read_pdu, sizeof(read_pdu), |
| read_included_cb, read_included_ref(data), |
| read_included_unref)) |
| return; |
| |
| read_included_unref(data); |
| success = false; |
| |
| done: |
| discovery_op_complete(op, success, att_ecode); |
| } |
| |
| static void read_included(struct read_incl_data *data) |
| { |
| struct bt_gatt_request *op = data->op; |
| uint8_t pdu[2]; |
| |
| memcpy(pdu, data->result->pdu + 2, sizeof(uint16_t)); |
| |
| data->pos += data->result->data_len; |
| |
| if (bt_att_send(op->att, BT_ATT_OP_READ_REQ, pdu, sizeof(pdu), |
| read_included_cb, |
| read_included_ref(data), |
| read_included_unref)) |
| return; |
| |
| if (op->callback) |
| op->callback(false, 0, NULL, data->op->user_data); |
| |
| read_included_unref(data); |
| } |
| |
| static void discover_included_cb(uint8_t opcode, const void *pdu, |
| uint16_t length, void *user_data) |
| { |
| struct bt_gatt_request *op = user_data; |
| struct bt_gatt_result *cur_result; |
| uint8_t att_ecode = 0; |
| uint16_t last_handle; |
| size_t data_length; |
| bool success; |
| |
| if (opcode == BT_ATT_OP_ERROR_RSP) { |
| att_ecode = process_error(pdu, length); |
| success = false; |
| goto failed; |
| } |
| |
| if (opcode != BT_ATT_OP_READ_BY_TYPE_RSP || !pdu || length < 6) { |
| success = false; |
| goto failed; |
| } |
| |
| data_length = ((const uint8_t *) pdu)[0]; |
| |
| /* |
| * Check if PDU contains data sets with length declared in the beginning |
| * of frame and if this length is correct. |
| * Data set length may be 6 or 8 octets: |
| * 2 octets - include service handle |
| * 2 octets - start handle of included service |
| * 2 octets - end handle of included service |
| * optional 2 octets - Bluetooth UUID of included service |
| */ |
| if ((data_length != 8 && data_length != 6) || |
| (length - 1) % data_length) { |
| success = false; |
| goto failed; |
| } |
| |
| cur_result = result_append(opcode, pdu + 1, length - 1, data_length, |
| op); |
| if (!cur_result) { |
| success = false; |
| goto failed; |
| } |
| |
| if (data_length == 6) { |
| struct read_incl_data *data; |
| |
| data = new_read_included(cur_result); |
| if (!data) { |
| success = false; |
| goto failed; |
| } |
| |
| read_included(data); |
| return; |
| } |
| |
| last_handle = get_le16(pdu + length - data_length); |
| |
| /* |
| * If last handle is lower from previous start handle then it is smth |
| * wrong. Let's stop search, otherwise we might enter infinite loop. |
| */ |
| if (last_handle < op->start_handle) { |
| success = false; |
| goto failed; |
| } |
| |
| op->start_handle = last_handle + 1; |
| if (last_handle != op->end_handle) { |
| uint8_t pdu[6]; |
| |
| put_le16(op->start_handle, pdu); |
| put_le16(op->end_handle, pdu + 2); |
| put_le16(GATT_INCLUDE_UUID, pdu + 4); |
| |
| op->id = bt_att_send(op->att, BT_ATT_OP_READ_BY_TYPE_REQ, |
| pdu, sizeof(pdu), |
| discover_included_cb, |
| bt_gatt_request_ref(op), |
| async_req_unref); |
| if (op->id) |
| return; |
| |
| success = false; |
| goto failed; |
| } |
| |
| success = true; |
| |
| failed: |
| discovery_op_complete(op, success, att_ecode); |
| } |
| |
| struct bt_gatt_request *bt_gatt_discover_included_services(struct bt_att *att, |
| uint16_t start, uint16_t end, |
| bt_gatt_request_callback_t callback, |
| void *user_data, |
| bt_gatt_destroy_func_t destroy) |
| { |
| struct bt_gatt_request *op; |
| uint8_t pdu[6]; |
| |
| if (!att) |
| return false; |
| |
| op = new0(struct bt_gatt_request, 1); |
| op->att = att; |
| op->callback = callback; |
| op->user_data = user_data; |
| op->destroy = destroy; |
| op->start_handle = start; |
| op->end_handle = end; |
| |
| put_le16(start, pdu); |
| put_le16(end, pdu + 2); |
| put_le16(GATT_INCLUDE_UUID, pdu + 4); |
| |
| op->id = bt_att_send(att, BT_ATT_OP_READ_BY_TYPE_REQ, pdu, sizeof(pdu), |
| discover_included_cb, bt_gatt_request_ref(op), |
| async_req_unref); |
| if (!op->id) { |
| free(op); |
| return NULL; |
| } |
| |
| return bt_gatt_request_ref(op); |
| } |
| |
| static void discover_chrcs_cb(uint8_t opcode, const void *pdu, |
| uint16_t length, void *user_data) |
| { |
| struct bt_gatt_request *op = user_data; |
| bool success; |
| uint8_t att_ecode = 0; |
| size_t data_length; |
| uint16_t last_handle; |
| |
| if (opcode == BT_ATT_OP_ERROR_RSP) { |
| success = false; |
| att_ecode = process_error(pdu, length); |
| goto done; |
| } |
| |
| /* PDU must contain at least the following (sans opcode): |
| * - Attr Data Length (1 octet) |
| * - Attr Data List (at least 7 octets): |
| * -- 2 octets: Attribute handle |
| * -- 1 octet: Characteristic properties |
| * -- 2 octets: Characteristic value handle |
| * -- 2 or 16 octets: characteristic UUID |
| */ |
| if (opcode != BT_ATT_OP_READ_BY_TYPE_RSP || !pdu || length < 8) { |
| success = false; |
| goto done; |
| } |
| |
| data_length = ((uint8_t *) pdu)[0]; |
| |
| if ((data_length != 7 && data_length != 21) || |
| ((length - 1) % data_length)) { |
| success = false; |
| goto done; |
| } |
| |
| if (!result_append(opcode, pdu + 1, length - 1, |
| data_length, op)) { |
| success = false; |
| goto done; |
| } |
| last_handle = get_le16(pdu + length - data_length); |
| |
| /* |
| * If last handle is lower from previous start handle then it is smth |
| * wrong. Let's stop search, otherwise we might enter infinite loop. |
| */ |
| if (last_handle < op->start_handle) { |
| success = false; |
| goto done; |
| } |
| |
| op->start_handle = last_handle + 1; |
| |
| if (last_handle != op->end_handle) { |
| uint8_t pdu[6]; |
| |
| put_le16(op->start_handle, pdu); |
| put_le16(op->end_handle, pdu + 2); |
| put_le16(GATT_CHARAC_UUID, pdu + 4); |
| |
| op->id = bt_att_send(op->att, BT_ATT_OP_READ_BY_TYPE_REQ, |
| pdu, sizeof(pdu), |
| discover_chrcs_cb, |
| bt_gatt_request_ref(op), |
| async_req_unref); |
| if (op->id) |
| return; |
| |
| success = false; |
| goto done; |
| } |
| |
| success = true; |
| |
| done: |
| discovery_op_complete(op, success, att_ecode); |
| } |
| |
| struct bt_gatt_request *bt_gatt_discover_characteristics(struct bt_att *att, |
| uint16_t start, uint16_t end, |
| bt_gatt_request_callback_t callback, |
| void *user_data, |
| bt_gatt_destroy_func_t destroy) |
| { |
| struct bt_gatt_request *op; |
| uint8_t pdu[6]; |
| |
| if (!att) |
| return false; |
| |
| op = new0(struct bt_gatt_request, 1); |
| op->att = att; |
| op->callback = callback; |
| op->user_data = user_data; |
| op->destroy = destroy; |
| op->start_handle = start; |
| op->end_handle = end; |
| |
| put_le16(start, pdu); |
| put_le16(end, pdu + 2); |
| put_le16(GATT_CHARAC_UUID, pdu + 4); |
| |
| op->id = bt_att_send(att, BT_ATT_OP_READ_BY_TYPE_REQ, pdu, sizeof(pdu), |
| discover_chrcs_cb, bt_gatt_request_ref(op), |
| async_req_unref); |
| if (!op->id) { |
| free(op); |
| return NULL; |
| } |
| |
| return bt_gatt_request_ref(op); |
| } |
| |
| static void read_by_type_cb(uint8_t opcode, const void *pdu, |
| uint16_t length, void *user_data) |
| { |
| struct bt_gatt_request *op = user_data; |
| bool success; |
| uint8_t att_ecode = 0; |
| size_t data_length; |
| uint16_t last_handle; |
| |
| if (opcode == BT_ATT_OP_ERROR_RSP) { |
| att_ecode = process_error(pdu, length); |
| success = false; |
| goto done; |
| } |
| |
| if (opcode != BT_ATT_OP_READ_BY_TYPE_RSP || !pdu) { |
| success = false; |
| att_ecode = 0; |
| goto done; |
| } |
| |
| data_length = ((uint8_t *) pdu)[0]; |
| if (((length - 1) % data_length)) { |
| success = false; |
| att_ecode = 0; |
| goto done; |
| } |
| |
| if (!result_append(opcode, pdu + 1, length - 1, data_length, op)) { |
| success = false; |
| att_ecode = 0; |
| goto done; |
| } |
| |
| last_handle = get_le16(pdu + length - data_length); |
| |
| /* |
| * If last handle is lower from previous start handle then it is smth |
| * wrong. Let's stop search, otherwise we might enter infinite loop. |
| */ |
| if (last_handle < op->start_handle) { |
| success = false; |
| goto done; |
| } |
| |
| op->start_handle = last_handle + 1; |
| |
| if (last_handle != op->end_handle) { |
| uint8_t pdu[4 + get_uuid_len(&op->uuid)]; |
| |
| put_le16(op->start_handle, pdu); |
| put_le16(op->end_handle, pdu + 2); |
| bt_uuid_to_le(&op->uuid, pdu + 4); |
| |
| op->id = bt_att_send(op->att, BT_ATT_OP_READ_BY_TYPE_REQ, |
| pdu, sizeof(pdu), |
| read_by_type_cb, |
| bt_gatt_request_ref(op), |
| async_req_unref); |
| if (op->id) |
| return; |
| |
| success = false; |
| goto done; |
| } |
| |
| success = true; |
| |
| done: |
| discovery_op_complete(op, success, att_ecode); |
| } |
| |
| bool bt_gatt_read_by_type(struct bt_att *att, uint16_t start, uint16_t end, |
| const bt_uuid_t *uuid, |
| bt_gatt_request_callback_t callback, |
| void *user_data, |
| bt_gatt_destroy_func_t destroy) |
| { |
| struct bt_gatt_request *op; |
| uint8_t pdu[4 + get_uuid_len(uuid)]; |
| |
| if (!att || !uuid || uuid->type == BT_UUID_UNSPEC) |
| return false; |
| |
| op = new0(struct bt_gatt_request, 1); |
| op->att = att; |
| op->callback = callback; |
| op->user_data = user_data; |
| op->destroy = destroy; |
| op->start_handle = start; |
| op->end_handle = end; |
| op->uuid = *uuid; |
| |
| put_le16(start, pdu); |
| put_le16(end, pdu + 2); |
| bt_uuid_to_le(uuid, pdu + 4); |
| |
| op->id = bt_att_send(att, BT_ATT_OP_READ_BY_TYPE_REQ, pdu, sizeof(pdu), |
| read_by_type_cb, |
| bt_gatt_request_ref(op), |
| async_req_unref); |
| if (op->id) |
| return true; |
| |
| free(op); |
| return false; |
| } |
| |
| static void discover_descs_cb(uint8_t opcode, const void *pdu, |
| uint16_t length, void *user_data) |
| { |
| struct bt_gatt_request *op = user_data; |
| bool success; |
| uint8_t att_ecode = 0; |
| uint8_t format; |
| uint16_t last_handle; |
| size_t data_length; |
| |
| if (opcode == BT_ATT_OP_ERROR_RSP) { |
| success = false; |
| att_ecode = process_error(pdu, length); |
| goto done; |
| } |
| |
| /* The PDU should contain the following data (sans opcode): |
| * - Format (1 octet) |
| * - Attr Data List (at least 4 octets): |
| * -- 2 octets: Attribute handle |
| * -- 2 or 16 octets: UUID. |
| */ |
| if (opcode != BT_ATT_OP_FIND_INFO_RSP || !pdu || length < 5) { |
| success = false; |
| goto done; |
| } |
| |
| format = ((uint8_t *) pdu)[0]; |
| |
| if (format == 0x01) |
| data_length = 4; |
| else if (format == 0x02) |
| data_length = 18; |
| else { |
| success = false; |
| goto done; |
| } |
| |
| if ((length - 1) % data_length) { |
| success = false; |
| goto done; |
| } |
| |
| if (!result_append(opcode, pdu + 1, length - 1, data_length, op)) { |
| success = false; |
| goto done; |
| } |
| |
| last_handle = get_le16(pdu + length - data_length); |
| |
| /* |
| * If last handle is lower from previous start handle then it is smth |
| * wrong. Let's stop search, otherwise we might enter infinite loop. |
| */ |
| if (last_handle < op->start_handle) { |
| success = false; |
| goto done; |
| } |
| |
| op->start_handle = last_handle + 1; |
| |
| if (last_handle != op->end_handle) { |
| uint8_t pdu[4]; |
| |
| put_le16(op->start_handle, pdu); |
| put_le16(op->end_handle, pdu + 2); |
| |
| op->id = bt_att_send(op->att, BT_ATT_OP_FIND_INFO_REQ, |
| pdu, sizeof(pdu), |
| discover_descs_cb, |
| bt_gatt_request_ref(op), |
| async_req_unref); |
| if (op->id) |
| return; |
| |
| success = false; |
| goto done; |
| } |
| |
| success = true; |
| |
| done: |
| discovery_op_complete(op, success, att_ecode); |
| } |
| |
| struct bt_gatt_request *bt_gatt_discover_descriptors(struct bt_att *att, |
| uint16_t start, uint16_t end, |
| bt_gatt_request_callback_t callback, |
| void *user_data, |
| bt_gatt_destroy_func_t destroy) |
| { |
| struct bt_gatt_request *op; |
| uint8_t pdu[4]; |
| |
| if (!att) |
| return false; |
| |
| op = new0(struct bt_gatt_request, 1); |
| op->att = att; |
| op->callback = callback; |
| op->user_data = user_data; |
| op->destroy = destroy; |
| op->start_handle = start; |
| op->end_handle = end; |
| |
| put_le16(start, pdu); |
| put_le16(end, pdu + 2); |
| |
| op->id = bt_att_send(att, BT_ATT_OP_FIND_INFO_REQ, pdu, sizeof(pdu), |
| discover_descs_cb, |
| bt_gatt_request_ref(op), |
| async_req_unref); |
| if (!op->id) { |
| free(op); |
| return NULL; |
| } |
| |
| return bt_gatt_request_ref(op); |
| } |