// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Google Inc. * * */ #ifdef HAVE_CONFIG_H #include #endif #include "src/shared/att.h" #include "lib/bluetooth.h" #include "lib/uuid.h" #include "src/shared/gatt-helpers.h" #include "src/shared/util.h" #include "src/shared/queue.h" #include "src/shared/gatt-db.h" #include "src/shared/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 #define DBG(_client, _format, arg...) \ gatt_log(_client, "[%p] %s:%s() " _format, _client, __FILE__, \ __func__, ## arg) struct ready_cb { bt_gatt_client_callback_t callback; bt_gatt_client_destroy_func_t destroy; void *data; }; struct idle_cb { bt_gatt_client_idle_callback_t callback; bt_gatt_client_destroy_func_t destroy; void *data; }; struct bt_gatt_client { struct bt_att *att; int ref_count; uint8_t features; struct bt_gatt_client *parent; struct queue *clones; struct queue *ready_cbs; struct queue *idle_cbs; 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, nfy_id, nfy_mult_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; if (!client->att) return NULL; req = new0(struct request, 1); 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 idle_destroy(void *data) { struct idle_cb *idle = data; if (idle->destroy) idle->destroy(idle->data); free(idle); } static bool idle_notify(const void *data, const void *user_data) { const struct idle_cb *idle = data; idle->callback(idle->data); return true; } static struct bt_gatt_client * bt_gatt_client_ref_safe(struct bt_gatt_client *client) { if (!client || !client->ref_count) return NULL; return bt_gatt_client_ref(client); } static void notify_client_idle(struct bt_gatt_client *client) { client = bt_gatt_client_ref_safe(client); if (!client) return; queue_remove_all(client->idle_cbs, idle_notify, NULL, idle_destroy); bt_gatt_client_unref(client); } static void request_unref(void *data) { struct request *req = data; struct bt_gatt_client *client = req->client; if (__sync_sub_and_fetch(&req->ref_count, 1)) return; if (req->destroy) req->destroy(req->data); if (!req->removed) { queue_remove(client->pending_requests, req); if (queue_isempty(client->pending_requests)) notify_client_idle(client); } free(req); } struct notify_chrc { struct bt_gatt_client *client; struct gatt_db_attribute *attr; uint16_t value_handle; uint16_t ccc_handle; uint16_t properties; unsigned int notify_id; 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; uint8_t att_ecode; 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 bool match_notify_chrc(const void *data, const void *user_data) { const struct notify_data *notify_data = data; const struct notify_chrc *chrc = user_data; return notify_data->chrc == chrc; } 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 notify_chrc_free(void *data) { struct notify_chrc *chrc = data; if (chrc->notify_id) gatt_db_attribute_unregister(chrc->attr, chrc->notify_id); queue_destroy(chrc->reg_notify_queue, notify_data_unref); free(chrc); } static void chrc_removed(struct gatt_db_attribute *attr, void *user_data) { struct notify_chrc *chrc = user_data; struct bt_gatt_client *client = chrc->client; struct notify_data *data; chrc->notify_id = 0; while ((data = queue_remove_if(client->notify_list, match_notify_chrc, chrc))) notify_data_cleanup(data); queue_remove(client->notify_chrcs, chrc); notify_chrc_free(chrc); } static struct notify_chrc *notify_chrc_create(struct bt_gatt_client *client, uint16_t handle) { struct gatt_db_attribute *attr, *ccc; struct notify_chrc *chrc; uint16_t value_handle; uint8_t properties; /* Check that there is an attribute with handle */ attr = gatt_db_get_attribute(client->db, handle); if (!attr) return NULL; if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, &properties, NULL, NULL)) return NULL; /* Check that there is an attribute with value_handle */ attr = gatt_db_get_attribute(client->db, value_handle); if (!attr) return NULL; chrc = new0(struct notify_chrc, 1); chrc->reg_notify_queue = queue_new(); if (!chrc->reg_notify_queue) { free(chrc); return NULL; } ccc = gatt_db_attribute_get_ccc(attr); if (ccc) chrc->ccc_handle = gatt_db_attribute_get_handle(ccc); chrc->client = client; chrc->attr = attr; chrc->value_handle = value_handle; chrc->properties = properties; chrc->notify_id = gatt_db_attribute_register(attr, chrc_removed, chrc, NULL); queue_push_tail(client->notify_chrcs, chrc); return 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; }; 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 *discov_ranges; struct queue *pending_svcs; struct queue *pending_chrcs; struct queue *ext_prop_desc; struct gatt_db_attribute *cur_svc; struct gatt_db_attribute *hash; uint8_t server_feat; bool success; uint16_t start; uint16_t end; uint16_t last; uint16_t svc_first; uint16_t svc_last; unsigned int db_id; 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) { if (op->db_id > 0) gatt_db_unregister(op->client->db, op->db_id); queue_destroy(op->discov_ranges, free); queue_destroy(op->pending_svcs, NULL); queue_destroy(op->pending_chrcs, free); queue_destroy(op->ext_prop_desc, NULL); free(op); } static bool read_db_hash(struct discovery_op *op); static void gatt_log_va(struct bt_gatt_client *client, const char *format, va_list va) { if (!client || !format) return; if (client->debug_callback) util_debug_va(client->debug_callback, client->debug_data, format, va); else gatt_log_va(client->parent, format, va); } static void gatt_log(struct bt_gatt_client *client, const char *format, ...) { va_list ap; if (!client || !format) return; va_start(ap, format); gatt_log_va(client, format, ap); va_end(ap); } static void discovery_op_complete(struct discovery_op *op, bool success, uint8_t err) { const struct queue_entry *svc; op->success = success; /* Read database hash if discovery has been successful */ if (success && read_db_hash(op)) return; /* * Unregister remove callback so it is not called when clearing unused * range. */ gatt_db_unregister(op->client->db, op->db_id); op->db_id = 0; /* Remove services pending */ for (svc = queue_get_entries(op->pending_svcs); svc; svc = svc->next) { struct gatt_db_attribute *attr = svc->data; uint16_t start, end; /* Leave active services if operation was aborted */ if ((!success && err == 0) && gatt_db_service_get_active(attr)) continue; gatt_db_attribute_get_service_data(attr, &start, &end, NULL, NULL); DBG(op->client, "service disappeared: start 0x%04x end 0x%04x", start, end); gatt_db_remove_service(op->client->db, attr); } /* Reset remaining range */ if (op->last != UINT16_MAX) gatt_db_clear_range(op->client->db, op->last + 1, UINT16_MAX); op->complete_func(op, success, err); } static void discovery_load_services(struct gatt_db_attribute *attr, void *user_data) { struct discovery_op *op = user_data; queue_push_tail(op->pending_svcs, attr); } static void discovery_service_changed(struct gatt_db_attribute *attr, void *user_data) { struct discovery_op *op = user_data; queue_remove(op->pending_svcs, attr); } 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; struct handle_range *range; op = new0(struct discovery_op, 1); op->discov_ranges = queue_new(); op->pending_svcs = queue_new(); op->pending_chrcs = queue_new(); op->ext_prop_desc = queue_new(); op->client = client; op->complete_func = complete_func; op->failure_func = failure_func; op->start = start; op->end = end; op->last = gatt_db_isempty(client->db) ? 0 : UINT16_MAX; op->svc_first = UINT16_MAX; op->svc_last = 0; /* Load existing services as pending */ gatt_db_foreach_service_in_range(client->db, NULL, discovery_load_services, op, start, end); /* * Services are only added when set active in which case they are no * longer pending so it is safe to remove either way. */ op->db_id = gatt_db_register(client->db, discovery_service_changed, discovery_service_changed, op, NULL); range = new0(struct handle_range, 1); range->start = start; range->end = end; queue_push_tail(op->discov_ranges, range); return op; } 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->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_remove_pending(struct discovery_op *op, struct gatt_db_attribute *attr) { struct gatt_db_attribute *svc; svc = gatt_db_attribute_get_service(attr); if (!svc) return; if (!queue_remove(op->pending_svcs, svc)) return; gatt_db_service_set_active(svc, true); if (op->cur_svc == svc) op->cur_svc = 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; uint16_t handle, start, end; uint128_t u128; bt_uuid_t uuid; char uuid_str[MAX_LEN_UUID_STR]; unsigned int includes_count, i; struct handle_range *range; discovery_req_clear(client); if (!success) { if (att_ecode == BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND) goto next; 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; DBG(client, "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)); DBG(client, "handle: 0x%04x, start: 0x%04x, end: 0x%04x," "uuid: %s", handle, start, end, uuid_str); attr = gatt_db_get_attribute(client->db, start); if (!attr) { DBG(client, "Unable to find attribute at 0x%04x: skipping", start); continue; } attr = gatt_db_insert_included(client->db, handle, attr); if (!attr) { DBG(client, "Unable to add include attribute at 0x%04x", handle); 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(attr) != handle) { DBG(client, "Invalid attribute 0x%04x expect it at 0x%04x", gatt_db_attribute_get_handle(attr), handle); goto failed; } if (!gatt_db_attribute_get_service_data(attr, NULL, &end, NULL, NULL)) { DBG(client, "Unable to get service data at 0x%04x", handle); goto failed; } /* Skip if there are no attributes */ if (handle == end) discover_remove_pending(op, attr); } next: range = queue_pop_head(op->discov_ranges); if (!range) { /* Skip if there are no attributes */ discover_remove_pending(op, op->cur_svc); goto failed; } client->discovery_req = bt_gatt_discover_characteristics(client->att, range->start, range->end, discover_chrcs_cb, discovery_op_ref(op), discovery_op_unref); free(range); if (client->discovery_req) return; DBG(client, "Failed to start characteristic discovery"); discovery_op_unref(op); failed: discovery_op_complete(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))) { struct gatt_db_attribute *svc; uint16_t start, end; /* Adjust current service */ svc = gatt_db_get_service(client->db, chrc_data->value_handle); if (op->cur_svc != svc) { if (op->cur_svc) { queue_remove(op->pending_svcs, op->cur_svc); /* Done with the current service */ gatt_db_service_set_active(op->cur_svc, true); } op->cur_svc = svc; } attr = gatt_db_insert_characteristic(client->db, chrc_data->value_handle, &chrc_data->uuid, 0, chrc_data->properties, NULL, NULL, NULL); if (!attr) { DBG(client, "Failed to insert characteristic at 0x%04x", chrc_data->value_handle); /* Some devices have been seen reporting orphaned * characteristics. In order to favor interoperability * we skip over characteristics in error */ free(chrc_data); continue; } if (gatt_db_attribute_get_handle(attr) != chrc_data->value_handle) goto failed; gatt_db_attribute_get_service_handles(svc, &start, &end); /* * Adjust end_handle in case the next chrc is not within the * same service. */ if (chrc_data->end_handle > end) chrc_data->end_handle = end; /* * check for descriptors presence, before initializing the * desc_handle and avoid integer overflow during desc_handle * initialization. */ if (chrc_data->value_handle >= chrc_data->end_handle) { free(chrc_data); continue; } desc_start = chrc_data->value_handle + 1; if (desc_start == chrc_data->end_handle && (chrc_data->properties & BT_GATT_CHRC_PROP_NOTIFY || chrc_data->properties & BT_GATT_CHRC_PROP_INDICATE)) { bt_uuid_t ccc_uuid; /* If there is only one descriptor that must be the CCC * in case either notify or indicate are supported. */ bt_uuid16_create(&ccc_uuid, GATT_CLIENT_CHARAC_CFG_UUID); attr = gatt_db_insert_descriptor(client->db, desc_start, &ccc_uuid, 0, NULL, NULL, NULL); if (attr) { free(chrc_data); continue; } } /* Check if the start range is within characteristic range */ 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; } DBG(client, "Failed to start descriptor discovery"); discovery_op_unref(op); goto failed; } /* Done with the current service */ discover_remove_pending(op, op->cur_svc); done: free(chrc_data); return true; failed: free(chrc_data); return false; } static void ext_prop_write_cb(struct gatt_db_attribute *attrib, int err, void *user_data) { struct bt_gatt_client *client = user_data; DBG(client, "Value set status: %d", err); } static void ext_prop_read_cb(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data); static bool read_ext_prop_desc(struct discovery_op *op) { struct bt_gatt_client *client = op->client; uint16_t handle; struct gatt_db_attribute *attr; attr = queue_peek_head(op->ext_prop_desc); if (!attr) return false; handle = gatt_db_attribute_get_handle(attr); if (!bt_gatt_client_read_value(client, handle, ext_prop_read_cb, discovery_op_ref(op), discovery_op_unref)) return false; return true; } static void ext_prop_read_cb(bool success, uint8_t att_ecode, const uint8_t *value, uint16_t length, void *user_data) { struct discovery_op *op = user_data; struct bt_gatt_client *client = op->client; bool discovering; struct gatt_db_attribute *desc_attr = NULL; if (!success) goto done; DBG(client, "Ext. prop value: 0x%04x", (uint16_t)value[0]); desc_attr = queue_pop_head(op->ext_prop_desc); if (!desc_attr) goto failed; if (!gatt_db_attribute_write(desc_attr, 0, value, length, 0, NULL, ext_prop_write_cb, client)) goto failed; /* Any other descriptor to read? */ if (read_ext_prop_desc(op)) return; if (!discover_descs(op, &discovering)) goto failed; if (discovering) return; goto done; failed: success = false; done: discovery_op_complete(op, success, att_ecode); } 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; uint128_t u128; bt_uuid_t uuid; char uuid_str[MAX_LEN_UUID_STR]; unsigned int desc_count; bool discovering; bt_uuid_t ext_prop_uuid; 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; DBG(client, "Descriptors found: %u", desc_count); bt_uuid16_create(&ext_prop_uuid, GATT_CHARAC_EXT_PROPER_UUID); 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)); DBG(client, "handle: 0x%04x, uuid: %s", handle, uuid_str); attr = gatt_db_insert_descriptor(client->db, handle, &uuid, 0, NULL, NULL, NULL); if (!attr) { attr = gatt_db_get_attribute(client->db, handle); if (attr && !bt_uuid_cmp(&uuid, gatt_db_attribute_get_type(attr))) continue; DBG(client, "Failed to insert descriptor at 0x%04x", handle); goto failed; } if (gatt_db_attribute_get_handle(attr) != handle) goto failed; if (!bt_uuid_cmp(&ext_prop_uuid, &uuid)) queue_push_tail(op->ext_prop_desc, attr); } /* If we got extended prop descriptor, lets read it right away */ if (read_ext_prop_desc(op)) return; next: if (!discover_descs(op, &discovering)) goto failed; if (discovering) return; goto done; failed: success = false; done: discovery_op_complete(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 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 (!result || !bt_gatt_iter_init(&iter, result)) goto failed; chrc_count = bt_gatt_result_characteristic_count(result); DBG(client, "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)); DBG(client, "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); 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); } next: /* * Before attempting to process discovered characteristics make sure we * discovered all missing ranges. */ if (queue_length(op->discov_ranges)) { struct handle_range *range; range = queue_peek_head(op->discov_ranges); if (!range) goto failed; client->discovery_req = bt_gatt_discover_included_services(client->att, range->start, range->end, discover_incl_cb, discovery_op_ref(op), discovery_op_unref); if (client->discovery_req) return; DBG(client, "Failed to start included services discovery"); discovery_op_unref(op); goto failed; } /* * 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; goto done; failed: success = false; done: discovery_op_complete(op, success, att_ecode); } static bool match_handle_range(const void *data, const void *match_data) { const struct handle_range *range = data; const struct handle_range *match_range = match_data; return (match_range->start >= range->start) && (match_range->start <= range->end); } static struct handle_range *range_new(uint16_t start, uint16_t end) { struct handle_range *range; if (!start || !end || start > end) return NULL; range = new0(struct handle_range, 1); range->start = start; range->end = end; return range; } static void remove_discov_range(struct discovery_op *op, uint16_t start, uint16_t end) { struct handle_range match_range; struct handle_range *range, *new_range; match_range.start = start; match_range.end = end; range = queue_find(op->discov_ranges, match_handle_range, &match_range); if (!range) return; if ((range->start == start) && (range->end == end)) { queue_remove(op->discov_ranges, range); free(range); } else if (range->start == start) { range->start = end + 1; if (!range->start || range->start > range->end) { queue_remove(op->discov_ranges, range); free(range); } } else if (range->end == end) range->end = start - 1; else { new_range = range_new(end + 1, range->end); if (new_range) queue_push_after(op->discov_ranges, range, new_range); range->end = start - 1; } } static void discovery_found_service(struct discovery_op *op, struct gatt_db_attribute *attr, uint16_t start, uint16_t end) { /* Skip if service already active */ if (!gatt_db_service_get_active(attr)) { /* Skip if there are no attributes */ if (end == start) gatt_db_service_set_active(attr, true); else queue_push_tail(op->pending_svcs, attr); if (start < op->svc_first) op->svc_first = start; if (end > op->svc_last) op->svc_last = end; } else { /* Remove from pending if active */ queue_remove(op->pending_svcs, attr); remove_discov_range(op, start, end); } /* Update last handle */ if (end > op->last) op->last = end; } static bool discovery_parse_services(struct discovery_op *op, bool primary, struct bt_gatt_iter *iter) { struct bt_gatt_client *client = op->client; struct gatt_db_attribute *attr; uint16_t start, end; uint128_t u128; bt_uuid_t uuid; char uuid_str[MAX_LEN_UUID_STR]; 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)); DBG(client, "start: 0x%04x, end: 0x%04x, uuid: %s", start, end, uuid_str); /* Store the service */ attr = gatt_db_insert_service(client->db, start, &uuid, primary, end - start + 1); if (!attr) { gatt_db_clear_range(client->db, start, end); attr = gatt_db_insert_service(client->db, start, &uuid, false, end - start + 1); if (!attr) { DBG(client, "Failed to store service"); return false; } /* Database has changed adjust last handle */ op->last = end; } /* Update pending list */ discovery_found_service(op, attr, start, end); } return true; } 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 handle_range *range; discovery_req_clear(client); if (!success) { switch (att_ecode) { case BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND: case BT_ATT_ERROR_UNSUPPORTED_GROUP_TYPE: success = true; att_ecode = 0; goto next; default: DBG(client, "Secondary service discovery failed." " ATT ECODE: 0x%02x", att_ecode); goto done; } } if (!result || !bt_gatt_iter_init(&iter, result)) { success = false; goto done; } DBG(client, "Secondary services found: %u", bt_gatt_result_service_count(result)); if (!discovery_parse_services(op, false, &iter)) { success = false; goto done; } next: if (queue_isempty(op->pending_svcs) || queue_isempty(op->discov_ranges)) goto done; if (op->svc_first > 0x0001) remove_discov_range(op, 1, op->svc_first - 1); if (op->svc_last < 0xffff) remove_discov_range(op, op->svc_last + 1, 0xffff); range = queue_peek_head(op->discov_ranges); client->discovery_req = bt_gatt_discover_included_services(client->att, range->start, range->end, discover_incl_cb, discovery_op_ref(op), discovery_op_unref); if (client->discovery_req) return; DBG(client, "Failed to start included services discovery"); discovery_op_unref(op); success = false; done: discovery_op_complete(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; discovery_req_clear(client); if (!success) { /* Reset error in case of not found */ switch (att_ecode) { case BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND: success = true; att_ecode = 0; goto secondary; default: DBG(client, "Primary service discovery failed." " ATT ECODE: 0x%02x", att_ecode); goto done; } } if (!result || !bt_gatt_iter_init(&iter, result)) { success = false; goto done; } DBG(client, "Primary services found: %u", bt_gatt_result_service_count(result)); if (!discovery_parse_services(op, true, &iter)) { success = false; goto done; } secondary: /* * Version 4.2 [Vol 1, Part A] page 101: * A secondary service is a service that provides auxiliary * functionality of a device and is referenced from at least one * primary service on the device. */ if (queue_isempty(op->pending_svcs)) goto done; /* 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; DBG(client, "Failed to start secondary service discovery"); discovery_op_unref(op); success = false; done: discovery_op_complete(op, success, att_ecode); } static void ready_destroy(void *data) { struct ready_cb *ready = data; if (ready->destroy) ready->destroy(ready->data); free(ready); } static void notify_client_ready(struct bt_gatt_client *client, bool success, uint8_t att_ecode) { const struct queue_entry *entry; client = bt_gatt_client_ref_safe(client); if (!client) return; if (client->ready) goto done; client->ready = success; if (client->parent) client->features = client->parent->features; for (entry = queue_get_entries(client->ready_cbs); entry; entry = entry->next) { struct ready_cb *ready = entry->data; ready->callback(success, att_ecode, ready->data); } queue_remove_all(client->ready_cbs, NULL, NULL, ready_destroy); /* Notify clones */ for (entry = queue_get_entries(client->clones); entry; entry = entry->next) { struct bt_gatt_client *clone = entry->data; notify_client_ready(clone, success, att_ecode); } done: bt_gatt_client_unref(client); } static void discover_all(struct discovery_op *op) { struct bt_gatt_client *client = op->client; 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; DBG(client, "Failed to initiate primary service discovery"); client->in_init = false; notify_client_ready(client, false, BT_ATT_ERROR_UNLIKELY); discovery_op_unref(op); } static void db_hash_write_value_cb(struct gatt_db_attribute *attrib, int err, void *user_data) { struct bt_gatt_client *client = user_data; DBG(client, "Value set status: %d", err); } static void db_hash_read_value_cb(struct gatt_db_attribute *attrib, int err, const uint8_t *value, size_t length, void *user_data) { const uint8_t **hash = user_data; if (err || (length != 16)) return; *hash = value; } static void db_hash_read_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; const uint8_t *hash = NULL, *value; uint16_t len, handle; struct bt_gatt_iter iter; if (!success) goto discover; bt_gatt_iter_init(&iter, result); bt_gatt_iter_next_read_by_type(&iter, &handle, &len, &value); DBG(client, "DB Hash found: handle 0x%04x length 0x%04x", handle, len); if (len != 16) goto discover; /* Read stored value in the db */ gatt_db_attribute_read(op->hash, 0, BT_ATT_OP_READ_REQ, NULL, db_hash_read_value_cb, &hash); /* Check if the has has changed since last time */ if (hash && !memcmp(hash, value, len)) { DBG(client, "DB Hash match: skipping discovery"); queue_remove_all(op->pending_svcs, NULL, NULL, NULL); discovery_op_complete(op, true, 0); return; } DBG(client, "DB Hash value:"); util_hexdump(' ', value, len, client->debug_callback, client->debug_data); /* Store ithe new hash in the db */ gatt_db_attribute_write(op->hash, 0, value, len, 0, NULL, db_hash_write_value_cb, client); discover: if (!op->success) { discover_all(op); return; } discovery_op_complete(op, true, 0); } 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 bool read_db_hash(struct discovery_op *op) { struct bt_gatt_client *client = op->client; bt_uuid_t uuid; /* Check if hash was already been read or there are more services to * process. */ if (op->hash || !queue_isempty(client->svc_chngd_queue)) return false; bt_uuid16_create(&uuid, GATT_CHARAC_DB_HASH); gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid, get_first_attribute, &op->hash); if (!op->hash) return false; if (!bt_gatt_read_by_type(client->att, 0x0001, 0xffff, &uuid, db_hash_read_cb, discovery_op_ref(op), discovery_op_unref)) { discovery_op_unref(op); return false; } return true; } static void db_server_feat_read(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; const uint8_t *value; uint16_t len, handle; struct bt_gatt_iter iter; if (!result) return; bt_gatt_iter_init(&iter, result); bt_gatt_iter_next_read_by_type(&iter, &handle, &len, &value); DBG(client, "Server Features found: handle 0x%04x " "length 0x%04x value 0x%02x", handle, len, value[0]); op->server_feat = value[0]; } static void server_feat_read_value(struct gatt_db_attribute *attrib, int err, const uint8_t *value, size_t length, void *user_data) { const uint8_t **feat = user_data; if (err) return; *feat = value; } static void read_server_feat(struct discovery_op *op) { struct bt_gatt_client *client = op->client; struct gatt_db_attribute *attr = NULL; const uint8_t *feat = NULL; bt_uuid_t uuid; bt_uuid16_create(&uuid, GATT_CHARAC_SERVER_FEAT); gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid, get_first_attribute, &attr); if (attr) { /* Read stored value in the db */ gatt_db_attribute_read(attr, 0, BT_ATT_OP_READ_REQ, NULL, server_feat_read_value, &feat); if (feat) return; } if (!bt_gatt_read_by_type(client->att, 0x0001, 0xffff, &uuid, db_server_feat_read, discovery_op_ref(op), discovery_op_unref)) discovery_op_unref(op); } 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) { DBG(client, "MTU Exchange failed. ATT ECODE: 0x%02x", att_ecode); /* * BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part G] page 546 * If the Error Response is sent by the server with the Error * Code set to RequestNot Supported , the Attribute Opcode is * not supported and the default MTU shall be used. */ if (att_ecode == BT_ATT_ERROR_REQUEST_NOT_SUPPORTED) goto discover; client->in_init = false; notify_client_ready(client, success, att_ecode); return; } DBG(client, "MTU exchange complete, with MTU: %u", bt_att_get_mtu(client->att)); discover: read_server_feat(op); if (read_db_hash(op)) { op->success = false; return; } discover_all(op); } struct service_changed_op { struct bt_gatt_client *client; uint16_t start_handle; uint16_t end_handle; }; 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 complete_notify_request(void *data) { struct notify_data *notify_data = data; notify_data->att_id = 0; if (notify_data->callback) notify_data->callback(notify_data->att_ecode, notify_data->user_data); } static bool notify_data_write_ccc(struct notify_data *notify_data, bool enable, bt_gatt_client_callback_t callback) { unsigned int att_id; uint16_t value; uint16_t properties = notify_data->chrc->properties; assert(notify_data->chrc->ccc_handle); if (enable) { /* Try to enable notifications or indications based on * whatever the characteristic supports. */ if (properties & BT_GATT_CHRC_PROP_NOTIFY) value = cpu_to_le16(0x0001); else if (properties & BT_GATT_CHRC_PROP_INDICATE) value = cpu_to_le16(0x0002); else return false; } att_id = bt_gatt_client_write_value(notify_data->client, notify_data->chrc->ccc_handle, (void *)&value, sizeof(value), 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 BT_ATT_ERROR_UNLIKELY; error_pdu = pdu; return error_pdu->ecode; } static bool notify_set_ecode(const void *data, const void *match_data) { struct notify_data *notify_data = (void *)data; uint8_t ecode = PTR_TO_UINT(match_data); notify_data->att_ecode = ecode; return true; } static void enable_ccc_callback(bool success, uint8_t att_ecode, void *user_data) { struct notify_data *notify_data = user_data; assert(notify_data->chrc->ccc_write_id); notify_data->chrc->ccc_write_id = 0; bt_gatt_client_ref_safe(notify_data->client); notify_data->att_ecode = att_ecode; /* Notify for all remaining requests. */ complete_notify_request(notify_data); queue_remove_all(notify_data->chrc->reg_notify_queue, notify_set_ecode, UINT_TO_PTR(notify_data->att_ecode), complete_notify_request); bt_gatt_client_unref(notify_data->client); } 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; } static unsigned int register_notify(struct bt_gatt_client *client, uint16_t 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; /* Check if a characteristic ref count has been started already */ chrc = queue_find(client->notify_chrcs, match_notify_chrc_value_handle, UINT_TO_PTR(handle)); if (!chrc) { /* * Create an entry if the characteristic is known and has a CCC * descriptor. */ chrc = notify_chrc_create(client, handle); if (!chrc) { DBG(client, "Unable to locate characteristic at 0x%04x", handle); 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); 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++; /* Increment the per-characteristic ref count of notify handlers */ __sync_fetch_and_add(¬ify_data->chrc->notify_count, 1); /* * 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 > 1, ccc handle cannot be found or registration * callback is not set consider notifications are already enabled. */ if (chrc->notify_count > 1 || !chrc->ccc_handle || !callback) { 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; } 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) { DBG(client, "Failed to register handler for \"Service Changed\""); success = false; client->svc_chngd_ind_id = 0; goto done; } client->svc_chngd_registered = true; success = true; DBG(client, "Registered handler for \"Service Changed\": %u", client->svc_chngd_ind_id); done: notify_client_ready(client, success, att_ecode); } static bool register_service_changed(struct bt_gatt_client *client) { bt_uuid_t uuid; struct gatt_db_attribute *attr = NULL; bt_uuid16_create(&uuid, SVC_CHNGD_UUID); if (client->svc_chngd_ind_id) return true; gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid, get_first_attribute, &attr); if (!attr) return true; /* * Register an indication handler for the "Service Changed" * characteristic and report ready only if the handler is registered * successfully. */ client->svc_chngd_ind_id = register_notify(client, gatt_db_attribute_get_handle(attr), service_changed_register_cb, service_changed_cb, client, NULL); return client->svc_chngd_ind_id ? true : false; } 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; const struct queue_entry *entry; client->in_svc_chngd = false; if (!success && att_ecode != BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND) { DBG(client, "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); /* Notify clones */ for (entry = queue_get_entries(client->clones); entry; entry = entry->next) { struct bt_gatt_client *clone = entry->data; if (clone->svc_chngd_callback) clone->svc_chngd_callback(start_handle, end_handle, clone->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; } if (register_service_changed(client)) return; DBG(client, "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; 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: DBG(client, "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) { DBG(client, "Service Changed received with invalid handles"); return; } DBG(client, "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); op->start_handle = start; op->end_handle = end; queue_push_tail(client->svc_chngd_queue, op); } static void server_feat_write_value(struct gatt_db_attribute *attrib, int err, void *user_data) { struct bt_gatt_client *client = user_data; DBG(client, "Server Features Value set status: %d", err); } static void write_server_features(struct bt_gatt_client *client, uint8_t feat) { bt_uuid_t uuid; struct gatt_db_attribute *attr = NULL; bt_uuid16_create(&uuid, GATT_CHARAC_SERVER_FEAT); gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid, get_first_attribute, &attr); if (!attr) return; /* Store value in the DB */ if (!gatt_db_attribute_write(attr, 0, &feat, sizeof(feat), 0, NULL, server_feat_write_value, client)) DBG(client, "Unable to store Server Features"); } static void write_client_features(struct bt_gatt_client *client) { bt_uuid_t uuid; struct gatt_db_attribute *attr = NULL; uint16_t handle; const uint8_t *feat = NULL; bt_uuid16_create(&uuid, GATT_CHARAC_CLI_FEAT); gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid, get_first_attribute, &attr); if (!attr) return; handle = gatt_db_attribute_get_handle(attr); client->features = BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING; bt_uuid16_create(&uuid, GATT_CHARAC_SERVER_FEAT); attr = NULL; gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid, get_first_attribute, &attr); if (attr) { /* Read stored value in the db */ gatt_db_attribute_read(attr, 0, BT_ATT_OP_READ_REQ, NULL, server_feat_read_value, &feat); if (feat && feat[0] & BT_GATT_CHRC_SERVER_FEAT_EATT) client->features |= BT_GATT_CHRC_CLI_FEAT_EATT; } client->features |= BT_GATT_CHRC_CLI_FEAT_NFY_MULTI; DBG(client, "Writing Client Features 0x%02x", client->features); bt_gatt_client_write_value(client, handle, &client->features, sizeof(client->features), NULL, NULL, NULL); } static void init_complete(struct discovery_op *op, bool success, uint8_t att_ecode) { struct bt_gatt_client *client = op->client; client->in_init = false; if (!success) goto fail; if (op->server_feat) write_server_features(client, op->server_feat); write_client_features(client); if (register_service_changed(client)) goto done; DBG(client, "Failed to register handler for \"Service Changed\""); success = false; fail: DBG(client, "Failed to initialize gatt-client"); op->success = false; done: notify_client_ready(client, success, att_ecode); } 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, NULL); if (!op) return false; /* * BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part G] page 546: * * 4.3.1 Exchange MTU * * This sub-procedure shall not be used on a BR/EDR physical link since * the MTU size is negotiated using L2CAP channel configuration * procedures. */ if (bt_att_get_link_type(client->att) == BT_ATT_BREDR) goto discover; /* Check if MTU needs to be send */ mtu = MAX(BT_ATT_DEFAULT_LE_MTU, mtu); if (mtu == BT_ATT_DEFAULT_LE_MTU) goto discover; /* Configure the MTU */ client->mtu_req_id = bt_gatt_exchange_mtu(client->att, 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; discover: read_server_feat(op); if (read_db_hash(op)) { op->success = false; goto done; } 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) { discovery_op_free(op); return false; } done: client->in_init = true; return true; } struct value_data { uint16_t handle; uint16_t len; const void *data; }; static void disable_ccc_callback(bool success, uint8_t att_ecode, void *user_data) { struct notify_data *notify_data = user_data; struct notify_data *next_data; 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(next_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); notify_data->att_id = 0; goto done; } if (__sync_sub_and_fetch(¬ify_data->chrc->notify_count, 1) || !notify_data->chrc->ccc_handle) goto done; notify_data_write_ccc(notify_data, false, disable_ccc_callback); done: notify_data_unref(notify_data); } static void notify_handler(void *data, void *user_data) { struct notify_data *notify_data = data; struct value_data *value_data = user_data; if (notify_data->chrc->value_handle != value_data->handle) return; /* * 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_data->handle, value_data->data, value_data->len, notify_data->user_data); } static void notify_cb(struct bt_att_chan *chan, uint8_t opcode, const void *pdu, uint16_t length, void *user_data) { struct bt_gatt_client *client = user_data; struct value_data data; if (queue_isempty(client->notify_list)) return; bt_gatt_client_ref(client); memset(&data, 0, sizeof(data)); if (opcode == BT_ATT_OP_HANDLE_NFY_MULT) { while (length >= 4) { data.handle = get_le16(pdu); length -= 2; pdu += 2; data.len = get_le16(pdu); length -= 2; pdu += 2; if (data.len > length) data.len = length; data.data = pdu; queue_foreach(client->notify_list, notify_handler, &data); length -= data.len; pdu += data.len; } } else { data.handle = get_le16(pdu); length -= 2; pdu += 2; data.len = length; data.data = pdu; queue_foreach(client->notify_list, notify_handler, &data); } if (opcode == BT_ATT_OP_HANDLE_IND && !client->parent) bt_att_chan_send(chan, BT_ATT_OP_HANDLE_CONF, NULL, 0, NULL, NULL, NULL); bt_gatt_client_unref(client); } static void bt_gatt_client_free(struct bt_gatt_client *client) { bt_gatt_client_cancel_all(client); queue_destroy(client->notify_chrcs, notify_chrc_free); queue_destroy(client->notify_list, notify_data_cleanup); queue_destroy(client->ready_cbs, ready_destroy); queue_destroy(client->idle_cbs, idle_destroy); 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->nfy_id); bt_att_unregister(client->att, client->nfy_mult_id); bt_att_unregister(client->att, client->ind_id); bt_att_unref(client->att); } gatt_db_unref(client->db); queue_destroy(client->clones, NULL); queue_destroy(client->svc_chngd_queue, free); queue_destroy(client->long_write_queue, request_unref); queue_destroy(client->pending_requests, request_unref); if (client->parent) { queue_remove(client->parent->clones, client); bt_gatt_client_unref(client->parent); } 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); } static struct bt_gatt_client *gatt_client_new(struct gatt_db *db, struct bt_att *att, uint8_t features) { struct bt_gatt_client *client; client = new0(struct bt_gatt_client, 1); client->disc_id = bt_att_register_disconnect(att, att_disconnect_cb, client, NULL); if (!client->disc_id) goto fail; client->clones = queue_new(); client->ready_cbs = queue_new(); client->idle_cbs = queue_new(); client->long_write_queue = queue_new(); client->svc_chngd_queue = queue_new(); client->notify_list = queue_new(); client->notify_chrcs = queue_new(); client->pending_requests = queue_new(); client->nfy_id = bt_att_register(att, BT_ATT_OP_HANDLE_NFY, notify_cb, client, NULL); if (!client->nfy_id) goto fail; client->nfy_mult_id = bt_att_register(att, BT_ATT_OP_HANDLE_NFY_MULT, notify_cb, client, NULL); if (!client->nfy_mult_id) goto fail; client->ind_id = bt_att_register(att, BT_ATT_OP_HANDLE_IND, notify_cb, client, NULL); if (!client->ind_id) goto fail; client->att = bt_att_ref(att); client->db = gatt_db_ref(db); client->features = features; return client; fail: bt_gatt_client_free(client); return NULL; } struct bt_gatt_client *bt_gatt_client_new(struct gatt_db *db, struct bt_att *att, uint16_t mtu, uint8_t features) { struct bt_gatt_client *client; if (!att || !db) return NULL; client = gatt_client_new(db, att, features); if (!client) return NULL; if (!gatt_client_init(client, mtu)) { bt_gatt_client_free(client); return NULL; } return bt_gatt_client_ref(client); } struct bt_gatt_client *bt_gatt_client_clone(struct bt_gatt_client *client) { struct bt_gatt_client *clone; if (!client) return NULL; clone = gatt_client_new(client->db, client->att, client->features); if (!clone) return NULL; queue_push_tail(client->clones, clone); /* * Reference the parent since the clones depend on it to propagate * service changed and ready callbacks. */ clone->parent = bt_gatt_client_ref(client); clone->ready = client->ready; return bt_gatt_client_ref(clone); } 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); } unsigned int bt_gatt_client_ready_register(struct bt_gatt_client *client, bt_gatt_client_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { struct ready_cb *ready; if (!client) return 0; ready = new0(struct ready_cb, 1); ready->callback = callback; ready->destroy = destroy; ready->data = user_data; queue_push_tail(client->ready_cbs, ready); return PTR_TO_UINT(ready); } bool bt_gatt_client_ready_unregister(struct bt_gatt_client *client, unsigned int id) { struct ready_cb *ready = UINT_TO_PTR(id); if (queue_remove(client->ready_cbs, ready)) { ready_destroy(ready); return true; } return false; } 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); } struct bt_att *bt_gatt_client_get_att(struct bt_gatt_client *client) { if (!client) return NULL; return client->att; } struct gatt_db *bt_gatt_client_get_db(struct bt_gatt_client *client) { if (!client || !client->db) return NULL; return client->db; } uint8_t bt_gatt_client_get_features(struct bt_gatt_client *client) { if (!client) return 0; if (client->parent) return client->parent->features; return client->features; } 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); } static bool cancel_request(struct request *req) { req->removed = true; if (req->long_write) return cancel_long_write_req(req->client, req); if (req->prep_write) return cancel_prep_write_session(req->client, req); return bt_att_cancel(req->client->att, req->att_id); } 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; return cancel_request(req); } static void cancel_pending(void *data) { cancel_request(data); } 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_pending); 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); 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 && opcode != BT_ATT_OP_READ_MULT_VL_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) return; if (opcode == BT_ATT_OP_READ_MULT_RSP || att_ecode) { op->callback(success, att_ecode, pdu, length, op->user_data); return; } if (length < 2) { op->callback(success, att_ecode, pdu, length, op->user_data); return; } /* Parse response */ while (length >= 2) { uint16_t len; len = get_le16(pdu); length -= 2; pdu += 2; /* The Length Value Tuple List may be truncated within the * first two octets of a tuple due to the size limits of the * current ATT_MTU. */ if (len > length) length = len; op->callback(success, att_ecode, pdu, len, 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 = newa(uint8_t, num_handles * 2); struct request *req; struct read_op *op; uint8_t opcode; 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); 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)); opcode = bt_gatt_client_get_features(client) & BT_GATT_CHRC_CLI_FEAT_EATT ? BT_ATT_OP_READ_MULT_VL_REQ : BT_ATT_OP_READ_MULT_REQ; req->att_id = bt_att_send(client->att, opcode, pdu, num_handles * 2, 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 ((!op->offset && opcode != BT_ATT_OP_READ_RSP) || (op->offset && 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]; int err; put_le16(op->value_handle, pdu); put_le16(op->offset, pdu + 2); err = bt_att_resend(op->client->att, req->att_id, BT_ATT_OP_READ_BLOB_REQ, pdu, sizeof(pdu), read_long_cb, request_ref(req), request_unref); if (!err) 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 att_op; uint8_t pdu[4]; uint16_t pdu_len; if (!client) return 0; op = new0(struct read_long_op, 1); 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); pdu_len = sizeof(value_handle); /* * Core v4.2, part F, section 1.3.4.4.5: * If the attribute value has a fixed length that is less than or equal * to (ATT_MTU - 3) octets in length, then an Error Response can be sent * with the error code «Attribute Not Long». * * To remove need for caller to handle "Attribute Not Long" error when * reading characteristics with short values, use Read Request for * reading first part of characteristics value instead of Read Blob * Request. Both are allowed in this case. */ if (op->offset) { att_op = BT_ATT_OP_READ_BLOB_REQ; pdu_len += sizeof(op->offset); put_le16(op->offset, pdu + 2); } else { att_op = BT_ATT_OP_READ_REQ; } req->att_id = bt_att_send(client->att, att_op, pdu, pdu_len, 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 = newa(uint8_t, 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_security(client->att, NULL); 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, 2 + length, 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 = newa(uint8_t, 2 + length); if (!client) return 0; op = new0(struct write_op, 1); 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, 2 + length, 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; int err; 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); err = bt_att_resend(op->client->att, req->att_id, BT_ATT_OP_PREP_WRITE_REQ, pdu, op->cur_length + 4, prepare_write_cb, request_ref(req), request_unref); if (err) { 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; bt_gatt_client_ref(op->client); if (op->callback) op->callback(success, op->reliable_error, att_ecode, op->user_data); start_next_long_write(op->client); bt_gatt_client_unref(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; int err; op->success = success; op->att_ecode = att_ecode; op->reliable_error = reliable_error; if (success) pdu = 0x01; /* Write */ else pdu = 0x00; /* Cancel */ err = bt_att_resend(op->client->att, req->att_id, BT_ATT_OP_EXEC_WRITE_REQ, &pdu, sizeof(pdu), execute_write_cb, request_ref(req), request_unref); if (!err) return; request_unref(req); success = false; bt_gatt_client_ref(op->client); if (op->callback) op->callback(success, reliable_error, att_ecode, op->user_data); start_next_long_write(op->client); bt_gatt_client_unref(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); 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); /* 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, 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 prep_write_op *op; uint8_t *pdu = newa(uint8_t, 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) { DBG(client, "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, length, 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); 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; } 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) { if (!client || !client->db || !chrc_value_handle || (!callback && !notify)) return 0; if (client->in_svc_chngd) return 0; return register_notify(client, chrc_value_handle, callback, notify, user_data, destroy); } 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; /* Remove data if it has been queued */ queue_remove(notify_data->chrc->reg_notify_queue, notify_data); /* Reset callbacks */ notify_data->callback = NULL; notify_data->notify = NULL; complete_unregister_notify(notify_data); return true; } bool bt_gatt_client_set_security(struct bt_gatt_client *client, int level) { if (!client) return false; return bt_att_set_security(client->att, level); } int bt_gatt_client_get_security(struct bt_gatt_client *client) { if (!client) return -1; return bt_att_get_security(client->att, NULL); } unsigned int bt_gatt_client_idle_register(struct bt_gatt_client *client, bt_gatt_client_idle_callback_t callback, void *user_data, bt_gatt_client_destroy_func_t destroy) { struct idle_cb *idle; if (!client) return 0; idle = new0(struct idle_cb, 1); idle->callback = callback; idle->destroy = destroy; idle->data = user_data; queue_push_tail(client->idle_cbs, idle); return PTR_TO_UINT(idle); } bool bt_gatt_client_idle_unregister(struct bt_gatt_client *client, unsigned int id) { struct idle_cb *idle = UINT_TO_PTR(id); if (!client || !id) return false; if (queue_remove(client->idle_cbs, idle)) { idle_destroy(idle); return true; } return false; }