diff options
Diffstat (limited to 'bluez/gatt-db.c')
-rw-r--r-- | bluez/gatt-db.c | 1636 |
1 files changed, 1636 insertions, 0 deletions
diff --git a/bluez/gatt-db.c b/bluez/gatt-db.c new file mode 100644 index 0000000..29dda55 --- /dev/null +++ b/bluez/gatt-db.c @@ -0,0 +1,1636 @@ +/* + * + * 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 <stdbool.h> +#include <errno.h> + +#include <bluetooth/bluetooth.h> +#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; +} |