From d3307259473c22f60a602147d81415e33dc98cbc Mon Sep 17 00:00:00 2001 From: Arman Uguray Date: Wed, 11 Feb 2015 19:17:33 -0800 Subject: [PATCH] core: Introduce btd_gatt_database This patch introduces src/gatt-database.* which handles incoming ATT connections, manages per-adapter shared/gatt-db instances, and routes connections to the corresponding device object. This is the layer that will perform all the CCC management and Service Changed handling. --- Makefile.am | 1 + src/adapter.c | 3 + src/gatt-database.c | 680 ++++++++++++++++++++++++++++++++++++++++++++ src/gatt-database.h | 25 ++ 4 files changed, 709 insertions(+) create mode 100644 src/gatt-database.c create mode 100644 src/gatt-database.h diff --git a/Makefile.am b/Makefile.am index 6b811375b..dd8cda2e4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -167,6 +167,7 @@ src_bluetoothd_SOURCES = $(builtin_sources) \ src/sdpd-server.c src/sdpd-request.c \ src/sdpd-service.c src/sdpd-database.c \ src/attrib-server.h src/attrib-server.c \ + src/gatt-database.h src/gatt-database.c \ src/sdp-xml.h src/sdp-xml.c \ src/sdp-client.h src/sdp-client.c \ src/textfile.h src/textfile.c \ diff --git a/src/adapter.c b/src/adapter.c index 1839286e4..61f747d5c 100644 --- a/src/adapter.c +++ b/src/adapter.c @@ -302,6 +302,7 @@ static void dev_class_changed_callback(uint16_t index, uint16_t length, appearance[1] = rp->val[1] & 0x1f; /* removes service class */ appearance[2] = rp->val[2]; + /* TODO: Do this through btd_gatt_database instead */ attrib_gap_set(adapter, GATT_CHARAC_APPEARANCE, appearance, 2); } @@ -4014,6 +4015,7 @@ static void convert_sdp_entry(char *key, char *value, void *user_data) if (record_has_uuid(rec, att_uuid)) goto failed; + /* TODO: Do this through btd_gatt_database */ if (!gatt_parse_record(rec, &uuid, &psm, &start, &end)) goto failed; @@ -6590,6 +6592,7 @@ static int adapter_register(struct btd_adapter *adapter) agent_unref(agent); } + /* TODO: Migrate to use btd_gatt_database */ btd_adapter_gatt_server_start(adapter); load_config(adapter); diff --git a/src/gatt-database.c b/src/gatt-database.c new file mode 100644 index 000000000..c69de15b0 --- /dev/null +++ b/src/gatt-database.c @@ -0,0 +1,680 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 Google Inc. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "lib/uuid.h" +#include "btio/btio.h" +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "src/shared/att.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-server.h" +#include "log.h" +#include "adapter.h" +#include "device.h" +#include "gatt-database.h" + +#ifndef ATT_CID +#define ATT_CID 4 +#endif + +#define UUID_GAP 0x1800 +#define UUID_GATT 0x1801 + +struct btd_gatt_database { + struct btd_adapter *adapter; + struct gatt_db *db; + unsigned int db_id; + GIOChannel *le_io; + struct queue *device_states; + struct gatt_db_attribute *svc_chngd; + struct gatt_db_attribute *svc_chngd_ccc; +}; + +struct device_state { + bdaddr_t bdaddr; + uint8_t bdaddr_type; + struct queue *ccc_states; +}; + +struct ccc_state { + uint16_t handle; + uint8_t value[2]; +}; + +struct device_info { + bdaddr_t bdaddr; + uint8_t bdaddr_type; +}; + +static bool dev_state_match(const void *a, const void *b) +{ + const struct device_state *dev_state = a; + const struct device_info *dev_info = b; + + return bacmp(&dev_state->bdaddr, &dev_info->bdaddr) == 0 && + dev_state->bdaddr_type == dev_info->bdaddr_type; +} + +static struct device_state * +find_device_state(struct btd_gatt_database *database, bdaddr_t *bdaddr, + uint8_t bdaddr_type) +{ + struct device_info dev_info; + + memset(&dev_info, 0, sizeof(dev_info)); + + bacpy(&dev_info.bdaddr, bdaddr); + dev_info.bdaddr_type = bdaddr_type; + + return queue_find(database->device_states, dev_state_match, &dev_info); +} + +static bool ccc_state_match(const void *a, const void *b) +{ + const struct ccc_state *ccc = a; + uint16_t handle = PTR_TO_UINT(b); + + return ccc->handle == handle; +} + +static struct ccc_state *find_ccc_state(struct device_state *dev_state, + uint16_t handle) +{ + return queue_find(dev_state->ccc_states, ccc_state_match, + UINT_TO_PTR(handle)); +} + +static struct device_state *device_state_create(bdaddr_t *bdaddr, + uint8_t bdaddr_type) +{ + struct device_state *dev_state; + + dev_state = new0(struct device_state, 1); + if (!dev_state) + return NULL; + + dev_state->ccc_states = queue_new(); + if (!dev_state->ccc_states) { + free(dev_state); + return NULL; + } + + bacpy(&dev_state->bdaddr, bdaddr); + dev_state->bdaddr_type = bdaddr_type; + + return dev_state; +} + +static struct device_state *get_device_state(struct btd_gatt_database *database, + bdaddr_t *bdaddr, + uint8_t bdaddr_type) +{ + struct device_state *dev_state; + + /* + * Find and return a device state. If a matching state doesn't exist, + * then create a new one. + */ + dev_state = find_device_state(database, bdaddr, bdaddr_type); + if (dev_state) + return dev_state; + + dev_state = device_state_create(bdaddr, bdaddr_type); + if (!dev_state) + return NULL; + + queue_push_tail(database->device_states, dev_state); + + return dev_state; +} + +static struct ccc_state *get_ccc_state(struct btd_gatt_database *database, + bdaddr_t *bdaddr, + uint8_t bdaddr_type, + uint16_t handle) +{ + struct device_state *dev_state; + struct ccc_state *ccc; + + dev_state = get_device_state(database, bdaddr, bdaddr_type); + if (!dev_state) + return NULL; + + ccc = find_ccc_state(dev_state, handle); + if (ccc) + return ccc; + + ccc = new0(struct ccc_state, 1); + if (!ccc) + return NULL; + + ccc->handle = handle; + queue_push_tail(dev_state->ccc_states, ccc); + + return ccc; +} + +static void device_state_free(void *data) +{ + struct device_state *state = data; + + queue_destroy(state->ccc_states, free); + free(state); +} + +static void gatt_database_free(void *data) +{ + struct btd_gatt_database *database = data; + + if (database->le_io) { + g_io_channel_shutdown(database->le_io, FALSE, NULL); + g_io_channel_unref(database->le_io); + } + + /* TODO: Persistently store CCC states before freeing them */ + queue_destroy(database->device_states, device_state_free); + gatt_db_unregister(database->db, database->db_id); + gatt_db_unref(database->db); + btd_adapter_unref(database->adapter); + free(database); +} + +static void connect_cb(GIOChannel *io, GError *gerr, gpointer user_data) +{ + struct btd_adapter *adapter; + struct btd_device *device; + uint8_t dst_type; + bdaddr_t src, dst; + + DBG("New incoming LE ATT connection"); + + if (gerr) { + error("%s", gerr->message); + return; + } + + bt_io_get(io, &gerr, BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_DEST_TYPE, &dst_type, + BT_IO_OPT_INVALID); + if (gerr) { + error("bt_io_get: %s", gerr->message); + g_error_free(gerr); + return; + } + + adapter = adapter_find(&src); + if (!adapter) + return; + + device = btd_adapter_get_device(adapter, &dst, dst_type); + if (!device) + return; + + /* TODO: create bt_gatt_server instance */ + + device_attach_att(device, io); +} + +static void gap_device_name_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct btd_gatt_database *database = user_data; + uint8_t error = 0; + size_t len = 0; + const uint8_t *value = NULL; + const char *device_name; + + DBG("GAP Device Name read request\n"); + + device_name = btd_adapter_get_name(database->adapter); + len = strlen(device_name); + + if (offset > len) { + error = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + len -= offset; + value = len ? (const uint8_t *) &device_name[offset] : NULL; + +done: + gatt_db_attribute_read_result(attrib, id, error, value, len); +} + +static void gap_appearance_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct btd_gatt_database *database = user_data; + uint8_t error = 0; + size_t len = 2; + const uint8_t *value = NULL; + uint8_t appearance[2]; + uint32_t dev_class; + + DBG("GAP Appearance read request\n"); + + dev_class = btd_adapter_get_class(database->adapter); + + if (offset > 2) { + error = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + appearance[0] = dev_class & 0x00ff; + appearance[1] = (dev_class >> 8) & 0x001f; + + len -= offset; + value = len ? &appearance[offset] : NULL; + +done: + gatt_db_attribute_read_result(attrib, id, error, value, len); +} + +static void populate_gap_service(struct btd_gatt_database *database) +{ + bt_uuid_t uuid; + struct gatt_db_attribute *service; + + /* Add the GAP service */ + bt_uuid16_create(&uuid, UUID_GAP); + service = gatt_db_add_service(database->db, &uuid, true, 5); + + /* + * Device Name characteristic. + */ + bt_uuid16_create(&uuid, GATT_CHARAC_DEVICE_NAME); + gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ, + gap_device_name_read_cb, + NULL, database); + + /* + * Device Appearance characteristic. + */ + bt_uuid16_create(&uuid, GATT_CHARAC_APPEARANCE); + gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ, + gap_appearance_read_cb, + NULL, database); + + gatt_db_service_set_active(service, true); +} + +static bool get_dst_info(struct bt_att *att, bdaddr_t *dst, uint8_t *dst_type) +{ + GIOChannel *io = NULL; + GError *gerr = NULL; + + io = g_io_channel_unix_new(bt_att_get_fd(att)); + if (!io) + return false; + + bt_io_get(io, &gerr, BT_IO_OPT_DEST_BDADDR, dst, + BT_IO_OPT_DEST_TYPE, dst_type, + BT_IO_OPT_INVALID); + if (gerr) { + error("gatt: bt_io_get: %s", gerr->message); + g_error_free(gerr); + g_io_channel_unref(io); + return false; + } + + g_io_channel_unref(io); + return true; +} + +static void gatt_ccc_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct btd_gatt_database *database = user_data; + struct ccc_state *ccc; + uint16_t handle; + uint8_t ecode = 0; + const uint8_t *value = NULL; + size_t len = 0; + bdaddr_t bdaddr; + uint8_t bdaddr_type; + + handle = gatt_db_attribute_get_handle(attrib); + + DBG("CCC read called for handle: 0x%04x", handle); + + if (offset > 2) { + ecode = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + if (!get_dst_info(att, &bdaddr, &bdaddr_type)) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto done; + } + + ccc = get_ccc_state(database, &bdaddr, bdaddr_type, handle); + if (!ccc) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto done; + } + + len -= offset; + value = len ? &ccc->value[offset] : NULL; + +done: + gatt_db_attribute_read_result(attrib, id, ecode, value, len); +} + +static void gatt_ccc_write_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct btd_gatt_database *database = user_data; + struct ccc_state *ccc; + uint16_t handle; + uint8_t ecode = 0; + bdaddr_t bdaddr; + uint8_t bdaddr_type; + + handle = gatt_db_attribute_get_handle(attrib); + + DBG("CCC read called for handle: 0x%04x", handle); + + if (!value || len != 2) { + ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; + goto done; + } + + if (offset > 2) { + ecode = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + if (!get_dst_info(att, &bdaddr, &bdaddr_type)) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto done; + } + + ccc = get_ccc_state(database, &bdaddr, bdaddr_type, handle); + if (!ccc) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto done; + } + + /* + * TODO: Perform this after checking with a callback to the upper + * layer. + */ + ccc->value[0] = value[0]; + ccc->value[1] = value[1]; + +done: + gatt_db_attribute_write_result(attrib, id, ecode); +} + +static struct gatt_db_attribute * +gatt_database_add_ccc(struct btd_gatt_database *database, + uint16_t service_handle) +{ + struct gatt_db_attribute *service; + bt_uuid_t uuid; + + if (!database || !service_handle) + return NULL; + + service = gatt_db_get_attribute(database->db, service_handle); + if (!service) { + error("No service exists with handle: 0x%04x", service_handle); + return NULL; + } + + bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID); + return gatt_db_service_add_descriptor(service, &uuid, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, + gatt_ccc_read_cb, gatt_ccc_write_cb, database); +} + +static void populate_gatt_service(struct btd_gatt_database *database) +{ + bt_uuid_t uuid; + struct gatt_db_attribute *service; + uint16_t start_handle; + + /* Add the GATT service */ + bt_uuid16_create(&uuid, UUID_GATT); + service = gatt_db_add_service(database->db, &uuid, true, 4); + gatt_db_attribute_get_service_handles(service, &start_handle, NULL); + + bt_uuid16_create(&uuid, GATT_CHARAC_SERVICE_CHANGED); + database->svc_chngd = gatt_db_service_add_characteristic(service, &uuid, + BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_INDICATE, + NULL, NULL, database); + + database->svc_chngd_ccc = gatt_database_add_ccc(database, start_handle); + + gatt_db_service_set_active(service, true); +} + +static void register_core_services(struct btd_gatt_database *database) +{ + populate_gap_service(database); + populate_gatt_service(database); +} + +struct not_data { + struct btd_gatt_database *database; + uint16_t handle, ccc_handle; + const uint8_t *value; + uint16_t len; + bool indicate; +}; + +static void send_notification_to_device(void *data, void *user_data) +{ + struct device_state *device_state = data; + struct not_data *not_data = user_data; + struct ccc_state *ccc; + struct btd_device *device; + + ccc = find_ccc_state(device_state, not_data->ccc_handle); + if (!ccc) + return; + + if (!ccc->value[0] || (not_data->indicate && !(ccc->value[0] & 0x02))) + return; + + device = btd_adapter_get_device(not_data->database->adapter, + &device_state->bdaddr, + device_state->bdaddr_type); + if (!device) + return; + + /* + * TODO: Notify device via bt_gatt_server + */ +} + +static void send_notification_to_devices(struct btd_gatt_database *database, + uint16_t handle, const uint8_t *value, + uint16_t len, uint16_t ccc_handle, + bool indicate) +{ + struct not_data not_data; + + memset(¬_data, 0, sizeof(not_data)); + + not_data.database = database; + not_data.handle = handle; + not_data.ccc_handle = ccc_handle; + not_data.value = value; + not_data.len = len; + not_data.indicate = indicate; + + queue_foreach(database->device_states, send_notification_to_device, + ¬_data); +} + +static void send_service_changed(struct btd_gatt_database *database, + struct gatt_db_attribute *attrib) +{ + uint16_t start, end; + uint8_t value[4]; + uint16_t handle, ccc_handle; + + if (!gatt_db_attribute_get_service_handles(attrib, &start, &end)) { + error("Failed to obtain changed service handles"); + return; + } + + handle = gatt_db_attribute_get_handle(database->svc_chngd); + ccc_handle = gatt_db_attribute_get_handle(database->svc_chngd_ccc); + + if (!handle || !ccc_handle) { + error("Failed to obtain handles for \"Service Changed\"" + " characteristic"); + return; + } + + put_le16(start, value); + put_le16(end, value + 2); + + send_notification_to_devices(database, handle, value, sizeof(value), + ccc_handle, true); +} + +static void gatt_db_service_added(struct gatt_db_attribute *attrib, + void *user_data) +{ + struct btd_gatt_database *database = user_data; + + DBG("GATT Service added to local database"); + + send_service_changed(database, attrib); +} + +static bool ccc_match_service(const void *data, const void *match_data) +{ + const struct ccc_state *ccc = data; + const struct gatt_db_attribute *attrib = match_data; + uint16_t start, end; + + if (!gatt_db_attribute_get_service_handles(attrib, &start, &end)) + return false; + + return ccc->handle >= start && ccc->handle <= end; +} + +static void remove_device_ccc(void *data, void *user_data) +{ + struct device_state *state = data; + + queue_remove_all(state->ccc_states, ccc_match_service, user_data, free); +} + +static void gatt_db_service_removed(struct gatt_db_attribute *attrib, + void *user_data) +{ + struct btd_gatt_database *database = user_data; + + DBG("Local GATT service removed"); + + send_service_changed(database, attrib); + + queue_foreach(database->device_states, remove_device_ccc, attrib); +} + +struct btd_gatt_database *btd_gatt_database_new(struct btd_adapter *adapter) +{ + struct btd_gatt_database *database; + GError *gerr = NULL; + const bdaddr_t *addr; + + if (!adapter) + return NULL; + + database = new0(struct btd_gatt_database, 1); + if (!database) + return NULL; + + database->adapter = btd_adapter_ref(adapter); + database->db = gatt_db_new(); + if (!database->db) + goto fail; + + database->device_states = queue_new(); + if (!database->device_states) + goto fail; + + database->db_id = gatt_db_register(database->db, gatt_db_service_added, + gatt_db_service_removed, + database, NULL); + if (!database->db_id) + goto fail; + + addr = btd_adapter_get_address(adapter); + database->le_io = bt_io_listen(connect_cb, NULL, NULL, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, addr, + BT_IO_OPT_SOURCE_TYPE, BDADDR_LE_PUBLIC, + BT_IO_OPT_CID, ATT_CID, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + if (!database->le_io) { + error("Failed to start listening: %s", gerr->message); + g_error_free(gerr); + goto fail; + } + + register_core_services(database); + + return database; + +fail: + gatt_database_free(database); + + return NULL; +} + +void btd_gatt_database_destroy(struct btd_gatt_database *database) +{ + if (!database) + return; + + gatt_database_free(database); +} + +struct gatt_db *btd_gatt_database_get_db(struct btd_gatt_database *database) +{ + if (!database) + return NULL; + + return database->db; +} diff --git a/src/gatt-database.h b/src/gatt-database.h new file mode 100644 index 000000000..0d9106b11 --- /dev/null +++ b/src/gatt-database.h @@ -0,0 +1,25 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 Google Inc. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + */ + +struct btd_gatt_database; + +struct btd_gatt_database *btd_gatt_database_new(struct btd_adapter *adapter); +void btd_gatt_database_destroy(struct btd_gatt_database *database); + +struct gatt_db *btd_gatt_database_get_db(struct btd_gatt_database *database); -- 2.47.3