/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Intel Corporation. All rights reserved. * * * 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 #include #include #include "uuid.h" #include "util.h" #include "queue.h" #include "timeout.h" #include "att.h" #include "gatt-db.h" #ifndef MAX #define MAX(a, b) ((a) > (b) ? (a) : (b)) #endif #define MAX_CHAR_DECL_VALUE_LEN 19 #define MAX_INCLUDED_VALUE_LEN 6 #define ATTRIBUTE_TIMEOUT 5000 static const bt_uuid_t primary_service_uuid = { .type = BT_UUID16, .value.u16 = GATT_PRIM_SVC_UUID }; static const bt_uuid_t secondary_service_uuid = { .type = BT_UUID16, .value.u16 = GATT_SND_SVC_UUID }; static const bt_uuid_t characteristic_uuid = { .type = BT_UUID16, .value.u16 = GATT_CHARAC_UUID }; static const bt_uuid_t included_service_uuid = { .type = BT_UUID16, .value.u16 = GATT_INCLUDE_UUID }; struct gatt_db { int ref_count; uint16_t next_handle; struct queue *services; struct queue *notify_list; unsigned int next_notify_id; }; struct notify { unsigned int id; gatt_db_attribute_cb_t service_added; gatt_db_attribute_cb_t service_removed; gatt_db_destroy_func_t destroy; void *user_data; }; struct pending_read { struct gatt_db_attribute *attrib; unsigned int id; unsigned int timeout_id; gatt_db_attribute_read_t func; void *user_data; }; struct pending_write { struct gatt_db_attribute *attrib; unsigned int id; unsigned int timeout_id; gatt_db_attribute_write_t func; void *user_data; }; struct gatt_db_attribute { struct gatt_db_service *service; uint16_t handle; bt_uuid_t uuid; uint32_t permissions; uint16_t value_len; uint8_t *value; gatt_db_read_t read_func; gatt_db_write_t write_func; void *user_data; unsigned int read_id; struct queue *pending_reads; unsigned int write_id; struct queue *pending_writes; }; struct gatt_db_service { struct gatt_db *db; bool active; bool claimed; uint16_t num_handles; struct gatt_db_attribute **attributes; }; static void pending_read_result(struct pending_read *p, int err, const uint8_t *data, size_t length) { if (p->timeout_id > 0) timeout_remove(p->timeout_id); p->func(p->attrib, err, data, length, p->user_data); free(p); } static void pending_read_free(void *data) { struct pending_read *p = data; pending_read_result(p, -ECANCELED, NULL, 0); } static void pending_write_result(struct pending_write *p, int err) { if (p->timeout_id > 0) timeout_remove(p->timeout_id); p->func(p->attrib, err, p->user_data); free(p); } static void pending_write_free(void *data) { struct pending_write *p = data; pending_write_result(p, -ECANCELED); } static void attribute_destroy(struct gatt_db_attribute *attribute) { /* Attribute was not initialized by user */ if (!attribute) return; queue_destroy(attribute->pending_reads, pending_read_free); queue_destroy(attribute->pending_writes, pending_write_free); free(attribute->value); free(attribute); } static struct gatt_db_attribute *new_attribute(struct gatt_db_service *service, const bt_uuid_t *type, const uint8_t *val, uint16_t len) { struct gatt_db_attribute *attribute; attribute = new0(struct gatt_db_attribute, 1); if (!attribute) return NULL; attribute->service = service; attribute->uuid = *type; attribute->value_len = len; if (len) { attribute->value = malloc0(len); if (!attribute->value) goto failed; memcpy(attribute->value, val, len); } attribute->pending_reads = queue_new(); if (!attribute->pending_reads) goto failed; attribute->pending_writes = queue_new(); if (!attribute->pending_reads) goto failed; return attribute; failed: attribute_destroy(attribute); return NULL; } struct gatt_db *gatt_db_ref(struct gatt_db *db) { if (!db) return NULL; __sync_fetch_and_add(&db->ref_count, 1); return db; } struct gatt_db *gatt_db_new(void) { struct gatt_db *db; db = new0(struct gatt_db, 1); if (!db) return NULL; db->services = queue_new(); if (!db->services) { free(db); return NULL; } db->notify_list = queue_new(); if (!db->notify_list) { queue_destroy(db->services, NULL); free(db); return NULL; } db->next_handle = 0x0001; return gatt_db_ref(db); } static void notify_destroy(void *data) { struct notify *notify = data; if (notify->destroy) notify->destroy(notify->user_data); free(notify); } static bool match_notify_id(const void *a, const void *b) { const struct notify *notify = a; unsigned int id = PTR_TO_UINT(b); return notify->id == id; } struct notify_data { struct gatt_db_attribute *attr; bool added; }; static void handle_notify(void *data, void *user_data) { struct notify *notify = data; struct notify_data *notify_data = user_data; if (notify_data->added) notify->service_added(notify_data->attr, notify->user_data); else notify->service_removed(notify_data->attr, notify->user_data); } static void notify_service_changed(struct gatt_db *db, struct gatt_db_service *service, bool added) { struct notify_data data; if (queue_isempty(db->notify_list)) return; data.attr = service->attributes[0]; data.added = added; gatt_db_ref(db); queue_foreach(db->notify_list, handle_notify, &data); gatt_db_unref(db); } static void gatt_db_service_destroy(void *data) { struct gatt_db_service *service = data; int i; if (service->active) notify_service_changed(service->db, service, false); for (i = 0; i < service->num_handles; i++) attribute_destroy(service->attributes[i]); free(service->attributes); free(service); } static void gatt_db_destroy(struct gatt_db *db) { if (!db) return; /* * Clear the notify list before clearing the services to prevent the * latter from sending service_removed events. */ queue_destroy(db->notify_list, notify_destroy); db->notify_list = NULL; queue_destroy(db->services, gatt_db_service_destroy); free(db); } void gatt_db_unref(struct gatt_db *db) { if (!db) return; if (__sync_sub_and_fetch(&db->ref_count, 1)) return; gatt_db_destroy(db); } bool gatt_db_isempty(struct gatt_db *db) { if (!db) return true; return queue_isempty(db->services); } static int uuid_to_le(const bt_uuid_t *uuid, uint8_t *dst) { bt_uuid_t uuid128; if (uuid->type == BT_UUID16) { put_le16(uuid->value.u16, dst); return bt_uuid_len(uuid); } bt_uuid_to_uuid128(uuid, &uuid128); bswap_128(&uuid128.value.u128, dst); return bt_uuid_len(&uuid128); } static bool le_to_uuid(const uint8_t *src, size_t len, bt_uuid_t *uuid) { uint128_t u128; if (len == 2) { bt_uuid16_create(uuid, get_le16(src)); return true; } if (len == 4) { bt_uuid32_create(uuid, get_le32(src)); return true; } if (len != 16) return false; bswap_128(src, &u128); bt_uuid128_create(uuid, u128); return true; } static struct gatt_db_service *gatt_db_service_create(const bt_uuid_t *uuid, bool primary, uint16_t num_handles) { struct gatt_db_service *service; const bt_uuid_t *type; uint8_t value[16]; uint16_t len; if (num_handles < 1) return NULL; service = new0(struct gatt_db_service, 1); if (!service) return NULL; service->attributes = new0(struct gatt_db_attribute *, num_handles); if (!service->attributes) { free(service); return NULL; } if (primary) type = &primary_service_uuid; else type = &secondary_service_uuid; len = uuid_to_le(uuid, value); service->attributes[0] = new_attribute(service, type, value, len); if (!service->attributes[0]) { gatt_db_service_destroy(service); return NULL; } return service; } bool gatt_db_remove_service(struct gatt_db *db, struct gatt_db_attribute *attrib) { struct gatt_db_service *service; if (!db || !attrib) return false; service = attrib->service; queue_remove(db->services, service); gatt_db_service_destroy(service); return true; } bool gatt_db_clear(struct gatt_db *db) { if (!db) return false; queue_remove_all(db->services, NULL, NULL, gatt_db_service_destroy); db->next_handle = 0; return true; } static void gatt_db_service_get_handles(const struct gatt_db_service *service, uint16_t *start_handle, uint16_t *end_handle) { if (start_handle) *start_handle = service->attributes[0]->handle; if (end_handle) *end_handle = service->attributes[0]->handle + service->num_handles - 1; } struct clear_range { uint16_t start, end; }; static bool match_range(const void *a, const void *b) { const struct gatt_db_service *service = a; const struct clear_range *range = b; uint16_t svc_start, svc_end; gatt_db_service_get_handles(service, &svc_start, &svc_end); return svc_start <= range->end && svc_end >= range->start; } bool gatt_db_clear_range(struct gatt_db *db, uint16_t start_handle, uint16_t end_handle) { struct clear_range range; if (!db || start_handle > end_handle) return false; range.start = start_handle; range.end = end_handle; queue_remove_all(db->services, match_range, &range, gatt_db_service_destroy); return true; } static bool find_insert_loc(struct gatt_db *db, uint16_t start, uint16_t end, struct gatt_db_service **after) { const struct queue_entry *services_entry; struct gatt_db_service *service; uint16_t cur_start, cur_end; *after = NULL; services_entry = queue_get_entries(db->services); while (services_entry) { service = services_entry->data; gatt_db_service_get_handles(service, &cur_start, &cur_end); if (start >= cur_start && start <= cur_end) return false; if (end >= cur_start && end <= cur_end) return false; if (end < cur_start) return true; *after = service; services_entry = services_entry->next; } return true; } struct gatt_db_attribute *gatt_db_insert_service(struct gatt_db *db, uint16_t handle, const bt_uuid_t *uuid, bool primary, uint16_t num_handles) { struct gatt_db_service *service, *after; after = NULL; if (!db || handle < 1) return NULL; if (num_handles < 1 || (handle + num_handles - 1) > UINT16_MAX) return NULL; if (!find_insert_loc(db, handle, handle + num_handles - 1, &after)) return NULL; service = gatt_db_service_create(uuid, primary, num_handles); if (!service) return NULL; if (after) { if (!queue_push_after(db->services, after, service)) goto fail; } else if (!queue_push_head(db->services, service)) { goto fail; } service->db = db; service->attributes[0]->handle = handle; service->num_handles = num_handles; /* Fast-forward next_handle if the new service was added to the end */ db->next_handle = MAX(handle + num_handles, db->next_handle); return service->attributes[0]; fail: gatt_db_service_destroy(service); return NULL; } struct gatt_db_attribute *gatt_db_add_service(struct gatt_db *db, const bt_uuid_t *uuid, bool primary, uint16_t num_handles) { return gatt_db_insert_service(db, db->next_handle, uuid, primary, num_handles); } unsigned int gatt_db_register(struct gatt_db *db, gatt_db_attribute_cb_t service_added, gatt_db_attribute_cb_t service_removed, void *user_data, gatt_db_destroy_func_t destroy) { struct notify *notify; if (!db || !(service_added || service_removed)) return 0; notify = new0(struct notify, 1); if (!notify) return 0; notify->service_added = service_added; notify->service_removed = service_removed; notify->destroy = destroy; notify->user_data = user_data; if (db->next_notify_id < 1) db->next_notify_id = 1; notify->id = db->next_notify_id++; if (!queue_push_tail(db->notify_list, notify)) { free(notify); return 0; } return notify->id; } bool gatt_db_unregister(struct gatt_db *db, unsigned int id) { struct notify *notify; if (!db || !id) return false; notify = queue_find(db->notify_list, match_notify_id, UINT_TO_PTR(id)); if (!notify) return false; queue_remove(db->notify_list, notify); notify_destroy(notify); return true; } static uint16_t get_attribute_index(struct gatt_db_service *service, int end_offset) { int i = 0; /* Here we look for first free attribute index with given offset */ while (i < (service->num_handles - end_offset) && service->attributes[i]) i++; return i == (service->num_handles - end_offset) ? 0 : i; } static uint16_t get_handle_at_index(struct gatt_db_service *service, int index) { return service->attributes[index]->handle; } static struct gatt_db_attribute * attribute_update(struct gatt_db_service *service, int index) { uint16_t previous_handle; /* We call this function with index > 0, because index 0 is reserved * for service declaration, and is set in add_service() */ previous_handle = service->attributes[index - 1]->handle; service->attributes[index]->handle = previous_handle + 1; return service->attributes[index]; } static void set_attribute_data(struct gatt_db_attribute *attribute, gatt_db_read_t read_func, gatt_db_write_t write_func, uint32_t permissions, void *user_data) { attribute->permissions = permissions; attribute->read_func = read_func; attribute->write_func = write_func; attribute->user_data = user_data; } struct gatt_db_attribute * gatt_db_service_add_characteristic(struct gatt_db_attribute *attrib, const bt_uuid_t *uuid, uint32_t permissions, uint8_t properties, gatt_db_read_t read_func, gatt_db_write_t write_func, void *user_data) { struct gatt_db_service *service; uint8_t value[MAX_CHAR_DECL_VALUE_LEN]; uint16_t len = 0; int i; if (!attrib) return NULL; service = attrib->service; i = get_attribute_index(service, 1); if (!i) return NULL; value[0] = properties; len += sizeof(properties); /* We set handle of characteristic value, which will be added next */ put_le16(get_handle_at_index(service, i - 1) + 2, &value[1]); len += sizeof(uint16_t); len += uuid_to_le(uuid, &value[3]); service->attributes[i] = new_attribute(service, &characteristic_uuid, value, len); if (!service->attributes[i]) return NULL; attribute_update(service, i++); service->attributes[i] = new_attribute(service, uuid, NULL, 0); if (!service->attributes[i]) { free(service->attributes[i - 1]); return NULL; } set_attribute_data(service->attributes[i], read_func, write_func, permissions, user_data); return attribute_update(service, i); } struct gatt_db_attribute * gatt_db_service_add_descriptor(struct gatt_db_attribute *attrib, const bt_uuid_t *uuid, uint32_t permissions, gatt_db_read_t read_func, gatt_db_write_t write_func, void *user_data) { struct gatt_db_service *service; int i; if (!attrib) return NULL; service = attrib->service; i = get_attribute_index(service, 0); if (!i) return NULL; service->attributes[i] = new_attribute(service, uuid, NULL, 0); if (!service->attributes[i]) return NULL; set_attribute_data(service->attributes[i], read_func, write_func, permissions, user_data); return attribute_update(service, i); } struct gatt_db_attribute * gatt_db_service_add_included(struct gatt_db_attribute *attrib, struct gatt_db_attribute *include) { struct gatt_db_service *service, *included; uint8_t value[MAX_INCLUDED_VALUE_LEN]; uint16_t included_handle, len = 0; int index; if (!attrib || !include) return NULL; service = attrib->service; included = include->service; /* Adjust include to point to the first attribute */ if (include != included->attributes[0]) include = included->attributes[0]; included_handle = include->handle; put_le16(included_handle, &value[len]); len += sizeof(uint16_t); put_le16(included_handle + included->num_handles - 1, &value[len]); len += sizeof(uint16_t); /* The Service UUID shall only be present when the UUID is a 16-bit * Bluetooth UUID. Vol 2. Part G. 3.2 */ if (include->value_len == sizeof(uint16_t)) { memcpy(&value[len], include->value, include->value_len); len += include->value_len; } index = get_attribute_index(service, 0); if (!index) return NULL; service->attributes[index] = new_attribute(service, &included_service_uuid, value, len); if (!service->attributes[index]) return NULL; /* The Attribute Permissions shall be read only and not require * authentication or authorization. Vol 2. Part G. 3.2 * * TODO handle permissions */ set_attribute_data(service->attributes[index], NULL, NULL, 0, NULL); return attribute_update(service, index); } bool gatt_db_service_set_active(struct gatt_db_attribute *attrib, bool active) { struct gatt_db_service *service; if (!attrib) return false; service = attrib->service; if (service->active == active) return true; service->active = active; notify_service_changed(service->db, service, active); return true; } bool gatt_db_service_get_active(struct gatt_db_attribute *attrib) { if (!attrib) return false; return attrib->service->active; } bool gatt_db_service_set_claimed(struct gatt_db_attribute *attrib, bool claimed) { if (!attrib) return false; attrib->service->claimed = claimed; return true; } bool gatt_db_service_get_claimed(struct gatt_db_attribute *attrib) { if (!attrib) return false; return attrib->service->claimed; } void gatt_db_read_by_group_type(struct gatt_db *db, uint16_t start_handle, uint16_t end_handle, const bt_uuid_t type, struct queue *queue) { const struct queue_entry *services_entry; struct gatt_db_service *service; uint16_t grp_start, grp_end, uuid_size; uuid_size = 0; services_entry = queue_get_entries(db->services); while (services_entry) { service = services_entry->data; if (!service->active) goto next_service; if (bt_uuid_cmp(&type, &service->attributes[0]->uuid)) goto next_service; grp_start = service->attributes[0]->handle; grp_end = grp_start + service->num_handles - 1; if (grp_end < start_handle || grp_start > end_handle) goto next_service; if (grp_start < start_handle || grp_start > end_handle) goto next_service; if (!uuid_size) uuid_size = service->attributes[0]->value_len; else if (uuid_size != service->attributes[0]->value_len) return; queue_push_tail(queue, service->attributes[0]); next_service: services_entry = services_entry->next; } } struct find_by_type_value_data { bt_uuid_t uuid; uint16_t start_handle; uint16_t end_handle; gatt_db_attribute_cb_t func; void *user_data; const void *value; size_t value_len; unsigned int num_of_res; }; static void find_by_type(void *data, void *user_data) { struct find_by_type_value_data *search_data = user_data; struct gatt_db_service *service = data; struct gatt_db_attribute *attribute; int i; if (!service->active) return; for (i = 0; i < service->num_handles; i++) { attribute = service->attributes[i]; if (!attribute) continue; if ((attribute->handle < search_data->start_handle) || (attribute->handle > search_data->end_handle)) continue; if (bt_uuid_cmp(&search_data->uuid, &attribute->uuid)) continue; /* TODO: fix for read-callback based attributes */ if (search_data->value && memcmp(attribute->value, search_data->value, search_data->value_len)) continue; search_data->num_of_res++; search_data->func(attribute, search_data->user_data); } } unsigned int gatt_db_find_by_type(struct gatt_db *db, uint16_t start_handle, uint16_t end_handle, const bt_uuid_t *type, gatt_db_attribute_cb_t func, void *user_data) { struct find_by_type_value_data data; memset(&data, 0, sizeof(data)); data.uuid = *type; data.start_handle = start_handle; data.end_handle = end_handle; data.func = func; data.user_data = user_data; queue_foreach(db->services, find_by_type, &data); return data.num_of_res; } unsigned int gatt_db_find_by_type_value(struct gatt_db *db, uint16_t start_handle, uint16_t end_handle, const bt_uuid_t *type, const void *value, size_t value_len, gatt_db_attribute_cb_t func, void *user_data) { struct find_by_type_value_data data; data.uuid = *type; data.start_handle = start_handle; data.end_handle = end_handle; data.func = func; data.user_data = user_data; data.value = value; data.value_len = value_len; queue_foreach(db->services, find_by_type, &data); return data.num_of_res; } struct read_by_type_data { struct queue *queue; bt_uuid_t uuid; uint16_t start_handle; uint16_t end_handle; }; static void read_by_type(void *data, void *user_data) { struct read_by_type_data *search_data = user_data; struct gatt_db_service *service = data; struct gatt_db_attribute *attribute; int i; if (!service->active) return; for (i = 0; i < service->num_handles; i++) { attribute = service->attributes[i]; if (!attribute) continue; if (attribute->handle < search_data->start_handle) continue; if (attribute->handle > search_data->end_handle) return; if (bt_uuid_cmp(&search_data->uuid, &attribute->uuid)) continue; queue_push_tail(search_data->queue, attribute); } } void gatt_db_read_by_type(struct gatt_db *db, uint16_t start_handle, uint16_t end_handle, const bt_uuid_t type, struct queue *queue) { struct read_by_type_data data; data.uuid = type; data.start_handle = start_handle; data.end_handle = end_handle; data.queue = queue; queue_foreach(db->services, read_by_type, &data); } struct find_information_data { struct queue *queue; uint16_t start_handle; uint16_t end_handle; }; static void find_information(void *data, void *user_data) { struct find_information_data *search_data = user_data; struct gatt_db_service *service = data; struct gatt_db_attribute *attribute; int i; if (!service->active) return; /* Check if service is in range */ if ((service->attributes[0]->handle + service->num_handles - 1) < search_data->start_handle) return; for (i = 0; i < service->num_handles; i++) { attribute = service->attributes[i]; if (!attribute) continue; if (attribute->handle < search_data->start_handle) continue; if (attribute->handle > search_data->end_handle) return; queue_push_tail(search_data->queue, attribute); } } void gatt_db_find_information(struct gatt_db *db, uint16_t start_handle, uint16_t end_handle, struct queue *queue) { struct find_information_data data; data.start_handle = start_handle; data.end_handle = end_handle; data.queue = queue; queue_foreach(db->services, find_information, &data); } void gatt_db_foreach_service(struct gatt_db *db, const bt_uuid_t *uuid, gatt_db_attribute_cb_t func, void *user_data) { gatt_db_foreach_service_in_range(db, uuid, func, user_data, 0x0001, 0xffff); } struct foreach_data { gatt_db_attribute_cb_t func; const bt_uuid_t *uuid; void *user_data; uint16_t start, end; }; static void foreach_service_in_range(void *data, void *user_data) { struct gatt_db_service *service = data; struct foreach_data *foreach_data = user_data; uint16_t svc_start; bt_uuid_t uuid; svc_start = get_handle_at_index(service, 0); if (svc_start > foreach_data->end || svc_start < foreach_data->start) return; if (foreach_data->uuid) { gatt_db_attribute_get_service_uuid(service->attributes[0], &uuid); if (bt_uuid_cmp(&uuid, foreach_data->uuid)) return; } foreach_data->func(service->attributes[0], foreach_data->user_data); } void gatt_db_foreach_service_in_range(struct gatt_db *db, const bt_uuid_t *uuid, gatt_db_attribute_cb_t func, void *user_data, uint16_t start_handle, uint16_t end_handle) { struct foreach_data data; if (!db || !func || start_handle > end_handle) return; data.func = func; data.uuid = uuid; data.user_data = user_data; data.start = start_handle; data.end = end_handle; queue_foreach(db->services, foreach_service_in_range, &data); } void gatt_db_service_foreach(struct gatt_db_attribute *attrib, const bt_uuid_t *uuid, gatt_db_attribute_cb_t func, void *user_data) { struct gatt_db_service *service; struct gatt_db_attribute *attr; uint16_t i; if (!attrib || !func) return; service = attrib->service; for (i = 0; i < service->num_handles; i++) { attr = service->attributes[i]; if (!attr) continue; if (uuid && bt_uuid_cmp(uuid, &attr->uuid)) continue; func(attr, user_data); } } void gatt_db_service_foreach_char(struct gatt_db_attribute *attrib, gatt_db_attribute_cb_t func, void *user_data) { gatt_db_service_foreach(attrib, &characteristic_uuid, func, user_data); } void gatt_db_service_foreach_desc(struct gatt_db_attribute *attrib, gatt_db_attribute_cb_t func, void *user_data) { struct gatt_db_service *service; struct gatt_db_attribute *attr; uint16_t i; if (!attrib || !func) return; /* Return if this attribute is not a characteristic declaration */ if (bt_uuid_cmp(&characteristic_uuid, &attrib->uuid)) return; service = attrib->service; /* Start from the attribute following the value handle */ i = attrib->handle - service->attributes[0]->handle + 2; for (; i < service->num_handles; i++) { attr = service->attributes[i]; if (!attr) continue; /* Return if we reached the end of this characteristic */ if (!bt_uuid_cmp(&characteristic_uuid, &attr->uuid) || !bt_uuid_cmp(&included_service_uuid, &attr->uuid)) return; func(attr, user_data); } } void gatt_db_service_foreach_incl(struct gatt_db_attribute *attrib, gatt_db_attribute_cb_t func, void *user_data) { gatt_db_service_foreach(attrib, &included_service_uuid, func, user_data); } static bool find_service_for_handle(const void *data, const void *user_data) { const struct gatt_db_service *service = data; uint16_t handle = PTR_TO_UINT(user_data); uint16_t start, end; gatt_db_service_get_handles(service, &start, &end); return (start <= handle) && (handle <= end); } struct gatt_db_attribute *gatt_db_get_attribute(struct gatt_db *db, uint16_t handle) { struct gatt_db_service *service; uint16_t service_handle; if (!db || !handle) return NULL; service = queue_find(db->services, find_service_for_handle, UINT_TO_PTR(handle)); if (!service) return NULL; service_handle = service->attributes[0]->handle; /* * We can safely get attribute from attributes array with offset, * because find_service_for_handle() check if given handle is * in service range. */ return service->attributes[handle - service_handle]; } static bool find_service_with_uuid(const void *data, const void *user_data) { const struct gatt_db_service *service = data; const bt_uuid_t *uuid = user_data; bt_uuid_t svc_uuid; gatt_db_attribute_get_service_uuid(service->attributes[0], &svc_uuid); return bt_uuid_cmp(uuid, &svc_uuid) == 0; } struct gatt_db_attribute *gatt_db_get_service_with_uuid(struct gatt_db *db, const bt_uuid_t *uuid) { struct gatt_db_service *service; if (!db || !uuid) return NULL; service = queue_find(db->services, find_service_with_uuid, uuid); if (!service) return NULL; return service->attributes[0]; } const bt_uuid_t *gatt_db_attribute_get_type( const struct gatt_db_attribute *attrib) { if (!attrib) return NULL; return &attrib->uuid; } uint16_t gatt_db_attribute_get_handle(const struct gatt_db_attribute *attrib) { if (!attrib) return 0; return attrib->handle; } bool gatt_db_attribute_get_service_uuid(const struct gatt_db_attribute *attrib, bt_uuid_t *uuid) { struct gatt_db_service *service; if (!attrib || !uuid) return false; service = attrib->service; if (service->attributes[0]->value_len == sizeof(uint16_t)) { uint16_t value; value = get_le16(service->attributes[0]->value); bt_uuid16_create(uuid, value); return true; } if (service->attributes[0]->value_len == sizeof(uint128_t)) { uint128_t value; bswap_128(service->attributes[0]->value, &value); bt_uuid128_create(uuid, value); return true; } return false; } bool gatt_db_attribute_get_service_handles( const struct gatt_db_attribute *attrib, uint16_t *start_handle, uint16_t *end_handle) { struct gatt_db_service *service; if (!attrib) return false; service = attrib->service; gatt_db_service_get_handles(service, start_handle, end_handle); return true; } bool gatt_db_attribute_get_service_data(const struct gatt_db_attribute *attrib, uint16_t *start_handle, uint16_t *end_handle, bool *primary, bt_uuid_t *uuid) { struct gatt_db_service *service; struct gatt_db_attribute *decl; if (!attrib) return false; service = attrib->service; decl = service->attributes[0]; gatt_db_service_get_handles(service, start_handle, end_handle); if (primary) *primary = bt_uuid_cmp(&decl->uuid, &secondary_service_uuid); if (!uuid) return true; /* * The service declaration attribute value is the 16 or 128 bit service * UUID. */ return le_to_uuid(decl->value, decl->value_len, uuid); } bool gatt_db_attribute_get_char_data(const struct gatt_db_attribute *attrib, uint16_t *handle, uint16_t *value_handle, uint8_t *properties, bt_uuid_t *uuid) { if (!attrib) return false; if (bt_uuid_cmp(&characteristic_uuid, &attrib->uuid)) return false; /* * Characteristic declaration value: * 1 octet: Characteristic properties * 2 octets: Characteristic value handle * 2 or 16 octets: characteristic UUID */ if (!attrib->value || (attrib->value_len != 5 && attrib->value_len != 19)) return false; if (handle) *handle = attrib->handle; if (properties) *properties = attrib->value[0]; if (value_handle) *value_handle = get_le16(attrib->value + 1); if (!uuid) return true; return le_to_uuid(attrib->value + 3, attrib->value_len - 3, uuid); } bool gatt_db_attribute_get_incl_data(const struct gatt_db_attribute *attrib, uint16_t *handle, uint16_t *start_handle, uint16_t *end_handle) { if (!attrib) return false; if (bt_uuid_cmp(&included_service_uuid, &attrib->uuid)) return false; /* * Include definition value: * 2 octets: start handle of included service * 2 octets: end handle of included service * optional 2 octets: 16-bit Bluetooth UUID */ if (!attrib->value || attrib->value_len < 4 || attrib->value_len > 6) return false; /* * We only return the handles since the UUID can be easily obtained * from the corresponding attribute. */ if (handle) *handle = attrib->handle; if (start_handle) *start_handle = get_le16(attrib->value); if (end_handle) *end_handle = get_le16(attrib->value + 2); return true; } uint32_t gatt_db_attribute_get_permissions(const struct gatt_db_attribute *attrib) { if (!attrib) return 0; return attrib->permissions; } static bool read_timeout(void *user_data) { struct pending_read *p = user_data; p->timeout_id = 0; queue_remove(p->attrib->pending_reads, p); pending_read_result(p, -ETIMEDOUT, NULL, 0); return false; } bool gatt_db_attribute_read(struct gatt_db_attribute *attrib, uint16_t offset, uint8_t opcode, struct bt_att *att, gatt_db_attribute_read_t func, void *user_data) { uint8_t *value; if (!attrib || !func) return false; if (attrib->read_func) { struct pending_read *p; p = new0(struct pending_read, 1); if (!p) return false; p->attrib = attrib; p->id = ++attrib->read_id; p->timeout_id = timeout_add(ATTRIBUTE_TIMEOUT, read_timeout, p, NULL); p->func = func; p->user_data = user_data; queue_push_tail(attrib->pending_reads, p); attrib->read_func(attrib, p->id, offset, opcode, att, attrib->user_data); return true; } /* Check boundary if value is stored in the db */ if (offset > attrib->value_len) { func(attrib, BT_ATT_ERROR_INVALID_OFFSET, NULL, 0, user_data); return true; } /* Guard against invalid access if offset equals to value length */ value = offset == attrib->value_len ? NULL : &attrib->value[offset]; func(attrib, 0, value, attrib->value_len - offset, user_data); return true; } static bool find_pending(const void *a, const void *b) { const struct pending_read *p = a; unsigned int id = PTR_TO_UINT(b); return p->id == id; } bool gatt_db_attribute_read_result(struct gatt_db_attribute *attrib, unsigned int id, int err, const uint8_t *value, size_t length) { struct pending_read *p; if (!attrib || !id) return false; p = queue_remove_if(attrib->pending_reads, find_pending, UINT_TO_PTR(id)); if (!p) return false; pending_read_result(p, err, value, length); return true; } static bool write_timeout(void *user_data) { struct pending_write *p = user_data; p->timeout_id = 0; queue_remove(p->attrib->pending_writes, p); pending_write_result(p, -ETIMEDOUT); return false; } bool gatt_db_attribute_write(struct gatt_db_attribute *attrib, uint16_t offset, const uint8_t *value, size_t len, uint8_t opcode, struct bt_att *att, gatt_db_attribute_write_t func, void *user_data) { if (!attrib || !func) return false; if (attrib->write_func) { struct pending_write *p; p = new0(struct pending_write, 1); if (!p) return false; p->attrib = attrib; p->id = ++attrib->write_id; p->timeout_id = timeout_add(ATTRIBUTE_TIMEOUT, write_timeout, p, NULL); p->func = func; p->user_data = user_data; queue_push_tail(attrib->pending_writes, p); attrib->write_func(attrib, p->id, offset, value, len, opcode, att, attrib->user_data); return true; } /* Nothing to write just skip */ if (len == 0) goto done; /* For values stored in db allocate on demand */ if (!attrib->value || offset >= attrib->value_len || len > (unsigned) (attrib->value_len - offset)) { void *buf; buf = realloc(attrib->value, len + offset); if (!buf) return false; attrib->value = buf; /* Init data in the first allocation */ if (!attrib->value_len) memset(attrib->value, 0, offset); attrib->value_len = len + offset; } memcpy(&attrib->value[offset], value, len); done: func(attrib, 0, user_data); return true; } bool gatt_db_attribute_write_result(struct gatt_db_attribute *attrib, unsigned int id, int err) { struct pending_write *p; if (!attrib || !id) return false; p = queue_remove_if(attrib->pending_writes, find_pending, UINT_TO_PTR(id)); if (!p) return false; pending_write_result(p, err); return true; } bool gatt_db_attribute_reset(struct gatt_db_attribute *attrib) { if (!attrib) return false; if (!attrib->value || !attrib->value_len) return true; free(attrib->value); attrib->value = NULL; attrib->value_len = 0; return true; }