| /* |
| * |
| * 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 "lib/uuid.h" |
| #include "src/shared/util.h" |
| #include "src/shared/queue.h" |
| #include "src/shared/gatt-db.h" |
| |
| #define MAX_CHAR_DECL_VALUE_LEN 19 |
| #define MAX_INCLUDED_VALUE_LEN 6 |
| |
| 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 { |
| uint16_t next_handle; |
| struct queue *services; |
| }; |
| |
| struct gatt_db_attribute { |
| 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; |
| }; |
| |
| struct gatt_db_service { |
| bool active; |
| uint16_t num_handles; |
| struct gatt_db_attribute **attributes; |
| }; |
| |
| static bool match_service_by_handle(const void *data, const void *user_data) |
| { |
| const struct gatt_db_service *service = data; |
| |
| return service->attributes[0]->handle == PTR_TO_INT(user_data); |
| } |
| |
| static struct gatt_db_attribute *new_attribute(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->uuid = *type; |
| attribute->value_len = len; |
| if (len) { |
| attribute->value = malloc0(len); |
| if (!attribute->value) { |
| free(attribute); |
| return NULL; |
| } |
| |
| memcpy(attribute->value, val, len); |
| } |
| |
| return attribute; |
| } |
| |
| static void attribute_destroy(struct gatt_db_attribute *attribute) |
| { |
| /* Attribute was not initialized by user */ |
| if (!attribute) |
| return; |
| |
| free(attribute->value); |
| free(attribute); |
| } |
| |
| 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->next_handle = 0x0001; |
| |
| return db; |
| } |
| |
| static void gatt_db_service_destroy(void *data) |
| { |
| struct gatt_db_service *service = data; |
| int i; |
| |
| for (i = 0; i < service->num_handles; i++) |
| attribute_destroy(service->attributes[i]); |
| |
| free(service->attributes); |
| free(service); |
| } |
| |
| void gatt_db_destroy(struct gatt_db *db) |
| { |
| queue_destroy(db->services, gatt_db_service_destroy); |
| free(db); |
| } |
| |
| 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); |
| } |
| |
| uint16_t gatt_db_add_service(struct gatt_db *db, 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 || (num_handles + db->next_handle) > UINT16_MAX) |
| return 0; |
| |
| service = new0(struct gatt_db_service, 1); |
| if (!service) |
| return 0; |
| |
| service->attributes = new0(struct gatt_db_attribute *, num_handles); |
| if (!service->attributes) { |
| free(service); |
| return 0; |
| } |
| |
| if (primary) |
| type = &primary_service_uuid; |
| else |
| type = &secondary_service_uuid; |
| |
| len = uuid_to_le(uuid, value); |
| |
| service->attributes[0] = new_attribute(type, value, len); |
| if (!service->attributes[0]) { |
| gatt_db_service_destroy(service); |
| return 0; |
| } |
| |
| if (!queue_push_tail(db->services, service)) { |
| gatt_db_service_destroy(service); |
| return 0; |
| } |
| |
| /* TODO now we get next handle from database. We should first look |
| * for 'holes' between existing services first, and assign next_handle |
| * only if enough space was not found. |
| */ |
| service->attributes[0]->handle = db->next_handle; |
| db->next_handle += num_handles; |
| service->num_handles = num_handles; |
| |
| return service->attributes[0]->handle; |
| } |
| |
| bool gatt_db_remove_service(struct gatt_db *db, uint16_t handle) |
| { |
| struct gatt_db_service *service; |
| |
| service = queue_remove_if(db->services, match_service_by_handle, |
| INT_TO_PTR(handle)); |
| if (!service) |
| return false; |
| |
| gatt_db_service_destroy(service); |
| |
| 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 uint16_t update_attribute_handle(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]->handle; |
| } |
| |
| 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; |
| } |
| |
| uint16_t gatt_db_add_characteristic(struct gatt_db *db, uint16_t handle, |
| 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) |
| { |
| uint8_t value[MAX_CHAR_DECL_VALUE_LEN]; |
| struct gatt_db_service *service; |
| uint16_t len = 0; |
| int i; |
| |
| service = queue_find(db->services, match_service_by_handle, |
| INT_TO_PTR(handle)); |
| if (!service) |
| return 0; |
| |
| i = get_attribute_index(service, 1); |
| if (!i) |
| return 0; |
| |
| 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(&characteristic_uuid, value, |
| len); |
| if (!service->attributes[i]) |
| return 0; |
| |
| update_attribute_handle(service, i++); |
| |
| service->attributes[i] = new_attribute(uuid, NULL, 0); |
| if (!service->attributes[i]) { |
| free(service->attributes[i - 1]); |
| return 0; |
| } |
| |
| set_attribute_data(service->attributes[i], read_func, write_func, |
| permissions, user_data); |
| |
| return update_attribute_handle(service, i); |
| } |
| |
| uint16_t gatt_db_add_char_descriptor(struct gatt_db *db, uint16_t handle, |
| 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; |
| |
| service = queue_find(db->services, match_service_by_handle, |
| INT_TO_PTR(handle)); |
| if (!service) |
| return 0; |
| |
| i = get_attribute_index(service, 0); |
| if (!i) |
| return 0; |
| |
| service->attributes[i] = new_attribute(uuid, NULL, 0); |
| if (!service->attributes[i]) |
| return 0; |
| |
| set_attribute_data(service->attributes[i], read_func, write_func, |
| permissions, user_data); |
| |
| return update_attribute_handle(service, i); |
| } |
| |
| uint16_t gatt_db_add_included_service(struct gatt_db *db, uint16_t handle, |
| uint16_t included_handle) |
| { |
| struct gatt_db_service *included_service; |
| uint8_t value[MAX_INCLUDED_VALUE_LEN]; |
| uint16_t len = 0; |
| struct gatt_db_service *service; |
| int index; |
| |
| service = queue_find(db->services, match_service_by_handle, |
| INT_TO_PTR(handle)); |
| if (!service) |
| return 0; |
| |
| included_service = queue_find(db->services, match_service_by_handle, |
| INT_TO_PTR(included_handle)); |
| |
| if (!included_service) |
| return 0; |
| |
| put_le16(included_handle, &value[len]); |
| len += sizeof(uint16_t); |
| |
| put_le16(included_handle + included_service->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 (included_service->attributes[0]->value_len == sizeof(uint16_t)) { |
| memcpy(&value[len], included_service->attributes[0]->value, |
| included_service->attributes[0]->value_len); |
| len += included_service->attributes[0]->value_len; |
| } |
| |
| index = get_attribute_index(service, 0); |
| if (!index) |
| return 0; |
| |
| service->attributes[index] = new_attribute(&included_service_uuid, |
| value, len); |
| if (!service->attributes[index]) |
| return 0; |
| |
| /* 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 update_attribute_handle(service, index); |
| } |
| |
| bool gatt_db_service_set_active(struct gatt_db *db, uint16_t handle, |
| bool active) |
| { |
| struct gatt_db_service *service; |
| |
| service = queue_find(db->services, match_service_by_handle, |
| INT_TO_PTR(handle)); |
| if (!service) |
| return false; |
| |
| service->active = active; |
| |
| return true; |
| } |
| |
| struct read_by_group_type_data { |
| struct queue *queue; |
| bt_uuid_t uuid; |
| uint16_t start_handle; |
| uint16_t end_handle; |
| uint16_t uuid_size; |
| bool stop_search; |
| }; |
| |
| static void read_by_group_type(void *data, void *user_data) |
| { |
| struct read_by_group_type_data *search_data = user_data; |
| struct gatt_db_service *service = data; |
| |
| if (!service->active) |
| return; |
| |
| /* Don't want more results as they have different size */ |
| if (search_data->stop_search) |
| return; |
| |
| if (bt_uuid_cmp(&search_data->uuid, &service->attributes[0]->uuid)) |
| return; |
| |
| if (service->attributes[0]->handle < search_data->start_handle) |
| return; |
| |
| /* Remember size of uuid */ |
| if (!search_data->uuid_size) { |
| search_data->uuid_size = service->attributes[0]->value_len; |
| } else if (search_data->uuid_size != |
| service->attributes[0]->value_len) { |
| /* Don't want more results. This is last */ |
| search_data->stop_search = true; |
| return; |
| } |
| |
| queue_push_tail(search_data->queue, |
| UINT_TO_PTR(service->attributes[0]->handle)); |
| } |
| |
| 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) |
| { |
| struct read_by_group_type_data data; |
| |
| data.uuid = type; |
| data.start_handle = start_handle; |
| data.end_handle = end_handle; |
| data.queue = queue; |
| data.uuid_size = 0; |
| data.stop_search = false; |
| |
| queue_foreach(db->services, read_by_group_type, &data); |
| } |
| |
| struct find_by_type_value_data { |
| struct queue *queue; |
| bt_uuid_t uuid; |
| uint16_t start_handle; |
| uint16_t end_handle; |
| }; |
| |
| 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; |
| |
| queue_push_tail(search_data->queue, |
| UINT_TO_PTR(attribute->handle)); |
| } |
| } |
| |
| void gatt_db_find_by_type(struct gatt_db *db, uint16_t start_handle, |
| uint16_t end_handle, |
| const bt_uuid_t *type, |
| struct queue *queue) |
| { |
| struct find_by_type_value_data data; |
| |
| data.uuid = *type; |
| data.start_handle = start_handle; |
| data.end_handle = end_handle; |
| data.queue = queue; |
| |
| queue_foreach(db->services, find_by_type, &data); |
| } |
| |
| 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, |
| UINT_TO_PTR(attribute->handle)); |
| } |
| } |
| |
| 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, |
| UINT_TO_PTR(attribute->handle)); |
| } |
| } |
| |
| 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); |
| } |
| |
| 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_INT(user_data); |
| uint16_t start, end; |
| |
| start = service->attributes[0]->handle; |
| end = start + service->num_handles; |
| |
| return (start <= handle) && (handle < end); |
| } |
| |
| bool gatt_db_read(struct gatt_db *db, uint16_t handle, uint16_t offset, |
| uint8_t att_opcode, bdaddr_t *bdaddr, |
| uint8_t **value, int *length) |
| { |
| struct gatt_db_service *service; |
| uint16_t service_handle; |
| struct gatt_db_attribute *a; |
| |
| if (!value || !length) |
| return false; |
| |
| service = queue_find(db->services, find_service_for_handle, |
| INT_TO_PTR(handle)); |
| if (!service) |
| return false; |
| |
| service_handle = service->attributes[0]->handle; |
| |
| a = service->attributes[handle - service_handle]; |
| if (!a) |
| return false; |
| |
| /* |
| * We call callback, and set length to -1, to notify user that callback |
| * has been called. Otherwise we set length to value length in database. |
| */ |
| if (a->read_func) { |
| *value = NULL; |
| *length = -1; |
| a->read_func(handle, offset, att_opcode, bdaddr, a->user_data); |
| } else { |
| if (offset > a->value_len) |
| return false; |
| |
| *value = &a->value[offset]; |
| *length = a->value_len - offset; |
| } |
| |
| return true; |
| } |
| |
| bool gatt_db_write(struct gatt_db *db, uint16_t handle, uint16_t offset, |
| const uint8_t *value, size_t len, |
| uint8_t att_opcode, bdaddr_t *bdaddr) |
| { |
| struct gatt_db_service *service; |
| uint16_t service_handle; |
| struct gatt_db_attribute *a; |
| |
| service = queue_find(db->services, find_service_for_handle, |
| INT_TO_PTR(handle)); |
| if (!service) |
| return false; |
| |
| service_handle = service->attributes[0]->handle; |
| |
| a = service->attributes[handle - service_handle]; |
| if (!a || !a->write_func) |
| return false; |
| |
| a->write_func(handle, offset, value, len, att_opcode, bdaddr, |
| a->user_data); |
| |
| return true; |
| } |
| |
| const bt_uuid_t *gatt_db_get_attribute_type(struct gatt_db *db, |
| uint16_t handle) |
| { |
| struct gatt_db_service *service; |
| struct gatt_db_attribute *attribute; |
| uint16_t service_handle; |
| |
| service = queue_find(db->services, find_service_for_handle, |
| INT_TO_PTR(handle)); |
| if (!service) |
| return NULL; |
| |
| service_handle = service->attributes[0]->handle; |
| |
| attribute = service->attributes[handle - service_handle]; |
| if (!attribute) |
| return NULL; |
| |
| return &attribute->uuid; |
| } |
| |
| uint16_t gatt_db_get_end_handle(struct gatt_db *db, uint16_t handle) |
| { |
| struct gatt_db_service *service; |
| |
| service = queue_find(db->services, find_service_for_handle, |
| INT_TO_PTR(handle)); |
| if (!service) |
| return 0; |
| |
| return service->attributes[0]->handle + service->num_handles - 1; |
| } |
| |
| bool gatt_db_get_attribute_permissions(struct gatt_db *db, uint16_t handle, |
| uint32_t *permissions) |
| { |
| struct gatt_db_attribute *attribute; |
| struct gatt_db_service *service; |
| uint16_t service_handle; |
| |
| service = queue_find(db->services, find_service_for_handle, |
| INT_TO_PTR(handle)); |
| if (!service) |
| return false; |
| |
| 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. |
| */ |
| attribute = service->attributes[handle - service_handle]; |
| if (!attribute) |
| return false; |
| |
| *permissions = attribute->permissions; |
| return true; |
| |
| } |