Diff between 66b407e1f245ef1c05a53612ce2ae3b4e710230a and d3307259473c22f60a602147d81415e33dc98cbc

Changed Files

File Additions Deletions Status
Makefile.am +1 -0 modified
src/adapter.c +3 -0 modified
src/gatt-database.c +680 -0 added
src/gatt-database.h +25 -0 added

Full Patch

diff --git a/Makefile.am b/Makefile.am
index 6b81137..dd8cda2 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 1839286..61f747d 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 0000000..c69de15
--- /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 <config.h>
+#endif
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#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(&not_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,
+								&not_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 0000000..0d9106b
--- /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);