blob: 9cbf409fbfb6770a8bef18e45126904e85bef67e [file] [log] [blame]
/*
*
* 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;
}