[PATCH BlueZ v1 07/14] core: Support per-client CCC state

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Added support to btd_gatt_database to track the states of CCC
descriptors on a per-client device basis. Added a new API for creating a
CCC entry for a given GATT service.
---
 src/gatt-database.c | 297 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 src/gatt-database.h |   3 +
 2 files changed, 295 insertions(+), 5 deletions(-)

diff --git a/src/gatt-database.c b/src/gatt-database.c
index 2da5a09..7b72515 100644
--- a/src/gatt-database.c
+++ b/src/gatt-database.c
@@ -48,8 +48,141 @@ struct btd_gatt_database {
 	struct btd_adapter *adapter;
 	struct gatt_db *db;
 	GIOChannel *le_io;
+	struct queue *device_states;
 };
 
+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 *server,
+							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(server->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 *server,
+							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(server, 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(server->device_states, dev_state);
+
+	return dev_state;
+}
+
+static struct ccc_state *get_ccc_state(struct btd_gatt_database *server,
+							bdaddr_t *bdaddr,
+							uint8_t bdaddr_type,
+							uint16_t handle)
+{
+	struct device_state *dev_state;
+	struct ccc_state *ccc;
+
+	dev_state = get_device_state(server, 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_server_free(void *data)
 {
 	struct btd_gatt_database *server = data;
@@ -59,6 +192,8 @@ static void gatt_server_free(void *data)
 		g_io_channel_unref(server->le_io);
 	}
 
+	/* TODO: Persistently store CCC states before freeing them */
+	queue_destroy(server->device_states, device_state_free);
 	gatt_db_unref(server->db);
 	btd_adapter_unref(server->adapter);
 	free(server);
@@ -266,6 +401,10 @@ bool btd_gatt_database_register_adapter(struct btd_adapter *adapter)
 	if (!server->db)
 		goto fail;
 
+	server->device_states = queue_new();
+	if (!server->device_states)
+		goto fail;
+
 	addr = btd_adapter_get_address(adapter);
 	server->le_io = bt_io_listen(connect_cb, NULL, NULL, NULL, &gerr,
 					BT_IO_OPT_SOURCE_BDADDR, addr,
@@ -299,18 +438,166 @@ void btd_gatt_database_unregister_adapter(struct btd_adapter *adapter)
 	queue_remove_all(servers, match_adapter, adapter, gatt_server_free);
 }
 
-struct gatt_db *btd_gatt_database_get_db(struct btd_adapter *adapter)
+static struct btd_gatt_database *get_server(struct btd_adapter *adapter)
 {
-	struct btd_gatt_database *server;
-
 	if (!servers) {
 		error("GATT server not initialized");
 		return false;
 	}
 
-	server = queue_find(servers, match_adapter, adapter);
-	if (!server)
+	return queue_find(servers, match_adapter, adapter);
+}
+
+struct gatt_db *btd_gatt_database_get_db(struct btd_adapter *adapter)
+{
+	struct btd_gatt_database *server;
+
+	server = get_server(adapter);
+	if (!server) {
+		error("Adapter not registered");
 		return false;
+	}
 
 	return server->db;
 }
+
+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 *server = 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(server, &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 *server = 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(server, &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);
+}
+
+struct gatt_db_attribute *btd_gatt_database_add_ccc(struct btd_adapter *adapter,
+							uint16_t service_handle)
+{
+	struct btd_gatt_database *server;
+	struct gatt_db_attribute *service;
+	bt_uuid_t uuid;
+
+	if (!adapter || !service_handle)
+		return NULL;
+
+	server = get_server(adapter);
+	if (!server) {
+		error("Adapter not registered");
+		return NULL;
+	}
+
+	service = gatt_db_get_attribute(server->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, server);
+}
diff --git a/src/gatt-database.h b/src/gatt-database.h
index 05e4ab9..4ba12c5 100644
--- a/src/gatt-database.h
+++ b/src/gatt-database.h
@@ -24,3 +24,6 @@ bool btd_gatt_database_register_adapter(struct btd_adapter *adapter);
 void btd_gatt_database_unregister_adapter(struct btd_adapter *adapter);
 
 struct gatt_db *btd_gatt_database_get_db(struct btd_adapter *adapter);
+
+struct gatt_db_attribute *btd_gatt_database_add_ccc(struct btd_adapter *adapter,
+						uint16_t service_handle);
-- 
2.2.0.rc0.207.ga3a616c

--
To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Bluez Devel]     [Linux Wireless Networking]     [Linux Wireless Personal Area Networking]     [Linux ATH6KL]     [Linux USB Devel]     [Linux Media Drivers]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux