/* * * 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 * */ #include "att.h" #include #include "uuid.h" #include "gatt-helpers.h" #include "util.h" #include "queue.h" #include "gatt-db.h" #include "gatt-client.h" #include #include #include #ifndef MAX #define MAX(a, b) ((a) > (b) ? (a) : (b)) #endif #ifndef MIN #define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif #define UUID_BYTES (BT_GATT_UUID_SIZE * sizeof(uint8_t)) #define GATT_SVC_UUID 0x1801 #define SVC_CHNGD_UUID 0x2a05 struct bt_gatt_client { struct bt_att *att; int ref_count; bt_gatt_client_callback_t ready_callback; bt_gatt_client_destroy_func_t ready_destroy; void *ready_data; bt_gatt_client_service_changed_callback_t svc_chngd_callback; bt_gatt_client_destroy_func_t svc_chngd_destroy; void *svc_chngd_data; bt_gatt_client_debug_func_t debug_callback; bt_gatt_client_destroy_func_t debug_destroy; void *debug_data; struct gatt_db *db; bool in_init; bool ready; /* * Queue of long write requests. An error during "prepare write" * requests can result in a cancel through "execute write". To prevent * cancelation of prepared writes to the wrong attribute and multiple * requests to the same attribute that may result in a corrupted final * value, we avoid interleaving prepared writes. */ struct queue *long_write_queue; bool in_long_write; unsigned int reliable_write_session_id; /* List of registered disconnect/notification/indication callbacks */ struct queue *notify_list; struct queue *notify_chrcs; int next_reg_id; unsigned int disc_id, notify_id, ind_id; /* * Handles of the GATT Service and the Service Changed characteristic * value handle. These will have the value 0 if they are not present on * the remote peripheral. */ unsigned int svc_chngd_ind_id; bool svc_chngd_registered; struct queue *svc_chngd_queue; /* Queued service changed events */ bool in_svc_chngd; /* * List of pending read/write operations. For operations that span * across multiple PDUs, this list provides a mapping from an operation * id to an ATT request id. */ struct queue *pending_requests; unsigned int next_request_id; struct bt_gatt_request *discovery_req; unsigned int mtu_req_id; }; struct request { struct bt_gatt_client *client; bool long_write; bool prep_write; bool removed; int ref_count; unsigned int id; unsigned int att_id; void *data; void (*destroy)(void *); }; static struct request *request_ref(struct request *req) { __sync_fetch_and_add(&req->ref_count, 1); return req; } static struct request *request_create(struct bt_gatt_client *client) { struct request *req; req = new0(struct request, 1); if (!req) return NULL; if (client->next_request_id < 1) client->next_request_id = 1; queue_push_tail(client->pending_requests, req); req->client = client; req->id = client->next_request_id++; return request_ref(req); } static void request_unref(void *data) { struct request *req = data; if (__sync_sub_and_fetch(&req->ref_count, 1)) return; if (req->destroy) req->destroy(req->data); if (!req->removed) queue_remove(req->client->pending_requests, req); free(req); } struct notify_chrc { uint16_t value_handle; uint16_t ccc_handle; uint16_t properties; int notify_count; /* Reference count of registered notify callbacks */ /* Pending calls to register_notify are queued here so that they can be * processed after a write that modifies the CCC descriptor. */ struct queue *reg_notify_queue; unsigned int ccc_write_id; }; struct notify_data { struct bt_gatt_client *client; unsigned int id; unsigned int att_id; int ref_count; struct notify_chrc *chrc; bt_gatt_client_register_callback_t callback; bt_gatt_client_notify_callback_t notify; void *user_data; bt_gatt_client_destroy_func_t destroy; }; static struct notify_data *notify_data_ref(struct notify_data *notify_data) { __sync_fetch_and_add(¬ify_data->ref_count, 1); return notify_data; } static void notify_data_unref(void *data) { struct notify_data *notify_data = data; if (__sync_sub_and_fetch(¬ify_data->ref_count, 1)) return; if (notify_data->destroy) notify_data->destroy(notify_data->user_data); free(notify_data); } static void find_ccc(struct gatt_db_attribute *attr, void *user_data) { struct gatt_db_attribute **ccc_ptr = user_data; bt_uuid_t uuid; if (*ccc_ptr) return; bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID); if (bt_uuid_cmp(&uuid, gatt_db_attribute_get_type(attr))) return; *ccc_ptr = attr; } static struct notify_chrc *notify_chrc_create(struct bt_gatt_client *client, uint16_t value_handle) { struct gatt_db_attribute *attr, *ccc; struct notify_chrc *chrc; bt_uuid_t uuid; uint8_t properties; /* Check that chrc_value_handle belongs to a known characteristic */ attr = gatt_db_get_attribute(client->db, value_handle - 1); if (!attr) return NULL; bt_uuid16_create(&uuid, GATT_CHARAC_UUID); if (bt_uuid_cmp(&uuid, gatt_db_attribute_get_type(attr))) return NULL; if (!gatt_db_attribute_get_char_data(attr, NULL, NULL, &properties, NULL)) return NULL; chrc = new0(struct notify_chrc, 1); if (!chrc) return NULL; chrc->reg_notify_queue = queue_new(); if (!chrc->reg_notify_queue) { free(chrc); return NULL; } /* * Find the CCC characteristic. Some characteristics that allow * notifications may not have a CCC descriptor. We treat these as * automatically successful. */ ccc = NULL; gatt_db_service_foreach_desc(attr, find_ccc, &ccc); if (ccc) chrc->ccc_handle = gatt_db_attribute_get_handle(ccc); chrc->value_handle = value_handle; chrc->properties = properties; queue_push_tail(client->notify_chrcs, chrc); return chrc; } static void notify_chrc_free(void *data) { struct notify_chrc *chrc = data; queue_destroy(chrc->reg_notify_queue, notify_data_unref); free(chrc); } static bool match_notify_data_id(const void *a, const void *b) { const struct notify_data *notify_data = a; unsigned int id = PTR_TO_UINT(b); return notify_data->id == id; } struct handle_range { uint16_t start; uint16_t end; }; static bool match_notify_data_handle_range(const void *a, const void *b) { const struct notify_data *notify_data = a; struct notify_chrc *chrc = notify_data->chrc; const struct handle_range *range = b; return chrc->value_handle >= range->start && chrc->value_handle <= range->end; } static bool match_notify_chrc_handle_range(const void *a, const void *b) { const struct notify_chrc *chrc = a; const struct handle_range *range = b; return chrc->value_handle >= range->start && chrc->value_handle <= range->end; } static void gatt_client_remove_all_notify_in_range( struct bt_gatt_client *client, uint16_t start_handle, uint16_t end_handle) { struct handle_range range; range.start = start_handle; range.end = end_handle; queue_remove_all(client->notify_list, match_notify_data_handle_range, &range, notify_data_unref); } static void gatt_client_remove_notify_chrcs_in_range( struct bt_gatt_client *client, uint16_t start_handle, uint16_t end_handle) { struct handle_range range; range.start = start_handle; range.end = end_handle; queue_remove_all(client->notify_chrcs, match_notify_chrc_handle_range, &range, notify_chrc_free); } struct discovery_op; typedef void (*discovery_op_complete_func_t)(struct discovery_op *op, bool success, uint8_t att_ecode); typedef void (*discovery_op_fail_func_t)(struct discovery_op *op); struct discovery_op { struct bt_gatt_client *client; struct queue *pending_svcs; struct queue *pending_chrcs; struct queue *tmp_queue; struct gatt_db_attribute *cur_svc; bool success; uint16_t start; uint16_t end; int ref_count; discovery_op_complete_func_t complete_func; discovery_op_fail_func_t failure_func; }; static void discovery_op_free(struct discovery_op *op) { queue_destroy(op->pending_svcs, NULL); queue_destroy(op->pending_chrcs, free); queue_destroy(op->tmp_queue, NULL); free(op); } static struct discovery_op *discovery_op_create(struct bt_gatt_client *client, uint16_t start, uint16_t end, discovery_op_complete_func_t complete_func, discovery_op_fail_func_t failure_func) { struct discovery_op *op; op = new0(struct discovery_op, 1); if (!op) return NULL; op->pending_svcs = queue_new(); if (!op->pending_svcs) goto fail; op->pending_chrcs = queue_new(); if (!op->pending_chrcs) goto fail; op->tmp_queue = queue_new(); if (!op->tmp_queue) goto fail; op->client = client; op->complete_func = complete_func; op->failure_func = failure_func; op->start = start; op->end = end; return op; fail: discovery_op_free(op); return NULL; } static struct discovery_op *discovery_op_ref(struct discovery_op *op) { __sync_fetch_and_add(&op->ref_count, 1); return op; } static void discovery_op_unref(void *data) { struct discovery_op *op = data; if (__sync_sub_and_fetch(&op->ref_count, 1)) return; if (!op->success) op->failure_func(op); discovery_op_free(op); } static void discovery_req_clear(struct bt_gatt_client *client) { if (!client->discovery_req) return; bt_gatt_request_unref(client->discovery_req); client->discovery_req = NULL; } static void discover_chrcs_cb(bool success, uint8_t att_ecode, struct bt_gatt_result *result, void *user_data); static void discover_incl_cb(bool success, uint8_t att_ecode, struct bt_gatt_result *result, void *user_data) { struct discovery_op *op = user_data; struct bt_gatt_client *client = op->client; struct bt_gatt_iter iter; struct gatt_db_attribute *attr, *tmp; uint16_t handle, start, end; uint128_t u128; bt_uuid_t uuid; char uuid_str[MAX_LEN_UUID_STR]; unsigned int includes_count, i; discovery_req_clear(client); if (!success) { if (att_ecode == BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND) goto next; goto failed; } /* Get the currently processed service */ attr = op->cur_svc; if (!attr) goto failed; if (!result || !bt_gatt_iter_init(&iter, result)) goto failed; includes_count = bt_gatt_result_included_count(result); if (includes_count == 0) goto failed; util_debug(client->debug_callback, client->debug_data, "Included services found: %u", includes_count); for (i = 0; i < includes_count; i++) { if (!bt_gatt_iter_next_included_service(&iter, &handle, &start, &end, u128.data)) break; bt_uuid128_create(&uuid, u128); /* Log debug message */ bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); util_debug(client->debug_callback, client->debug_data, "handle: 0x%04x, start: 0x%04x, end: 0x%04x," "uuid: %s", handle, start, end, uuid_str); tmp = gatt_db_get_attribute(client->db, start); if (!tmp) goto failed; tmp = gatt_db_service_add_included(attr, tmp); if (!tmp) goto failed; /* * GATT requires that all include definitions precede * characteristic declarations. Based on the order we're adding * these entries, the correct handle must be assigned to the new * attribute. */ if (gatt_db_attribute_get_handle(tmp) != handle) goto failed; } next: /* Move on to the next service */ attr = queue_pop_head(op->pending_svcs); if (!attr) { struct queue *tmp_queue; tmp_queue = op->pending_svcs; op->pending_svcs = op->tmp_queue; op->tmp_queue = tmp_queue; /* * We have processed all include definitions. Move on to * characteristics. */ attr = queue_pop_head(op->pending_svcs); if (!attr) goto failed; if (!gatt_db_attribute_get_service_handles(attr, &start, &end)) goto failed; op->cur_svc = attr; client->discovery_req = bt_gatt_discover_characteristics( client->att, start, end, discover_chrcs_cb, discovery_op_ref(op), discovery_op_unref); if (client->discovery_req) return; util_debug(client->debug_callback, client->debug_data, "Failed to start characteristic discovery"); discovery_op_unref(op); goto failed; } queue_push_tail(op->tmp_queue, attr); op->cur_svc = attr; if (!gatt_db_attribute_get_service_handles(attr, &start, &end)) goto failed; if (start == end) goto next; client->discovery_req = bt_gatt_discover_included_services(client->att, start, end, discover_incl_cb, discovery_op_ref(op), discovery_op_unref); if (client->discovery_req) return; util_debug(client->debug_callback, client->debug_data, "Failed to start included discovery"); discovery_op_unref(op); failed: op->success = false; op->complete_func(op, false, att_ecode); } struct chrc { uint16_t start_handle; uint16_t end_handle; uint16_t value_handle; uint8_t properties; bt_uuid_t uuid; }; static void discover_descs_cb(bool success, uint8_t att_ecode, struct bt_gatt_result *result, void *user_data); static bool discover_descs(struct discovery_op *op, bool *discovering) { struct bt_gatt_client *client = op->client; struct gatt_db_attribute *attr; struct chrc *chrc_data; uint16_t desc_start; *discovering = false; while ((chrc_data = queue_pop_head(op->pending_chrcs))) { attr = gatt_db_service_add_characteristic(op->cur_svc, &chrc_data->uuid, 0, chrc_data->properties, NULL, NULL, NULL); if (!attr) goto failed; if (gatt_db_attribute_get_handle(attr) != chrc_data->value_handle) goto failed; desc_start = chrc_data->value_handle + 1; if (desc_start > chrc_data->end_handle) { free(chrc_data); continue; } client->discovery_req = bt_gatt_discover_descriptors( client->att, desc_start, chrc_data->end_handle, discover_descs_cb, discovery_op_ref(op), discovery_op_unref); if (client->discovery_req) { *discovering = true; goto done; } util_debug(client->debug_callback, client->debug_data, "Failed to start descriptor discovery"); discovery_op_unref(op); goto failed; } done: free(chrc_data); return true; failed: free(chrc_data); return false; } static void discover_descs_cb(bool success, uint8_t att_ecode, struct bt_gatt_result *result, void *user_data) { struct discovery_op *op = user_data; struct bt_gatt_client *client = op->client; struct bt_gatt_iter iter; struct gatt_db_attribute *attr; uint16_t handle, start, end; uint128_t u128; bt_uuid_t uuid; char uuid_str[MAX_LEN_UUID_STR]; unsigned int desc_count; bool discovering; discovery_req_clear(client); if (!success) { if (att_ecode == BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND) { success = true; goto next; } goto done; } if (!result || !bt_gatt_iter_init(&iter, result)) goto failed; desc_count = bt_gatt_result_descriptor_count(result); if (desc_count == 0) goto failed; util_debug(client->debug_callback, client->debug_data, "Descriptors found: %u", desc_count); while (bt_gatt_iter_next_descriptor(&iter, &handle, u128.data)) { bt_uuid128_create(&uuid, u128); /* Log debug message */ bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); util_debug(client->debug_callback, client->debug_data, "handle: 0x%04x, uuid: %s", handle, uuid_str); attr = gatt_db_service_add_descriptor(op->cur_svc, &uuid, 0, NULL, NULL, NULL); if (!attr) goto failed; if (gatt_db_attribute_get_handle(attr) != handle) goto failed; } if (!discover_descs(op, &discovering)) goto failed; if (discovering) return; next: /* Done with the current service */ gatt_db_service_set_active(op->cur_svc, true); attr = queue_pop_head(op->pending_svcs); if (!attr) goto done; if (!gatt_db_attribute_get_service_handles(attr, &start, &end)) goto failed; if (start == end) goto next; /* Move on to the next service */ op->cur_svc = attr; client->discovery_req = bt_gatt_discover_characteristics(client->att, start, end, discover_chrcs_cb, discovery_op_ref(op), discovery_op_unref); if (client->discovery_req) return; util_debug(client->debug_callback, client->debug_data, "Failed to start characteristic discovery"); discovery_op_unref(op); failed: success = false; done: op->success = success; op->complete_func(op, success, att_ecode); } static void discover_chrcs_cb(bool success, uint8_t att_ecode, struct bt_gatt_result *result, void *user_data) { struct discovery_op *op = user_data; struct bt_gatt_client *client = op->client; struct bt_gatt_iter iter; struct gatt_db_attribute *attr; struct chrc *chrc_data; uint16_t start, end, value; uint8_t properties; uint128_t u128; bt_uuid_t uuid; char uuid_str[MAX_LEN_UUID_STR]; unsigned int chrc_count; bool discovering; discovery_req_clear(client); if (!success) { if (att_ecode == BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND) { success = true; goto next; } goto done; } if (!op->cur_svc || !result || !bt_gatt_iter_init(&iter, result)) goto failed; chrc_count = bt_gatt_result_characteristic_count(result); util_debug(client->debug_callback, client->debug_data, "Characteristics found: %u", chrc_count); if (chrc_count == 0) goto failed; while (bt_gatt_iter_next_characteristic(&iter, &start, &end, &value, &properties, u128.data)) { bt_uuid128_create(&uuid, u128); /* Log debug message */ bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); util_debug(client->debug_callback, client->debug_data, "start: 0x%04x, end: 0x%04x, value: 0x%04x, " "props: 0x%02x, uuid: %s", start, end, value, properties, uuid_str); chrc_data = new0(struct chrc, 1); if (!chrc_data) goto failed; chrc_data->start_handle = start; chrc_data->end_handle = end; chrc_data->value_handle = value; chrc_data->properties = properties; chrc_data->uuid = uuid; queue_push_tail(op->pending_chrcs, chrc_data); } /* * Sequentially discover descriptors for each characteristic and insert * the characteristics into the database as we proceed. */ if (!discover_descs(op, &discovering)) goto failed; if (discovering) return; next: /* Done with the current service */ gatt_db_service_set_active(op->cur_svc, true); attr = queue_pop_head(op->pending_svcs); if (!attr) goto done; if (!gatt_db_attribute_get_service_handles(attr, &start, &end)) goto failed; if (start == end) goto next; /* Move on to the next service */ op->cur_svc = attr; client->discovery_req = bt_gatt_discover_characteristics(client->att, start, end, discover_chrcs_cb, discovery_op_ref(op), discovery_op_unref); if (client->discovery_req) return; util_debug(client->debug_callback, client->debug_data, "Failed to start characteristic discovery"); discovery_op_unref(op); failed: success = false; done: op->success = success; op->complete_func(op, success, att_ecode); } static void discover_secondary_cb(bool success, uint8_t att_ecode, struct bt_gatt_result *result, void *user_data) { struct discovery_op *op = user_data; struct bt_gatt_client *client = op->client; struct bt_gatt_iter iter; struct gatt_db_attribute *attr; uint16_t start, end; uint128_t u128; bt_uuid_t uuid; char uuid_str[MAX_LEN_UUID_STR]; discovery_req_clear(client); if (!success) { util_debug(client->debug_callback, client->debug_data, "Secondary service discovery failed." " ATT ECODE: 0x%02x", att_ecode); switch (att_ecode) { case BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND: case BT_ATT_ERROR_UNSUPPORTED_GROUP_TYPE: goto next; default: goto done; } } if (!result || !bt_gatt_iter_init(&iter, result)) { success = false; goto done; } util_debug(client->debug_callback, client->debug_data, "Secondary services found: %u", bt_gatt_result_service_count(result)); while (bt_gatt_iter_next_service(&iter, &start, &end, u128.data)) { bt_uuid128_create(&uuid, u128); /* Log debug message */ bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); util_debug(client->debug_callback, client->debug_data, "start: 0x%04x, end: 0x%04x, uuid: %s", start, end, uuid_str); /* Store the service */ attr = gatt_db_insert_service(client->db, start, &uuid, false, end - start + 1); if (!attr) { util_debug(client->debug_callback, client->debug_data, "Failed to create service"); success = false; goto done; } queue_push_tail(op->pending_svcs, attr); } next: /* Sequentially discover included services */ attr = queue_pop_head(op->pending_svcs); /* Complete with success if queue is empty */ if (!attr) goto done; /* * Store the service in the tmp queue to be reused during * characteristics discovery later. */ queue_push_tail(op->tmp_queue, attr); op->cur_svc = attr; if (!gatt_db_attribute_get_service_handles(attr, &start, &end)) { success = false; goto done; } client->discovery_req = bt_gatt_discover_included_services(client->att, start, end, discover_incl_cb, discovery_op_ref(op), discovery_op_unref); if (client->discovery_req) return; util_debug(client->debug_callback, client->debug_data, "Failed to start included services discovery"); discovery_op_unref(op); done: op->success = success; op->complete_func(op, success, att_ecode); } static void discover_primary_cb(bool success, uint8_t att_ecode, struct bt_gatt_result *result, void *user_data) { struct discovery_op *op = user_data; struct bt_gatt_client *client = op->client; struct bt_gatt_iter iter; struct gatt_db_attribute *attr; uint16_t start, end; uint128_t u128; bt_uuid_t uuid; char uuid_str[MAX_LEN_UUID_STR]; discovery_req_clear(client); if (!success) { util_debug(client->debug_callback, client->debug_data, "Primary service discovery failed." " ATT ECODE: 0x%02x", att_ecode); goto secondary; } if (!result || !bt_gatt_iter_init(&iter, result)) { success = false; goto done; } util_debug(client->debug_callback, client->debug_data, "Primary services found: %u", bt_gatt_result_service_count(result)); while (bt_gatt_iter_next_service(&iter, &start, &end, u128.data)) { bt_uuid128_create(&uuid, u128); /* Log debug message. */ bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); util_debug(client->debug_callback, client->debug_data, "start: 0x%04x, end: 0x%04x, uuid: %s", start, end, uuid_str); attr = gatt_db_insert_service(client->db, start, &uuid, true, end - start + 1); if (!attr) { util_debug(client->debug_callback, client->debug_data, "Failed to store service"); success = false; goto done; } queue_push_tail(op->pending_svcs, attr); } secondary: /* Discover secondary services */ client->discovery_req = bt_gatt_discover_secondary_services(client->att, NULL, op->start, op->end, discover_secondary_cb, discovery_op_ref(op), discovery_op_unref); if (client->discovery_req) return; util_debug(client->debug_callback, client->debug_data, "Failed to start secondary service discovery"); discovery_op_unref(op); success = false; done: op->success = success; op->complete_func(op, success, att_ecode); } static void notify_client_ready(struct bt_gatt_client *client, bool success, uint8_t att_ecode) { if (!client->ready_callback) return; bt_gatt_client_ref(client); client->ready_callback(success, att_ecode, client->ready_data); bt_gatt_client_unref(client); } static void exchange_mtu_cb(bool success, uint8_t att_ecode, void *user_data) { struct discovery_op *op = user_data; struct bt_gatt_client *client = op->client; op->success = success; client->mtu_req_id = 0; if (!success) { util_debug(client->debug_callback, client->debug_data, "MTU Exchange failed. ATT ECODE: 0x%02x", att_ecode); client->in_init = false; notify_client_ready(client, success, att_ecode); return; } util_debug(client->debug_callback, client->debug_data, "MTU exchange complete, with MTU: %u", bt_att_get_mtu(client->att)); /* Don't do discovery if the database was pre-populated */ if (!gatt_db_isempty(client->db)) { op->complete_func(op, true, 0); return; } client->discovery_req = bt_gatt_discover_all_primary_services( client->att, NULL, discover_primary_cb, discovery_op_ref(op), discovery_op_unref); if (client->discovery_req) return; util_debug(client->debug_callback, client->debug_data, "Failed to initiate primary service discovery"); client->in_init = false; notify_client_ready(client, false, att_ecode); discovery_op_unref(op); } struct service_changed_op { struct bt_gatt_client *client; uint16_t start_handle; uint16_t end_handle; }; static void service_changed_reregister_cb(uint16_t att_ecode, void *user_data) { struct bt_gatt_client *client = user_data; if (!att_ecode) { util_debug(client->debug_callback, client->debug_data, "Re-registered handler for \"Service Changed\" after " "change in GATT service"); client->svc_chngd_registered = true; return; } util_debug(client->debug_callback, client->debug_data, "Failed to register handler for \"Service Changed\""); client->svc_chngd_ind_id = 0; } static void process_service_changed(struct bt_gatt_client *client, uint16_t start_handle, uint16_t end_handle); static void service_changed_cb(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data); static void get_first_attribute(struct gatt_db_attribute *attrib, void *user_data) { struct gatt_db_attribute **stored = user_data; if (*stored) return; *stored = attrib; } static void service_changed_complete(struct discovery_op *op, bool success, uint8_t att_ecode) { struct bt_gatt_client *client = op->client; struct service_changed_op *next_sc_op; uint16_t start_handle = op->start; uint16_t end_handle = op->end; struct gatt_db_attribute *attr = NULL; bt_uuid_t uuid; client->in_svc_chngd = false; if (!success && att_ecode != BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND) { util_debug(client->debug_callback, client->debug_data, "Failed to discover services within changed range - " "error: 0x%02x", att_ecode); gatt_db_clear_range(client->db, start_handle, end_handle); } /* Notify the upper layer of changed services */ if (client->svc_chngd_callback) client->svc_chngd_callback(start_handle, end_handle, client->svc_chngd_data); /* Process any queued events */ next_sc_op = queue_pop_head(client->svc_chngd_queue); if (next_sc_op) { process_service_changed(client, next_sc_op->start_handle, next_sc_op->end_handle); free(next_sc_op); return; } bt_uuid16_create(&uuid, SVC_CHNGD_UUID); gatt_db_find_by_type(client->db, start_handle, end_handle, &uuid, get_first_attribute, &attr); if (!attr) return; /* The GATT service was modified. Re-register the handler for * indications from the "Service Changed" characteristic. */ client->svc_chngd_registered = false; client->svc_chngd_ind_id = bt_gatt_client_register_notify(client, gatt_db_attribute_get_handle(attr), service_changed_reregister_cb, service_changed_cb, client, NULL); if (client->svc_chngd_ind_id) return; util_debug(client->debug_callback, client->debug_data, "Failed to re-register handler for \"Service Changed\""); } static void service_changed_failure(struct discovery_op *op) { struct bt_gatt_client *client = op->client; gatt_db_clear_range(client->db, op->start, op->end); } static void process_service_changed(struct bt_gatt_client *client, uint16_t start_handle, uint16_t end_handle) { struct discovery_op *op; /* Invalidate and remove all effected notify callbacks */ gatt_client_remove_all_notify_in_range(client, start_handle, end_handle); gatt_client_remove_notify_chrcs_in_range(client, start_handle, end_handle); /* Remove all services that overlap the modified range since we'll * rediscover them */ gatt_db_clear_range(client->db, start_handle, end_handle); op = discovery_op_create(client, start_handle, end_handle, service_changed_complete, service_changed_failure); if (!op) goto fail; client->discovery_req = bt_gatt_discover_primary_services(client->att, NULL, start_handle, end_handle, discover_primary_cb, discovery_op_ref(op), discovery_op_unref); if (client->discovery_req) { client->in_svc_chngd = true; return; } discovery_op_free(op); fail: util_debug(client->debug_callback, client->debug_data, "Failed to initiate service discovery" " after Service Changed"); } static void service_changed_cb(uint16_t value_handle, const uint8_t *value, uint16_t length, void *user_data) { struct bt_gatt_client *client = user_data; struct service_changed_op *op; uint16_t start, end; if (length != 4) return; start = get_le16(value); end = get_le16(value + 2); if (start > end) { util_debug(client->debug_callback, client->debug_data, "Service Changed received with invalid handles"); return; } util_debug(client->debug_callback, client->debug_data, "Service Changed received - start: 0x%04x end: 0x%04x", start, end); if (!client->in_svc_chngd) { process_service_changed(client, start, end); return; } op = new0(struct service_changed_op, 1); if (!op) return; op->start_handle = start; op->end_handle = end; queue_push_tail(client->svc_chngd_queue, op); } static void service_changed_register_cb(uint16_t att_ecode, void *user_data) { bool success; struct bt_gatt_client *client = user_data; if (att_ecode) { util_debug(client->debug_callback, client->debug_data, "Failed to register handler for \"Service Changed\""); success = false; client->svc_chngd_ind_id = 0; goto done; } client->svc_chngd_registered = true; client->ready = true; success = true; util_debug(client->debug_callback, client->debug_data, "Registered handler for \"Service Changed\": %u", client->svc_chngd_ind_id); done: notify_client_ready(client, success, att_ecode); } static void init_complete(struct discovery_op *op, bool success, uint8_t att_ecode) { struct bt_gatt_client *client = op->client; struct gatt_db_attribute *attr = NULL; bt_uuid_t uuid; client->in_init = false; if (!success) goto fail; bt_uuid16_create(&uuid, SVC_CHNGD_UUID); gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid, get_first_attribute, &attr); if (!attr) { client->ready = true; goto done; } /* Register an indication handler for the "Service Changed" * characteristic and report ready only if the handler is registered * successfully. Temporarily set "ready" to true so that we can register * the handler using the existing framework. */ client->ready = true; client->svc_chngd_ind_id = bt_gatt_client_register_notify(client, gatt_db_attribute_get_handle(attr), service_changed_register_cb, service_changed_cb, client, NULL); if (!client->svc_chngd_registered) client->ready = false; if (client->svc_chngd_ind_id) return; util_debug(client->debug_callback, client->debug_data, "Failed to register handler for \"Service Changed\""); success = false; fail: util_debug(client->debug_callback, client->debug_data, "Failed to initialize gatt-client"); op->success = false; done: notify_client_ready(client, success, att_ecode); } static void init_fail(struct discovery_op *op) { struct bt_gatt_client *client = op->client; gatt_db_clear(client->db); } static bool gatt_client_init(struct bt_gatt_client *client, uint16_t mtu) { struct discovery_op *op; if (client->in_init || client->ready) return false; op = discovery_op_create(client, 0x0001, 0xffff, init_complete, init_fail); if (!op) return false; /* Configure the MTU */ client->mtu_req_id = bt_gatt_exchange_mtu(client->att, MAX(BT_ATT_DEFAULT_LE_MTU, mtu), exchange_mtu_cb, discovery_op_ref(op), discovery_op_unref); if (!client->mtu_req_id) { discovery_op_free(op); return false; } client->in_init = true; return true; } struct pdu_data { const void *pdu; uint16_t length; }; static void complete_notify_request(void *data) { struct notify_data *notify_data = data; /* Increment the per-characteristic ref count of notify handlers */ __sync_fetch_and_add(¬ify_data->chrc->notify_count, 1); notify_data->att_id = 0; notify_data->callback(0, notify_data->user_data); } static bool notify_data_write_ccc(struct notify_data *notify_data, bool enable, bt_att_response_func_t callback) { uint8_t pdu[4]; unsigned int att_id; assert(notify_data->chrc->ccc_handle); memset(pdu, 0, sizeof(pdu)); put_le16(notify_data->chrc->ccc_handle, pdu); if (enable) { /* Try to enable notifications and/or indications based on * whatever the characteristic supports. */ if (notify_data->chrc->properties & BT_GATT_CHRC_PROP_NOTIFY) pdu[2] = 0x01; if (notify_data->chrc->properties & BT_GATT_CHRC_PROP_INDICATE) pdu[2] |= 0x02; if (!pdu[2]) return false; } att_id = bt_att_send(notify_data->client->att, BT_ATT_OP_WRITE_REQ, pdu, sizeof(pdu), callback, notify_data_ref(notify_data), notify_data_unref); notify_data->chrc->ccc_write_id = notify_data->att_id = att_id; return !!att_id; } 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 enable_ccc_callback(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct notify_data *notify_data = user_data; uint16_t att_ecode; assert(!notify_data->chrc->notify_count); assert(notify_data->chrc->ccc_write_id); notify_data->chrc->ccc_write_id = 0; if (opcode == BT_ATT_OP_ERROR_RSP) { att_ecode = process_error(pdu, length); /* Failed to enable. Complete the current request and move on to * the next one in the queue. If there was an error sending the * write request, then just move on to the next queued entry. */ queue_remove(notify_data->client->notify_list, notify_data); notify_data->callback(att_ecode, notify_data->user_data); while ((notify_data = queue_pop_head( notify_data->chrc->reg_notify_queue))) { if (notify_data_write_ccc(notify_data, true, enable_ccc_callback)) return; } return; } /* Success! Report success for all remaining requests. */ bt_gatt_client_ref(notify_data->client); complete_notify_request(notify_data); queue_remove_all(notify_data->chrc->reg_notify_queue, NULL, NULL, complete_notify_request); bt_gatt_client_unref(notify_data->client); } static void disable_ccc_callback(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct notify_data *notify_data = user_data; struct notify_data *next_data; assert(!notify_data->chrc->notify_count); assert(notify_data->chrc->ccc_write_id); notify_data->chrc->ccc_write_id = 0; /* This is a best effort procedure, so ignore errors and process any * queued requests. */ while (1) { next_data = queue_pop_head(notify_data->chrc->reg_notify_queue); if (!next_data || notify_data_write_ccc(notify_data, true, enable_ccc_callback)) return; } } static void complete_unregister_notify(void *data) { struct notify_data *notify_data = data; /* * If a procedure to enable the CCC is still pending, then cancel it and * return. */ if (notify_data->att_id) { bt_att_cancel(notify_data->client->att, notify_data->att_id); goto done; } if (__sync_sub_and_fetch(¬ify_data->chrc->notify_count, 1) || !notify_data->chrc->ccc_handle) goto done; if (notify_data_write_ccc(notify_data, false, disable_ccc_callback)) return; done: notify_data_unref(notify_data); } static void notify_handler(void *data, void *user_data) { struct notify_data *notify_data = data; struct pdu_data *pdu_data = user_data; uint16_t value_handle; const uint8_t *value = NULL; value_handle = get_le16(pdu_data->pdu); if (notify_data->chrc->value_handle != value_handle) return; if (pdu_data->length > 2) value = pdu_data->pdu + 2; /* * Even if the notify data has a pending ATT request to write to the * CCC, there is really no reason not to notify the handlers. */ if (notify_data->notify) notify_data->notify(value_handle, value, pdu_data->length - 2, notify_data->user_data); } static void notify_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_client *client = user_data; struct pdu_data pdu_data; bt_gatt_client_ref(client); memset(&pdu_data, 0, sizeof(pdu_data)); pdu_data.pdu = pdu; pdu_data.length = length; queue_foreach(client->notify_list, notify_handler, &pdu_data); if (opcode == BT_ATT_OP_HANDLE_VAL_IND) bt_att_send(client->att, BT_ATT_OP_HANDLE_VAL_CONF, NULL, 0, NULL, NULL, NULL); bt_gatt_client_unref(client); } static void notify_data_cleanup(void *data) { struct notify_data *notify_data = data; if (notify_data->att_id) bt_att_cancel(notify_data->client->att, notify_data->att_id); notify_data_unref(notify_data); } static void bt_gatt_client_free(struct bt_gatt_client *client) { bt_gatt_client_cancel_all(client); queue_destroy(client->notify_list, notify_data_cleanup); if (client->ready_destroy) client->ready_destroy(client->ready_data); if (client->debug_destroy) client->debug_destroy(client->debug_data); if (client->att) { bt_att_unregister_disconnect(client->att, client->disc_id); bt_att_unregister(client->att, client->notify_id); bt_att_unregister(client->att, client->ind_id); bt_att_unref(client->att); } gatt_db_unref(client->db); queue_destroy(client->svc_chngd_queue, free); queue_destroy(client->long_write_queue, request_unref); queue_destroy(client->notify_chrcs, notify_chrc_free); queue_destroy(client->pending_requests, request_unref); free(client); } static void att_disconnect_cb(int err, void *user_data) { struct bt_gatt_client *client = user_data; bool in_init = client->in_init; client->disc_id = 0; bt_att_unref(client->att); client->att = NULL; client->in_init = false; client->ready = false; if (in_init) notify_client_ready(client, false, 0); } struct bt_gatt_client *bt_gatt_client_new(struct gatt_db *db, struct bt_att *att, uint16_t mtu) { struct bt_gatt_client *client; if (!att || !db) return NULL; client = new0(struct bt_gatt_client, 1); if (!client) return NULL; client->disc_id = bt_att_register_disconnect(att, att_disconnect_cb, client, NULL); if (!client->disc_id) goto fail; client->long_write_queue = queue_new(); if (!client->long_write_queue) goto fail; client->svc_chngd_queue = queue_new(); if (!client->svc_chngd_queue) goto fail; client->notify_list = queue_new(); if (!client->notify_list) goto fail; client->notify_chrcs = queue_new(); if (!client->notify_chrcs) goto fail; client->pending_requests = queue_new(); if (!client->pending_requests) goto fail; client->notify_id = bt_att_register(att, BT_ATT_OP_HANDLE_VAL_NOT, notify_cb, client, NULL); if (!client->notify_id) goto fail; client->ind_id = bt_att_register(att, BT_ATT_OP_HANDLE_VAL_IND, notify_cb, client, NULL); if (!client->ind_id) goto fail; client->att = bt_att_ref(att); client->db = gatt_db_ref(db); if (!gatt_client_init(client, mtu)) goto fail; return bt_gatt_client_ref(client); fail: bt_gatt_client_free(client); return NULL; } struct bt_gatt_client *bt_gatt_client_ref(struct bt_gatt_client *client) { if (!client) return NULL; __sync_fetch_and_add(&client->ref_count, 1); return client; } void bt_gatt_client_unref(struct bt_gatt_client *client) { if (!client) return; if (__sync_sub_and_fetch(&client->ref_count, 1)) return; bt_gatt_client_free(client); } bool bt_gatt_client_is_ready(struct bt_gatt_client *client) { return (client && client->ready); } bool bt_gatt_client_set_ready_handler(struct bt_gatt_client *client, bt_gatt_client_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { if (!client) return false; if (client->ready_destroy) client->ready_destroy(client->ready_data); client->ready_callback = callback; client->ready_destroy = destroy; client->ready_data = user_data; return true; } bool bt_gatt_client_set_service_changed(struct bt_gatt_client *client, bt_gatt_client_service_changed_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { if (!client) return false; if (client->svc_chngd_destroy) client->svc_chngd_destroy(client->svc_chngd_data); client->svc_chngd_callback = callback; client->svc_chngd_destroy = destroy; client->svc_chngd_data = user_data; return true; } bool bt_gatt_client_set_debug(struct bt_gatt_client *client, bt_gatt_client_debug_func_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { if (!client) return false; if (client->debug_destroy) client->debug_destroy(client->debug_data); client->debug_callback = callback; client->debug_destroy = destroy; client->debug_data = user_data; return true; } uint16_t bt_gatt_client_get_mtu(struct bt_gatt_client *client) { if (!client || !client->att) return 0; return bt_att_get_mtu(client->att); } static bool match_req_id(const void *a, const void *b) { const struct request *req = a; unsigned int id = PTR_TO_UINT(b); return req->id == id; } static void cancel_long_write_cb(uint8_t opcode, const void *pdu, uint16_t len, void *user_data) { struct bt_gatt_client *client = user_data; if (queue_isempty(client->long_write_queue)) client->in_long_write = false; } static bool cancel_long_write_req(struct bt_gatt_client *client, struct request *req) { uint8_t pdu = 0x00; /* * att_id == 0 means that request has been queued and no prepare write * has been sent so far.Let's just remove if from the queue. * Otherwise execute write needs to be send. */ if (!req->att_id) return queue_remove(client->long_write_queue, req); return !!bt_att_send(client->att, BT_ATT_OP_EXEC_WRITE_REQ, &pdu, sizeof(pdu), cancel_long_write_cb, client, NULL); } static void cancel_prep_write_cb(uint8_t opcode, const void *pdu, uint16_t len, void *user_data) { struct request *req = user_data; struct bt_gatt_client *client = req->client; client->reliable_write_session_id = 0; } static bool cancel_prep_write_session(struct bt_gatt_client *client, struct request *req) { uint8_t pdu = 0x00; return !!bt_att_send(client->att, BT_ATT_OP_EXEC_WRITE_REQ, &pdu, sizeof(pdu), cancel_prep_write_cb, req, request_unref); } bool bt_gatt_client_cancel(struct bt_gatt_client *client, unsigned int id) { struct request *req; if (!client || !id || !client->att) return false; req = queue_remove_if(client->pending_requests, match_req_id, UINT_TO_PTR(id)); if (!req) return false; req->removed = true; if (!bt_att_cancel(client->att, req->att_id) && !req->long_write && !req->prep_write) return false; /* If this was a long-write, we need to abort all prepared writes */ if (req->long_write) return cancel_long_write_req(client, req); if (req->prep_write) return cancel_prep_write_session(client, req); return true; } static void cancel_request(void *data) { struct request *req = data; req->removed = true; bt_att_cancel(req->client->att, req->att_id); if (req->long_write) cancel_long_write_req(req->client, req); if (req->prep_write) cancel_prep_write_session(req->client, req); } bool bt_gatt_client_cancel_all(struct bt_gatt_client *client) { if (!client || !client->att) return false; queue_remove_all(client->pending_requests, NULL, NULL, cancel_request); if (client->discovery_req) { bt_gatt_request_cancel(client->discovery_req); bt_gatt_request_unref(client->discovery_req); client->discovery_req = NULL; } if (client->mtu_req_id) bt_att_cancel(client->att, client->mtu_req_id); return true; } struct read_op { bt_gatt_client_read_callback_t callback; void *user_data; bt_gatt_client_destroy_func_t destroy; }; static void destroy_read_op(void *data) { struct read_op *op = data; if (op->destroy) op->destroy(op->user_data); free(op); } static void read_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct request *req = user_data; struct read_op *op = req->data; bool success; uint8_t att_ecode = 0; const uint8_t *value = NULL; uint16_t value_len = 0; 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; } success = true; value_len = length; if (value_len) value = pdu; done: if (op->callback) op->callback(success, att_ecode, value, length, op->user_data); } unsigned int bt_gatt_client_read_value(struct bt_gatt_client *client, uint16_t value_handle, bt_gatt_client_read_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { struct request *req; struct read_op *op; uint8_t pdu[2]; if (!client) return 0; op = new0(struct read_op, 1); if (!op) return 0; req = request_create(client); if (!req) { free(op); return 0; } op->callback = callback; op->user_data = user_data; op->destroy = destroy; req->data = op; req->destroy = destroy_read_op; put_le16(value_handle, pdu); req->att_id = bt_att_send(client->att, BT_ATT_OP_READ_REQ, pdu, sizeof(pdu), read_cb, req, request_unref); if (!req->att_id) { op->destroy = NULL; request_unref(req); return 0; } return req->id; } static void read_multiple_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct request *req = user_data; struct read_op *op = req->data; uint8_t att_ecode; bool success; if (opcode != BT_ATT_OP_READ_MULT_RSP || (!pdu && length)) { success = false; if (opcode == BT_ATT_OP_ERROR_RSP) att_ecode = process_error(pdu, length); else att_ecode = 0; pdu = NULL; length = 0; } else { success = true; att_ecode = 0; } if (op->callback) op->callback(success, att_ecode, pdu, length, op->user_data); } unsigned int bt_gatt_client_read_multiple(struct bt_gatt_client *client, uint16_t *handles, uint8_t num_handles, bt_gatt_client_read_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { uint8_t pdu[num_handles * 2]; struct request *req; struct read_op *op; int i; if (!client) return 0; if (num_handles < 2) return 0; if (num_handles * 2 > bt_att_get_mtu(client->att) - 1) return 0; op = new0(struct read_op, 1); if (!op) return 0; req = request_create(client); if (!req) { free(op); return 0; } op->callback = callback; op->user_data = user_data; op->destroy = destroy; req->data = op; req->destroy = destroy_read_op; for (i = 0; i < num_handles; i++) put_le16(handles[i], pdu + (2 * i)); req->att_id = bt_att_send(client->att, BT_ATT_OP_READ_MULT_REQ, pdu, sizeof(pdu), read_multiple_cb, req, request_unref); if (!req->att_id) { op->destroy = NULL; request_unref(req); return 0; } return req->id; } struct read_long_op { struct bt_gatt_client *client; int ref_count; uint16_t value_handle; uint16_t offset; struct iovec iov; bt_gatt_client_read_callback_t callback; void *user_data; bt_gatt_client_destroy_func_t destroy; }; static void destroy_read_long_op(void *data) { struct read_long_op *op = data; if (op->destroy) op->destroy(op->user_data); free(op->iov.iov_base); free(op); } static bool append_chunk(struct read_long_op *op, const uint8_t *data, uint16_t len) { void *buf; /* Truncate if the data would exceed maximum length */ if (op->offset + len > BT_ATT_MAX_VALUE_LEN) len = BT_ATT_MAX_VALUE_LEN - op->offset; buf = realloc(op->iov.iov_base, op->iov.iov_len + len); if (!buf) return false; op->iov.iov_base = buf; memcpy(op->iov.iov_base + op->iov.iov_len, data, len); op->iov.iov_len += len; op->offset += len; return true; } static void read_long_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct request *req = user_data; struct read_long_op *op = req->data; bool success; uint8_t att_ecode = 0; if (opcode == BT_ATT_OP_ERROR_RSP) { success = false; att_ecode = process_error(pdu, length); goto done; } if (opcode != BT_ATT_OP_READ_BLOB_RSP || (!pdu && length)) { success = false; goto done; } if (!length) goto success; if (!append_chunk(op, pdu, length)) { success = false; goto done; } if (op->offset >= BT_ATT_MAX_VALUE_LEN) goto success; if (length >= bt_att_get_mtu(op->client->att) - 1) { uint8_t pdu[4]; put_le16(op->value_handle, pdu); put_le16(op->offset, pdu + 2); req->att_id = bt_att_send(op->client->att, BT_ATT_OP_READ_BLOB_REQ, pdu, sizeof(pdu), read_long_cb, request_ref(req), request_unref); if (req->att_id) return; request_unref(req); success = false; goto done; } success: success = true; done: if (op->callback) op->callback(success, att_ecode, op->iov.iov_base, op->iov.iov_len, op->user_data); } unsigned int bt_gatt_client_read_long_value(struct bt_gatt_client *client, uint16_t value_handle, uint16_t offset, bt_gatt_client_read_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { struct request *req; struct read_long_op *op; uint8_t pdu[4]; if (!client) return 0; op = new0(struct read_long_op, 1); if (!op) return 0; req = request_create(client); if (!req) { free(op); return 0; } op->client = client; op->value_handle = value_handle; op->offset = offset; op->callback = callback; op->user_data = user_data; op->destroy = destroy; req->data = op; req->destroy = destroy_read_long_op; put_le16(value_handle, pdu); put_le16(offset, pdu + 2); req->att_id = bt_att_send(client->att, BT_ATT_OP_READ_BLOB_REQ, pdu, sizeof(pdu), read_long_cb, req, request_unref); if (!req->att_id) { op->destroy = NULL; request_unref(req); return 0; } return req->id; } unsigned int bt_gatt_client_write_without_response( struct bt_gatt_client *client, uint16_t value_handle, bool signed_write, const uint8_t *value, uint16_t length) { uint8_t pdu[2 + length]; struct request *req; int security; uint8_t op; if (!client) return 0; req = request_create(client); if (!req) return 0; /* Only use signed write if unencrypted */ if (signed_write) { security = bt_att_get_sec_level(client->att); op = security > BT_SECURITY_LOW ? BT_ATT_OP_WRITE_CMD : BT_ATT_OP_SIGNED_WRITE_CMD; } else op = BT_ATT_OP_WRITE_CMD; put_le16(value_handle, pdu); memcpy(pdu + 2, value, length); req->att_id = bt_att_send(client->att, op, pdu, sizeof(pdu), NULL, req, request_unref); if (!req->att_id) { request_unref(req); return 0; } return req->id; } struct write_op { struct bt_gatt_client *client; bt_gatt_client_callback_t callback; void *user_data; bt_gatt_destroy_func_t destroy; }; static void destroy_write_op(void *data) { struct write_op *op = data; if (op->destroy) op->destroy(op->user_data); free(op); } static void write_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct request *req = user_data; struct write_op *op = req->data; bool success = true; uint8_t att_ecode = 0; if (opcode == BT_ATT_OP_ERROR_RSP) { success = false; att_ecode = process_error(pdu, length); goto done; } if (opcode != BT_ATT_OP_WRITE_RSP || pdu || length) success = false; done: if (op->callback) op->callback(success, att_ecode, op->user_data); } unsigned int bt_gatt_client_write_value(struct bt_gatt_client *client, uint16_t value_handle, const uint8_t *value, uint16_t length, bt_gatt_client_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { struct request *req; struct write_op *op; uint8_t pdu[2 + length]; if (!client) return 0; op = new0(struct write_op, 1); if (!op) return 0; req = request_create(client); if (!req) { free(op); return 0; } op->callback = callback; op->user_data = user_data; op->destroy = destroy; req->data = op; req->destroy = destroy_write_op; put_le16(value_handle, pdu); memcpy(pdu + 2, value, length); req->att_id = bt_att_send(client->att, BT_ATT_OP_WRITE_REQ, pdu, sizeof(pdu), write_cb, req, request_unref); if (!req->att_id) { op->destroy = NULL; request_unref(req); return 0; } return req->id; } struct long_write_op { struct bt_gatt_client *client; bool reliable; bool success; uint8_t att_ecode; bool reliable_error; uint16_t value_handle; uint8_t *value; uint16_t length; uint16_t offset; uint16_t index; uint16_t cur_length; bt_gatt_client_write_long_callback_t callback; void *user_data; bt_gatt_client_destroy_func_t destroy; }; static void long_write_op_free(void *data) { struct long_write_op *op = data; if (op->destroy) op->destroy(op->user_data); free(op->value); free(op); } static void prepare_write_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data); static void complete_write_long_op(struct request *req, bool success, uint8_t att_ecode, bool reliable_error); static void handle_next_prep_write(struct request *req) { struct long_write_op *op = req->data; bool success = true; uint8_t *pdu; pdu = malloc(op->cur_length + 4); if (!pdu) { success = false; goto done; } put_le16(op->value_handle, pdu); put_le16(op->offset + op->index, pdu + 2); memcpy(pdu + 4, op->value + op->index, op->cur_length); req->att_id = bt_att_send(op->client->att, BT_ATT_OP_PREP_WRITE_REQ, pdu, op->cur_length + 4, prepare_write_cb, request_ref(req), request_unref); if (!req->att_id) { request_unref(req); success = false; } free(pdu); /* If so far successful, then the operation should continue. * Otherwise, there was an error and the procedure should be * completed. */ if (success) return; done: complete_write_long_op(req, success, 0, false); } static void start_next_long_write(struct bt_gatt_client *client) { struct request *req; if (queue_isempty(client->long_write_queue)) { client->in_long_write = false; return; } req = queue_pop_head(client->long_write_queue); if (!req) return; handle_next_prep_write(req); /* * send_next_prep_write adds an extra ref. Unref here to clean up if * necessary, since we also added a ref before pushing to the queue. */ request_unref(req); } static void execute_write_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct request *req = user_data; struct long_write_op *op = req->data; bool success = op->success; uint8_t att_ecode = op->att_ecode; if (opcode == BT_ATT_OP_ERROR_RSP) { success = false; att_ecode = process_error(pdu, length); } else if (opcode != BT_ATT_OP_EXEC_WRITE_RSP || pdu || length) success = false; if (op->callback) op->callback(success, op->reliable_error, att_ecode, op->user_data); start_next_long_write(op->client); } static void complete_write_long_op(struct request *req, bool success, uint8_t att_ecode, bool reliable_error) { struct long_write_op *op = req->data; uint8_t pdu; op->success = success; op->att_ecode = att_ecode; op->reliable_error = reliable_error; if (success) pdu = 0x01; /* Write */ else pdu = 0x00; /* Cancel */ req->att_id = bt_att_send(op->client->att, BT_ATT_OP_EXEC_WRITE_REQ, &pdu, sizeof(pdu), execute_write_cb, request_ref(req), request_unref); if (req->att_id) return; request_unref(req); success = false; if (op->callback) op->callback(success, reliable_error, att_ecode, op->user_data); start_next_long_write(op->client); } static void prepare_write_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct request *req = user_data; struct long_write_op *op = req->data; bool success = true; bool reliable_error = false; uint8_t att_ecode = 0; uint16_t next_index; if (opcode == BT_ATT_OP_ERROR_RSP) { success = false; att_ecode = process_error(pdu, length); goto done; } if (opcode != BT_ATT_OP_PREP_WRITE_RSP) { success = false; goto done; } if (op->reliable) { if (!pdu || length != (op->cur_length + 4)) { success = false; reliable_error = true; goto done; } if (get_le16(pdu) != op->value_handle || get_le16(pdu + 2) != (op->offset + op->index)) { success = false; reliable_error = true; goto done; } if (memcmp(pdu + 4, op->value + op->index, op->cur_length)) { success = false; reliable_error = true; goto done; } } next_index = op->index + op->cur_length; if (next_index == op->length) { /* All bytes written */ goto done; } /* If the last written length was greater than or equal to what can fit * inside a PDU, then there is more data to send. */ if (op->cur_length >= bt_att_get_mtu(op->client->att) - 5) { op->index = next_index; op->cur_length = MIN(op->length - op->index, bt_att_get_mtu(op->client->att) - 5); handle_next_prep_write(req); return; } done: complete_write_long_op(req, success, att_ecode, reliable_error); } unsigned int bt_gatt_client_write_long_value(struct bt_gatt_client *client, bool reliable, uint16_t value_handle, uint16_t offset, const uint8_t *value, uint16_t length, bt_gatt_client_write_long_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { struct request *req; struct long_write_op *op; uint8_t *pdu; if (!client) return 0; if ((size_t)(length + offset) > UINT16_MAX) return 0; /* Don't allow writing a 0-length value using this procedure. The * upper-layer should use bt_gatt_write_value for that instead. */ if (!length || !value) return 0; op = new0(struct long_write_op, 1); if (!op) return 0; op->value = malloc(length); if (!op->value) { free(op); return 0; } req = request_create(client); if (!req) { free(op->value); free(op); return 0; } memcpy(op->value, value, length); op->client = client; op->reliable = reliable; op->value_handle = value_handle; op->length = length; op->offset = offset; op->cur_length = MIN(length, bt_att_get_mtu(client->att) - 5); op->callback = callback; op->user_data = user_data; op->destroy = destroy; req->data = op; req->destroy = long_write_op_free; req->long_write = true; if (client->in_long_write || client->reliable_write_session_id > 0) { queue_push_tail(client->long_write_queue, req); return req->id; } pdu = malloc(op->cur_length + 4); if (!pdu) { free(op->value); free(op); return 0; } put_le16(value_handle, pdu); put_le16(offset, pdu + 2); memcpy(pdu + 4, op->value, op->cur_length); req->att_id = bt_att_send(client->att, BT_ATT_OP_PREP_WRITE_REQ, pdu, op->cur_length + 4, prepare_write_cb, req, request_unref); free(pdu); if (!req->att_id) { op->destroy = NULL; request_unref(req); return 0; } client->in_long_write = true; return req->id; } struct prep_write_op { bt_gatt_client_write_long_callback_t callback; void *user_data; bt_gatt_destroy_func_t destroy; uint8_t *pdu; uint16_t pdu_len; }; static void destroy_prep_write_op(void *data) { struct prep_write_op *op = data; if (op->destroy) op->destroy(op->user_data); free(op->pdu); free(op); } static void prep_write_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct request *req = user_data; struct prep_write_op *op = req->data; bool success; uint8_t att_ecode; bool reliable_error; if (opcode == BT_ATT_OP_ERROR_RSP) { success = false; reliable_error = false; att_ecode = process_error(pdu, length); goto done; } if (opcode != BT_ATT_OP_PREP_WRITE_RSP) { success = false; reliable_error = false; att_ecode = 0; goto done; } if (!pdu || length != op->pdu_len || memcmp(pdu, op->pdu, op->pdu_len)) { success = false; reliable_error = true; att_ecode = 0; goto done; } success = true; reliable_error = false; att_ecode = 0; done: if (op->callback) op->callback(success, reliable_error, att_ecode, op->user_data); } static struct request *get_reliable_request(struct bt_gatt_client *client, unsigned int id) { struct request *req; struct prep_write_op *op; op = new0(struct prep_write_op, 1); if (!op) return NULL; /* Following prepare writes */ if (id != 0) req = queue_find(client->pending_requests, match_req_id, UINT_TO_PTR(id)); else req = request_create(client); if (!req) { free(op); return NULL; } req->data = op; return req; } unsigned int bt_gatt_client_prepare_write(struct bt_gatt_client *client, unsigned int id, uint16_t value_handle, uint16_t offset, uint8_t *value, uint16_t length, bt_gatt_client_write_long_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { struct request *req; struct prep_write_op *op; uint8_t pdu[4 + length]; if (!client) return 0; if (client->in_long_write) return 0; /* * Make sure that client who owns reliable session continues with * prepare writes or this is brand new reliable session (id == 0) */ if (id != client->reliable_write_session_id) { util_debug(client->debug_callback, client->debug_data, "There is other reliable write session ongoing %u", client->reliable_write_session_id); return 0; } req = get_reliable_request(client, id); if (!req) return 0; op = (struct prep_write_op *)req->data; op->callback = callback; op->user_data = user_data; op->destroy = destroy; req->destroy = destroy_prep_write_op; req->prep_write = true; put_le16(value_handle, pdu); put_le16(offset, pdu + 2); memcpy(pdu + 4, value, length); /* * Before sending command we need to remember pdu as we need to validate * it in the response. Store handle, offset and value. Therefore * increase length by 4 (handle + offset) as we need it in couple places * below */ length += 4; op->pdu = malloc(length); if (!op->pdu) { op->destroy = NULL; request_unref(req); return 0; } memcpy(op->pdu, pdu, length); op->pdu_len = length; /* * Now we are ready to send command * Note that request_unref will be done on write execute */ req->att_id = bt_att_send(client->att, BT_ATT_OP_PREP_WRITE_REQ, pdu, sizeof(pdu), prep_write_cb, req, NULL); if (!req->att_id) { op->destroy = NULL; request_unref(req); return 0; } /* * Store first request id for prepare write and treat it as a session id * valid until write execute is done */ if (client->reliable_write_session_id == 0) client->reliable_write_session_id = req->id; return client->reliable_write_session_id; } static void exec_write_cb(uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct request *req = user_data; struct write_op *op = req->data; bool success; uint8_t att_ecode; if (opcode == BT_ATT_OP_ERROR_RSP) { success = false; att_ecode = process_error(pdu, length); goto done; } if (opcode != BT_ATT_OP_EXEC_WRITE_RSP || pdu || length) { success = false; att_ecode = 0; goto done; } success = true; att_ecode = 0; done: if (op->callback) op->callback(success, att_ecode, op->user_data); op->client->reliable_write_session_id = 0; start_next_long_write(op->client); } unsigned int bt_gatt_client_write_execute(struct bt_gatt_client *client, unsigned int id, bt_gatt_client_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { struct request *req; struct write_op *op; uint8_t pdu; if (!client) return 0; if (client->in_long_write) return 0; if (client->reliable_write_session_id != id) return 0; op = new0(struct write_op, 1); if (!op) return 0; req = queue_find(client->pending_requests, match_req_id, UINT_TO_PTR(id)); if (!req) { free(op); return 0; } op->client = client; op->callback = callback; op->user_data = user_data; op->destroy = destroy; pdu = 0x01; req->data = op; req->destroy = destroy_write_op; req->att_id = bt_att_send(client->att, BT_ATT_OP_EXEC_WRITE_REQ, &pdu, sizeof(pdu), exec_write_cb, req, request_unref); if (!req->att_id) { op->destroy = NULL; request_unref(req); return 0; } return id; } static bool match_notify_chrc_value_handle(const void *a, const void *b) { const struct notify_chrc *chrc = a; uint16_t value_handle = PTR_TO_UINT(b); return chrc->value_handle == value_handle; } unsigned int bt_gatt_client_register_notify(struct bt_gatt_client *client, uint16_t chrc_value_handle, bt_gatt_client_register_callback_t callback, bt_gatt_client_notify_callback_t notify, void *user_data, bt_gatt_client_destroy_func_t destroy) { struct notify_data *notify_data; struct notify_chrc *chrc = NULL; if (!client || !client->db || !chrc_value_handle || !callback) return 0; if (!bt_gatt_client_is_ready(client) || client->in_svc_chngd) return 0; /* Check if a characteristic ref count has been started already */ chrc = queue_find(client->notify_chrcs, match_notify_chrc_value_handle, UINT_TO_PTR(chrc_value_handle)); if (!chrc) { /* * Create an entry if the characteristic is known and has a CCC * descriptor. */ chrc = notify_chrc_create(client, chrc_value_handle); if (!chrc) return 0; } /* Fail if we've hit the maximum allowed notify sessions */ if (chrc->notify_count == INT_MAX) return 0; notify_data = new0(struct notify_data, 1); if (!notify_data) return 0; notify_data->client = client; notify_data->ref_count = 1; notify_data->chrc = chrc; notify_data->callback = callback; notify_data->notify = notify; notify_data->user_data = user_data; notify_data->destroy = destroy; /* Add the handler to the bt_gatt_client's general list */ queue_push_tail(client->notify_list, notify_data); /* Assign an ID to the handler. */ if (client->next_reg_id < 1) client->next_reg_id = 1; notify_data->id = client->next_reg_id++; /* * If a write to the CCC descriptor is in progress, then queue this * request. */ if (chrc->ccc_write_id) { queue_push_tail(chrc->reg_notify_queue, notify_data); return notify_data->id; } /* * If the ref count is not zero, then notifications are already enabled. */ if (chrc->notify_count > 0 || !chrc->ccc_handle) { complete_notify_request(notify_data); return notify_data->id; } /* Write to the CCC descriptor */ if (!notify_data_write_ccc(notify_data, true, enable_ccc_callback)) { queue_remove(client->notify_list, notify_data); free(notify_data); return 0; } return notify_data->id; } bool bt_gatt_client_unregister_notify(struct bt_gatt_client *client, unsigned int id) { struct notify_data *notify_data; if (!client || !id) return false; notify_data = queue_remove_if(client->notify_list, match_notify_data_id, UINT_TO_PTR(id)); if (!notify_data) return false; assert(notify_data->chrc->notify_count > 0); assert(!notify_data->chrc->ccc_write_id); complete_unregister_notify(notify_data); return true; } bool bt_gatt_client_set_sec_level(struct bt_gatt_client *client, int level) { if (!client) return false; return bt_att_set_sec_level(client->att, level); } int bt_gatt_client_get_sec_level(struct bt_gatt_client *client) { if (!client) return -1; return bt_att_get_sec_level(client->att); }