*v3: - Rebased remaining patches and built them into src/gatt-database.c instead
of src/gatt-manager.c.
- Eliminated unnecessary lookups in CCC and notification related functions.
- Added a fake Battery Service to tools/gatt-example.
Arman Uguray (7):
core/gatt: Make CCC addition API public
core/gatt: Create CCC for external characteristic
core/gatt: Send not/ind for D-Bus characteristics
core/gatt: Create CEP for external characteristic
core/gatt: Register descriptors
core/gatt: Support descriptor reads/writes
tools: Added a Python example for GATT server API
src/gatt-database.c | 718 ++++++++++++++++++++++++++++++++++++++++++++--------
src/gatt-database.h | 11 +
tools/gatt-example | 533 ++++++++++++++++++++++++++++++++++++++
3 files changed, 1153 insertions(+), 109 deletions(-)
create mode 100755 tools/gatt-example
--
2.2.0.rc0.207.ga3a616c
Hi Arman,
On Wed, Mar 4, 2015 at 6:30 AM, Arman Uguray <[email protected]> wrote:
> *v3: - Rebased remaining patches and built them into src/gatt-database.c instead
> of src/gatt-manager.c.
>
> - Eliminated unnecessary lookups in CCC and notification related functions.
>
> - Added a fake Battery Service to tools/gatt-example.
>
> Arman Uguray (7):
> core/gatt: Make CCC addition API public
> core/gatt: Create CCC for external characteristic
> core/gatt: Send not/ind for D-Bus characteristics
> core/gatt: Create CEP for external characteristic
> core/gatt: Register descriptors
> core/gatt: Support descriptor reads/writes
> tools: Added a Python example for GATT server API
>
> src/gatt-database.c | 718 ++++++++++++++++++++++++++++++++++++++++++++--------
> src/gatt-database.h | 11 +
> tools/gatt-example | 533 ++++++++++++++++++++++++++++++++++++++
> 3 files changed, 1153 insertions(+), 109 deletions(-)
> create mode 100755 tools/gatt-example
>
> --
> 2.2.0.rc0.207.ga3a616c
Applied, thanks.
--
Luiz Augusto von Dentz
This patch introduces tools/gatt-example, which demonstrates how an
external application can use the GattManager1 API to register GATT
services. This example simulates a fake Heart Rate service and a fake
Battery service while also providing a test service with a non-SIG UUID
that exercises various API behavior.
---
tools/gatt-example | 533 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 533 insertions(+)
create mode 100755 tools/gatt-example
diff --git a/tools/gatt-example b/tools/gatt-example
new file mode 100755
index 0000000..a6f5cbe
--- /dev/null
+++ b/tools/gatt-example
@@ -0,0 +1,533 @@
+#!/usr/bin/python
+
+import dbus
+import dbus.exceptions
+import dbus.mainloop.glib
+import dbus.service
+
+import array
+import gobject
+
+from random import randint
+
+mainloop = None
+
+BLUEZ_SERVICE_NAME = 'org.bluez'
+GATT_MANAGER_IFACE = 'org.bluez.GattManager1'
+DBUS_OM_IFACE = 'org.freedesktop.DBus.ObjectManager'
+DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties'
+
+GATT_SERVICE_IFACE = 'org.bluez.GattService1'
+GATT_CHRC_IFACE = 'org.bluez.GattCharacteristic1'
+GATT_DESC_IFACE = 'org.bluez.GattDescriptor1'
+
+class InvalidArgsException(dbus.exceptions.DBusException):
+ _dbus_error_name = 'org.freedesktop.DBus.Error.InvalidArgs'
+
+class NotSupportedException(dbus.exceptions.DBusException):
+ _dbus_error_name = 'org.bluez.Error.NotSupported'
+
+class NotPermittedException(dbus.exceptions.DBusException):
+ _dbus_error_name = 'org.bluez.Error.NotPermitted'
+
+class InvalidValueLengthException(dbus.exceptions.DBusException):
+ _dbus_error_name = 'org.bluez.Error.InvalidValueLength'
+
+class FailedException(dbus.exceptions.DBusException):
+ _dbus_error_name = 'org.bluez.Error.Failed'
+
+
+class Service(dbus.service.Object):
+ PATH_BASE = '/org/bluez/example/service'
+
+ def __init__(self, bus, index, uuid, primary):
+ self.path = self.PATH_BASE + str(index)
+ self.bus = bus
+ self.uuid = uuid
+ self.primary = primary
+ self.characteristics = []
+ dbus.service.Object.__init__(self, bus, self.path)
+
+ def get_properties(self):
+ return {
+ GATT_SERVICE_IFACE: {
+ 'UUID': self.uuid,
+ 'Primary': self.primary,
+ 'Characteristics': dbus.Array(
+ self.get_characteristic_paths(),
+ signature='o')
+ }
+ }
+
+ def get_path(self):
+ return dbus.ObjectPath(self.path)
+
+ def add_characteristic(self, characteristic):
+ self.characteristics.append(characteristic)
+
+ def get_characteristic_paths(self):
+ result = []
+ for chrc in self.characteristics:
+ result.append(chrc.get_path())
+ return result
+
+ def get_characteristics(self):
+ return self.characteristics
+
+ @dbus.service.method(DBUS_PROP_IFACE,
+ in_signature='s',
+ out_signature='a{sv}')
+ def GetAll(self, interface):
+ if interface != GATT_SERVICE_IFACE:
+ raise InvalidArgsException()
+
+ return self.get_properties[GATT_SERVICE_IFACE]
+
+ @dbus.service.method(DBUS_OM_IFACE, out_signature='a{oa{sa{sv}}}')
+ def GetManagedObjects(self):
+ response = {}
+ print 'GetManagedObjects'
+
+ response[self.get_path()] = self.get_properties()
+ chrcs = self.get_characteristics()
+ for chrc in chrcs:
+ response[chrc.get_path()] = chrc.get_properties()
+ descs = chrc.get_descriptors()
+ for desc in descs:
+ response[desc.get_path()] = desc.get_properties()
+
+ return response
+
+
+class Characteristic(dbus.service.Object):
+ def __init__(self, bus, index, uuid, flags, service):
+ self.path = service.path + '/char' + str(index)
+ self.bus = bus
+ self.uuid = uuid
+ self.service = service
+ self.flags = flags
+ self.descriptors = []
+ dbus.service.Object.__init__(self, bus, self.path)
+
+ def get_properties(self):
+ return {
+ GATT_CHRC_IFACE: {
+ 'Service': self.service.get_path(),
+ 'UUID': self.uuid,
+ 'Flags': self.flags,
+ 'Descriptors': dbus.Array(
+ self.get_descriptor_paths(),
+ signature='o')
+ }
+ }
+
+ def get_path(self):
+ return dbus.ObjectPath(self.path)
+
+ def add_descriptor(self, descriptor):
+ self.descriptors.append(descriptor)
+
+ def get_descriptor_paths(self):
+ result = []
+ for desc in self.descriptors:
+ result.append(desc.get_path())
+ return result
+
+ def get_descriptors(self):
+ return self.descriptors
+
+ @dbus.service.method(DBUS_PROP_IFACE,
+ in_signature='s',
+ out_signature='a{sv}')
+ def GetAll(self, interface):
+ if interface != GATT_CHRC_IFACE:
+ raise InvalidArgsException()
+
+ return self.get_properties[GATT_CHRC_IFACE]
+
+ @dbus.service.method(GATT_CHRC_IFACE, out_signature='ay')
+ def ReadValue(self):
+ print 'Default ReadValue called, returning error'
+ raise NotSupportedException()
+
+ @dbus.service.method(GATT_CHRC_IFACE, in_signature='ay')
+ def WriteValue(self, value):
+ print 'Default WriteValue called, returning error'
+ raise NotSupportedException()
+
+ @dbus.service.method(GATT_CHRC_IFACE)
+ def StartNotify(self):
+ print 'Default StartNotify called, returning error'
+ raise NotSupportedException()
+
+ @dbus.service.method(GATT_CHRC_IFACE)
+ def StopNotify(self):
+ print 'Default StopNotify called, returning error'
+ raise NotSupportedException()
+
+ @dbus.service.signal(DBUS_PROP_IFACE,
+ signature='sa{sv}as')
+ def PropertiesChanged(self, interface, changed, invalidated):
+ pass
+
+
+class Descriptor(dbus.service.Object):
+ def __init__(self, bus, index, uuid, characteristic):
+ self.path = characteristic.path + '/desc' + str(index)
+ self.bus = bus
+ self.uuid = uuid
+ self.chrc = characteristic
+ dbus.service.Object.__init__(self, bus, self.path)
+
+ def get_properties(self):
+ return {
+ GATT_DESC_IFACE: {
+ 'Characteristic': self.chrc.get_path(),
+ 'UUID': self.uuid,
+ }
+ }
+
+ def get_path(self):
+ return dbus.ObjectPath(self.path)
+
+ @dbus.service.method(DBUS_PROP_IFACE,
+ in_signature='s',
+ out_signature='a{sv}')
+ def GetAll(self, interface):
+ if interface != GATT_DESC_IFACE:
+ raise InvalidArgsException()
+
+ return self.get_properties[GATT_CHRC_IFACE]
+
+ @dbus.service.method(GATT_DESC_IFACE, out_signature='ay')
+ def ReadValue(self):
+ print 'Default ReadValue called, returning error'
+ raise NotSupportedException()
+
+ @dbus.service.method(GATT_DESC_IFACE, in_signature='ay')
+ def WriteValue(self, value):
+ print 'Default WriteValue called, returning error'
+ raise NotSupportedException()
+
+
+class HeartRateService(Service):
+ """
+ Fake Heart Rate Service that simulates a fake heart beat and control point
+ behavior.
+
+ """
+ HR_UUID = '0000180d-0000-1000-8000-00805f9b34fb'
+
+ def __init__(self, bus, index):
+ Service.__init__(self, bus, index, self.HR_UUID, True)
+ self.add_characteristic(HeartRateMeasurementChrc(bus, 0, self))
+ self.add_characteristic(BodySensorLocationChrc(bus, 1, self))
+ self.add_characteristic(HeartRateControlPointChrc(bus, 2, self))
+ self.energy_expended = 0
+
+
+class HeartRateMeasurementChrc(Characteristic):
+ HR_MSRMT_UUID = '00002a37-0000-1000-8000-00805f9b34fb'
+
+ def __init__(self, bus, index, service):
+ Characteristic.__init__(
+ self, bus, index,
+ self.HR_MSRMT_UUID,
+ ['notify'],
+ service)
+ self.notifying = False
+ self.hr_ee_count = 0
+
+ def hr_msrmt_cb(self):
+ value = []
+ value.append(dbus.Byte(0x06))
+
+ value.append(dbus.Byte(randint(90, 130)))
+
+ if self.hr_ee_count % 10 == 0:
+ value[0] = dbus.Byte(value[0] | 0x08)
+ value.append(dbus.Byte(self.service.energy_expended & 0xff))
+ value.append(dbus.Byte((self.service.energy_expended >> 8) & 0xff))
+
+ self.service.energy_expended = \
+ min(0xffff, self.service.energy_expended + 1)
+ self.hr_ee_count += 1
+
+ print 'Updating value: ' + repr(value)
+
+ self.PropertiesChanged(GATT_CHRC_IFACE, { 'Value': value }, [])
+
+ return self.notifying
+
+ def _update_hr_msrmt_simulation(self):
+ print 'Update HR Measurement Simulation'
+
+ if not self.notifying:
+ return
+
+ gobject.timeout_add(1000, self.hr_msrmt_cb)
+
+ def StartNotify(self):
+ if self.notifying:
+ print 'Already notifying, nothing to do'
+ return
+
+ self.notifying = True
+ self._update_hr_msrmt_simulation()
+
+ def StopNotify(self):
+ if not self.notifying:
+ print 'Not notifying, nothing to do'
+ return
+
+ self.notifying = False
+ self._update_hr_msrmt_simulation()
+
+
+class BodySensorLocationChrc(Characteristic):
+ BODY_SNSR_LOC_UUID = '00002a38-0000-1000-8000-00805f9b34fb'
+
+ def __init__(self, bus, index, service):
+ Characteristic.__init__(
+ self, bus, index,
+ self.BODY_SNSR_LOC_UUID,
+ ['read'],
+ service)
+
+ def ReadValue(self):
+ # Return 'Chest' as the sensor location.
+ return [ 0x01 ]
+
+class HeartRateControlPointChrc(Characteristic):
+ HR_CTRL_PT_UUID = '00002a39-0000-1000-8000-00805f9b34fb'
+
+ def __init__(self, bus, index, service):
+ Characteristic.__init__(
+ self, bus, index,
+ self.HR_CTRL_PT_UUID,
+ ['write'],
+ service)
+
+ def WriteValue(self, value):
+ print 'Heart Rate Control Point WriteValue called'
+
+ if len(value) != 1:
+ raise InvalidValueLengthException()
+
+ byte = value[0]
+ print 'Control Point value: ' + repr(byte)
+
+ if byte != 1:
+ raise FailedException("0x80")
+
+ print 'Energy Expended field reset!'
+ self.service.energy_expended = 0
+
+
+class BatteryService(Service):
+ """
+ Fake Battery service that emulates a draining battery.
+
+ """
+ BATTERY_UUID = '180f'
+
+ def __init__(self, bus, index):
+ Service.__init__(self, bus, index, self.BATTERY_UUID, True)
+ self.add_characteristic(BatteryLevelCharacteristic(bus, 0, self))
+
+
+class BatteryLevelCharacteristic(Characteristic):
+ """
+ Fake Battery Level characteristic. The battery level is drained by 2 points
+ every 5 seconds.
+
+ """
+ BATTERY_LVL_UUID = '2a19'
+
+ def __init__(self, bus, index, service):
+ Characteristic.__init__(
+ self, bus, index,
+ self.BATTERY_LVL_UUID,
+ ['read', 'notify'],
+ service)
+ self.notifying = False
+ self.battery_lvl = 100
+ gobject.timeout_add(5000, self.drain_battery)
+
+ def notify_battery_level(self):
+ if not self.notifying:
+ return
+ self.PropertiesChanged(
+ GATT_CHRC_IFACE,
+ { 'Value': [dbus.Byte(self.battery_lvl)] }, [])
+
+ def drain_battery(self):
+ if self.battery_lvl > 0:
+ self.battery_lvl -= 2
+ if self.battery_lvl < 0:
+ self.battery_lvl = 0
+ print 'Battery Level drained: ' + repr(self.battery_lvl)
+ self.notify_battery_level()
+ return True
+
+ def ReadValue(self):
+ print 'Battery Level read: ' + repr(self.battery_lvl)
+ return [dbus.Byte(self.battery_lvl)]
+
+ def StartNotify(self):
+ if self.notifying:
+ print 'Already notifying, nothing to do'
+ return
+
+ self.notifying = True
+ self.notify_battery_level()
+
+ def StopNotify(self):
+ if not self.notifying:
+ print 'Not notifying, nothing to do'
+ return
+
+ self.notifying = False
+
+
+class TestService(Service):
+ """
+ Dummy test service that provides characteristics and descriptors that
+ exercise various API functionality.
+
+ """
+ TEST_SVC_UUID = '12345678-1234-5678-1234-56789abcdef0'
+
+ def __init__(self, bus, index):
+ Service.__init__(self, bus, index, self.TEST_SVC_UUID, False)
+ self.add_characteristic(TestCharacteristic(bus, 0, self))
+
+
+class TestCharacteristic(Characteristic):
+ """
+ Dummy test characteristic. Allows writing arbitrary bytes to its value, and
+ contains "extended properties", as well as a test descriptor.
+
+ """
+ TEST_CHRC_UUID = '12345678-1234-5678-1234-56789abcdef1'
+
+ def __init__(self, bus, index, service):
+ Characteristic.__init__(
+ self, bus, index,
+ self.TEST_CHRC_UUID,
+ ['read', 'write', 'writable-auxiliaries'],
+ service)
+ self.value = []
+ self.add_descriptor(TestDescriptor(bus, 0, self))
+ self.add_descriptor(
+ CharacteristicUserDescriptionDescriptor(bus, 1, self))
+
+ def ReadValue(self):
+ print 'TestCharacteristic Read: ' + repr(self.value)
+ return self.value
+
+ def WriteValue(self, value):
+ print 'TestCharacteristic Write: ' + repr(value)
+ self.value = value
+
+
+class TestDescriptor(Descriptor):
+ """
+ Dummy test descriptor. Returns a static value.
+
+ """
+ TEST_DESC_UUID = '12345678-1234-5678-1234-56789abcdef2'
+
+ def __init__(self, bus, index, characteristic):
+ Descriptor.__init__(
+ self, bus, index,
+ self.TEST_DESC_UUID,
+ characteristic)
+
+ def ReadValue(self):
+ return [
+ dbus.Byte('T'), dbus.Byte('e'), dbus.Byte('s'), dbus.Byte('t')
+ ]
+
+
+class CharacteristicUserDescriptionDescriptor(Descriptor):
+ """
+ Writable CUD descriptor.
+
+ """
+ CUD_UUID = '2901'
+
+ def __init__(self, bus, index, characteristic):
+ self.writable = 'writable-auxiliaries' in characteristic.flags
+ self.value = array.array('B', 'This is a characteristic for testing')
+ self.value = self.value.tolist()
+ Descriptor.__init__(
+ self, bus, index,
+ self.CUD_UUID,
+ characteristic)
+
+ def ReadValue(self):
+ return self.value
+
+ def WriteValue(self, value):
+ if not self.writable:
+ raise NotPermittedException()
+ self.value = value
+
+
+def register_service_cb():
+ print 'GATT service registered'
+
+
+def register_service_error_cb(error):
+ print 'Failed to register service: ' + str(error)
+ mainloop.quit()
+
+
+def find_adapter(bus):
+ remote_om = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, '/'),
+ DBUS_OM_IFACE)
+ objects = remote_om.GetManagedObjects()
+
+ for o, props in objects.iteritems():
+ if props.has_key(GATT_MANAGER_IFACE):
+ return o
+
+ return None
+
+def main():
+ global mainloop
+
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ bus = dbus.SystemBus()
+
+ adapter = find_adapter(bus)
+ if not adapter:
+ print 'GattManager1 interface not found'
+ return
+
+ service_manager = dbus.Interface(
+ bus.get_object(BLUEZ_SERVICE_NAME, adapter),
+ GATT_MANAGER_IFACE)
+
+ hr_service = HeartRateService(bus, 0)
+ bat_service = BatteryService(bus, 1)
+ test_service = TestService(bus, 2)
+
+ mainloop = gobject.MainLoop()
+
+ service_manager.RegisterService(hr_service.get_path(), {},
+ reply_handler=register_service_cb,
+ error_handler=register_service_error_cb)
+ service_manager.RegisterService(bat_service.get_path(), {},
+ reply_handler=register_service_cb,
+ error_handler=register_service_error_cb)
+ service_manager.RegisterService(test_service.get_path(), {},
+ reply_handler=register_service_cb,
+ error_handler=register_service_error_cb)
+
+ mainloop.run()
+
+if __name__ == '__main__':
+ main()
--
2.2.0.rc0.207.ga3a616c
This patch adds support for reading and writing to a descriptor from an
external application during a read/write procedure. This patch unifies
the code paths for characteristic and descriptor read/write operations.
---
src/gatt-database.c | 217 +++++++++++++++++++++++++++++++++-------------------
1 file changed, 139 insertions(+), 78 deletions(-)
diff --git a/src/gatt-database.c b/src/gatt-database.c
index 0ee8042..bba05ad 100644
--- a/src/gatt-database.c
+++ b/src/gatt-database.c
@@ -110,12 +110,15 @@ struct external_desc {
GDBusProxy *proxy;
struct gatt_db_attribute *attrib;
bool handled;
+ struct queue *pending_reads;
+ struct queue *pending_writes;
};
struct pending_op {
- struct external_chrc *chrc;
unsigned int id;
- void *user_data;
+ struct gatt_db_attribute *attrib;
+ struct queue *owner_queue;
+ void *setup_data;
};
struct device_state {
@@ -291,17 +294,19 @@ static void cancel_pending_read(void *data)
{
struct pending_op *op = data;
- gatt_db_attribute_read_result(op->chrc->attrib, op->id,
+ gatt_db_attribute_read_result(op->attrib, op->id,
BT_ATT_ERROR_REQUEST_NOT_SUPPORTED,
NULL, 0);
+ op->owner_queue = NULL;
}
static void cancel_pending_write(void *data)
{
struct pending_op *op = data;
- gatt_db_attribute_write_result(op->chrc->attrib, op->id,
+ gatt_db_attribute_write_result(op->attrib, op->id,
BT_ATT_ERROR_REQUEST_NOT_SUPPORTED);
+ op->owner_queue = NULL;
}
static void chrc_free(void *data)
@@ -326,6 +331,9 @@ static void desc_free(void *data)
{
struct external_desc *desc = data;
+ queue_destroy(desc->pending_reads, cancel_pending_read);
+ queue_destroy(desc->pending_writes, cancel_pending_write);
+
if (desc->proxy)
g_dbus_proxy_unref(desc->proxy);
@@ -1076,8 +1084,23 @@ static struct external_desc *desc_create(struct external_service *service,
if (!desc)
return NULL;
+ desc->pending_reads = queue_new();
+ if (!desc->pending_reads) {
+ free(desc);
+ return NULL;
+ }
+
+ desc->pending_writes = queue_new();
+ if (!desc->pending_writes) {
+ queue_destroy(desc->pending_reads, NULL);
+ free(desc);
+ return NULL;
+ }
+
desc->chrc_path = g_strdup(chrc_path);
if (!desc->chrc_path) {
+ queue_destroy(desc->pending_reads, NULL);
+ queue_destroy(desc->pending_writes, NULL);
free(desc);
return NULL;
}
@@ -1385,7 +1408,7 @@ static void read_reply_cb(DBusMessage *message, void *user_data)
uint8_t *value = NULL;
int len = 0;
- if (!op->chrc) {
+ if (!op->owner_queue) {
DBG("Pending read was canceled when object got removed");
return;
}
@@ -1427,12 +1450,22 @@ static void read_reply_cb(DBusMessage *message, void *user_data)
value = len ? value : NULL;
done:
- gatt_db_attribute_read_result(op->chrc->attrib, op->id, ecode,
- value, len);
+ gatt_db_attribute_read_result(op->attrib, op->id, ecode, value, len);
}
-static struct pending_op *pending_read_new(struct external_chrc *chrc,
- unsigned int id)
+static void pending_op_free(void *data)
+{
+ struct pending_op *op = data;
+
+ if (op->owner_queue)
+ queue_remove(op->owner_queue, op);
+
+ free(op);
+}
+
+static struct pending_op *pending_read_new(struct queue *owner_queue,
+ struct gatt_db_attribute *attrib,
+ unsigned int id)
{
struct pending_op *op;
@@ -1440,51 +1473,33 @@ static struct pending_op *pending_read_new(struct external_chrc *chrc,
if (!op)
return NULL;
- op->chrc = chrc;
+ op->owner_queue = owner_queue;
+ op->attrib = attrib;
op->id = id;
- queue_push_tail(chrc->pending_reads, op);
+ queue_push_tail(owner_queue, op);
return op;
}
-static void pending_read_free(void *data)
-{
- struct pending_op *op = data;
-
- if (op->chrc)
- queue_remove(op->chrc->pending_reads, op);
-
- free(op);
-}
-
-static void chrc_read_cb(struct gatt_db_attribute *attrib,
- unsigned int id, uint16_t offset,
- uint8_t opcode, struct bt_att *att,
- void *user_data)
+static void send_read(struct gatt_db_attribute *attrib, GDBusProxy *proxy,
+ struct queue *owner_queue,
+ unsigned int id)
{
- struct external_chrc *chrc = user_data;
struct pending_op *op;
uint8_t ecode = BT_ATT_ERROR_UNLIKELY;
- if (chrc->attrib != attrib) {
- error("Read callback called with incorrect attribute");
- goto error;
-
- }
-
- op = pending_read_new(chrc, id);
+ op = pending_read_new(owner_queue, attrib, id);
if (!op) {
error("Failed to allocate memory for pending read call");
ecode = BT_ATT_ERROR_INSUFFICIENT_RESOURCES;
goto error;
}
- if (g_dbus_proxy_method_call(chrc->proxy, "ReadValue", NULL,
- read_reply_cb, op,
- pending_read_free) == TRUE)
+ if (g_dbus_proxy_method_call(proxy, "ReadValue", NULL, read_reply_cb,
+ op, pending_op_free) == TRUE)
return;
- pending_read_free(op);
+ pending_op_free(op);
error:
gatt_db_attribute_read_result(attrib, id, ecode, NULL, 0);
@@ -1493,7 +1508,7 @@ error:
static void write_setup_cb(DBusMessageIter *iter, void *user_data)
{
struct pending_op *op = user_data;
- struct iovec *iov = op->user_data;
+ struct iovec *iov = op->setup_data;
DBusMessageIter array;
dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "y", &array);
@@ -1509,7 +1524,7 @@ static void write_reply_cb(DBusMessage *message, void *user_data)
DBusMessageIter iter;
uint8_t ecode = 0;
- if (!op->chrc) {
+ if (!op->owner_queue) {
DBG("Pending write was canceled when object got removed");
return;
}
@@ -1535,13 +1550,14 @@ static void write_reply_cb(DBusMessage *message, void *user_data)
}
done:
- gatt_db_attribute_write_result(op->chrc->attrib, op->id, ecode);
+ gatt_db_attribute_write_result(op->attrib, op->id, ecode);
}
-static struct pending_op *pending_write_new(struct external_chrc *chrc,
- unsigned int id,
- const uint8_t *value,
- size_t len)
+static struct pending_op *pending_write_new(struct queue *owner_queue,
+ struct gatt_db_attribute *attrib,
+ unsigned int id,
+ const uint8_t *value,
+ size_t len)
{
struct pending_op *op;
struct iovec iov;
@@ -1553,52 +1569,36 @@ static struct pending_op *pending_write_new(struct external_chrc *chrc,
iov.iov_base = (uint8_t *) value;
iov.iov_len = len;
- op->chrc = chrc;
+ op->owner_queue = owner_queue;
+ op->attrib = attrib;
op->id = id;
- op->user_data = &iov;
- queue_push_tail(chrc->pending_writes, op);
+ op->setup_data = &iov;
+ queue_push_tail(owner_queue, op);
return op;
}
-static void pending_write_free(void *data)
-{
- struct pending_op *op = data;
-
- if (op->chrc)
- queue_remove(op->chrc->pending_writes, op);
-
- free(op);
-}
-
-static void chrc_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)
+static void send_write(struct gatt_db_attribute *attrib, GDBusProxy *proxy,
+ struct queue *owner_queue,
+ unsigned int id,
+ const uint8_t *value, size_t len)
{
- struct external_chrc *chrc = user_data;
struct pending_op *op;
uint8_t ecode = BT_ATT_ERROR_UNLIKELY;
- if (chrc->attrib != attrib) {
- error("Write callback called with incorrect attribute");
- goto error;
- }
-
- op = pending_write_new(chrc, id, value, len);
+ op = pending_write_new(owner_queue, attrib, id, value, len);
if (!op) {
error("Failed to allocate memory for pending read call");
ecode = BT_ATT_ERROR_INSUFFICIENT_RESOURCES;
goto error;
}
- if (g_dbus_proxy_method_call(chrc->proxy, "WriteValue", write_setup_cb,
+ if (g_dbus_proxy_method_call(proxy, "WriteValue", write_setup_cb,
write_reply_cb, op,
- pending_write_free) == TRUE)
+ pending_op_free) == TRUE)
return;
- pending_write_free(op);
+ pending_op_free(op);
error:
gatt_db_attribute_write_result(attrib, id, ecode);
@@ -1775,6 +1775,37 @@ static bool create_cep_entry(struct external_service *service,
return true;
}
+static void desc_read_cb(struct gatt_db_attribute *attrib,
+ unsigned int id, uint16_t offset,
+ uint8_t opcode, struct bt_att *att,
+ void *user_data)
+{
+ struct external_desc *desc = user_data;
+
+ if (desc->attrib != attrib) {
+ error("Read callback called with incorrect attribute");
+ return;
+ }
+
+ send_read(attrib, desc->proxy, desc->pending_reads, id);
+}
+
+static void desc_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 external_desc *desc = user_data;
+
+ if (desc->attrib != attrib) {
+ error("Read callback called with incorrect attribute");
+ return;
+ }
+
+ send_write(attrib, desc->proxy, desc->pending_writes, id, value, len);
+}
+
static bool database_add_desc(struct external_service *service,
struct external_desc *desc)
{
@@ -1786,13 +1817,12 @@ static bool database_add_desc(struct external_service *service,
}
/*
- * TODO: Set read/write callbacks and property set permissions based on
- * a D-Bus property of the external descriptor.
+ * TODO: Set permissions based on a D-Bus property of the external
+ * descriptor.
*/
- desc->attrib = gatt_db_service_add_descriptor(service->attrib,
- &uuid, 0, NULL,
- NULL, NULL);
-
+ desc->attrib = gatt_db_service_add_descriptor(service->attrib, &uuid,
+ BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
+ desc_read_cb, desc_write_cb, desc);
if (!desc->attrib) {
error("Failed to create descriptor entry in database");
return false;
@@ -1803,6 +1833,37 @@ static bool database_add_desc(struct external_service *service,
return true;
}
+static void chrc_read_cb(struct gatt_db_attribute *attrib,
+ unsigned int id, uint16_t offset,
+ uint8_t opcode, struct bt_att *att,
+ void *user_data)
+{
+ struct external_chrc *chrc = user_data;
+
+ if (chrc->attrib != attrib) {
+ error("Read callback called with incorrect attribute");
+ return;
+ }
+
+ send_read(attrib, chrc->proxy, chrc->pending_reads, id);
+}
+
+static void chrc_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 external_chrc *chrc = user_data;
+
+ if (chrc->attrib != attrib) {
+ error("Write callback called with incorrect attribute");
+ return;
+ }
+
+ send_write(attrib, chrc->proxy, chrc->pending_writes, id, value, len);
+}
+
static bool database_add_chrc(struct external_service *service,
struct external_chrc *chrc)
{
--
2.2.0.rc0.207.ga3a616c
This patch adds support for registering external descriptor objects.
---
src/gatt-database.c | 191 +++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 176 insertions(+), 15 deletions(-)
diff --git a/src/gatt-database.c b/src/gatt-database.c
index 915461c..0ee8042 100644
--- a/src/gatt-database.c
+++ b/src/gatt-database.c
@@ -53,6 +53,7 @@
#define GATT_MANAGER_IFACE "org.bluez.GattManager1"
#define GATT_SERVICE_IFACE "org.bluez.GattService1"
#define GATT_CHRC_IFACE "org.bluez.GattCharacteristic1"
+#define GATT_DESC_IFACE "org.bluez.GattDescriptor1"
#define UUID_GAP 0x1800
#define UUID_GATT 0x1801
@@ -87,10 +88,12 @@ struct external_service {
struct gatt_db_attribute *attrib;
uint16_t attr_cnt;
struct queue *chrcs;
+ struct queue *descs;
};
struct external_chrc {
struct external_service *service;
+ char *path;
GDBusProxy *proxy;
uint8_t props;
uint8_t ext_props;
@@ -101,6 +104,14 @@ struct external_chrc {
unsigned int ntfy_cnt;
};
+struct external_desc {
+ struct external_service *service;
+ char *chrc_path;
+ GDBusProxy *proxy;
+ struct gatt_db_attribute *attrib;
+ bool handled;
+};
+
struct pending_op {
struct external_chrc *chrc;
unsigned int id;
@@ -300,6 +311,9 @@ static void chrc_free(void *data)
queue_destroy(chrc->pending_reads, cancel_pending_read);
queue_destroy(chrc->pending_writes, cancel_pending_write);
+ if (chrc->path)
+ g_free(chrc->path);
+
if (chrc->proxy)
g_dbus_proxy_set_property_watch(chrc->proxy, NULL, NULL);
@@ -308,11 +322,25 @@ static void chrc_free(void *data)
free(chrc);
}
+static void desc_free(void *data)
+{
+ struct external_desc *desc = data;
+
+ if (desc->proxy)
+ g_dbus_proxy_unref(desc->proxy);
+
+ if (desc->chrc_path)
+ g_free(desc->chrc_path);
+
+ free(desc);
+}
+
static void service_free(void *data)
{
struct external_service *service = data;
queue_destroy(service->chrcs, chrc_free);
+ queue_destroy(service->descs, desc_free);
gatt_db_remove_service(service->database->db, service->attrib);
@@ -1002,7 +1030,8 @@ static void service_remove(void *data)
}
static struct external_chrc *chrc_create(struct external_service *service,
- GDBusProxy *proxy)
+ GDBusProxy *proxy,
+ const char *path)
{
struct external_chrc *chrc;
@@ -1023,12 +1052,42 @@ static struct external_chrc *chrc_create(struct external_service *service,
return NULL;
}
+ chrc->path = g_strdup(path);
+ if (!chrc->path) {
+ queue_destroy(chrc->pending_reads, NULL);
+ queue_destroy(chrc->pending_writes, NULL);
+ free(chrc);
+ return NULL;
+ }
+
chrc->service = service;
chrc->proxy = g_dbus_proxy_ref(proxy);
return chrc;
}
+static struct external_desc *desc_create(struct external_service *service,
+ GDBusProxy *proxy,
+ const char *chrc_path)
+{
+ struct external_desc *desc;
+
+ desc = new0(struct external_desc, 1);
+ if (!desc)
+ return NULL;
+
+ desc->chrc_path = g_strdup(chrc_path);
+ if (!desc->chrc_path) {
+ free(desc);
+ return NULL;
+ }
+
+ desc->service = service;
+ desc->proxy = g_dbus_proxy_ref(proxy);
+
+ return desc;
+}
+
static bool incr_attr_count(struct external_service *service, uint16_t incr)
{
if (service->attr_cnt > UINT16_MAX - incr)
@@ -1039,18 +1098,28 @@ static bool incr_attr_count(struct external_service *service, uint16_t incr)
return true;
}
-static bool parse_service(GDBusProxy *proxy, struct external_service *service)
+static bool parse_path(GDBusProxy *proxy, const char *name, const char **path)
{
DBusMessageIter iter;
- const char *service_path;
- if (!g_dbus_proxy_get_property(proxy, "Service", &iter))
+ if (!g_dbus_proxy_get_property(proxy, name, &iter))
return false;
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_OBJECT_PATH)
return false;
- dbus_message_iter_get_basic(&iter, &service_path);
+ dbus_message_iter_get_basic(&iter, path);
+
+ return true;
+}
+
+static bool check_service_path(GDBusProxy *proxy,
+ struct external_service *service)
+{
+ const char *service_path;
+
+ if (!parse_path(proxy, "Service", &service_path))
+ return false;
return g_strcmp0(service_path, service->path) == 0;
}
@@ -1110,7 +1179,6 @@ static void proxy_added_cb(GDBusProxy *proxy, void *user_data)
{
struct external_service *service = user_data;
const char *iface, *path;
- struct external_chrc *chrc;
if (service->failed || service->attrib)
return;
@@ -1121,8 +1189,6 @@ static void proxy_added_cb(GDBusProxy *proxy, void *user_data)
if (!g_str_has_prefix(path, service->path))
return;
- /* TODO: Handle descriptors here */
-
if (g_strcmp0(iface, GATT_SERVICE_IFACE) == 0) {
if (service->proxy)
return;
@@ -1146,13 +1212,15 @@ static void proxy_added_cb(GDBusProxy *proxy, void *user_data)
service->proxy = g_dbus_proxy_ref(proxy);
} else if (g_strcmp0(iface, GATT_CHRC_IFACE) == 0) {
+ struct external_chrc *chrc;
+
if (g_strcmp0(path, service->path) == 0) {
error("Characteristic path same as service path");
service->failed = true;
return;
}
- chrc = chrc_create(service, proxy);
+ chrc = chrc_create(service, proxy, path);
if (!chrc) {
service->failed = true;
return;
@@ -1194,8 +1262,35 @@ static void proxy_added_cb(GDBusProxy *proxy, void *user_data)
}
queue_push_tail(service->chrcs, chrc);
- } else
+ } else if (g_strcmp0(iface, GATT_DESC_IFACE) == 0) {
+ struct external_desc *desc;
+ const char *chrc_path;
+
+ if (!parse_path(proxy, "Characteristic", &chrc_path)) {
+ error("Failed to obtain characteristic path for "
+ "descriptor");
+ service->failed = true;
+ return;
+ }
+
+ desc = desc_create(service, proxy, chrc_path);
+ if (!desc) {
+ service->failed = true;
+ return;
+ }
+
+ /* Add 1 for the descriptor attribute */
+ if (!incr_attr_count(service, 1)) {
+ error("Failed to increment attribute count");
+ service->failed = true;
+ return;
+ }
+
+ queue_push_tail(service->descs, desc);
+ } else {
+ DBG("Ignoring unrelated interface: %s", iface);
return;
+ }
DBG("Object added to service - path: %s, iface: %s", path, iface);
}
@@ -1680,18 +1775,47 @@ static bool create_cep_entry(struct external_service *service,
return true;
}
+static bool database_add_desc(struct external_service *service,
+ struct external_desc *desc)
+{
+ bt_uuid_t uuid;
+
+ if (!parse_uuid(desc->proxy, &uuid)) {
+ error("Failed to read \"UUID\" property of descriptor");
+ return false;
+ }
+
+ /*
+ * TODO: Set read/write callbacks and property set permissions based on
+ * a D-Bus property of the external descriptor.
+ */
+ desc->attrib = gatt_db_service_add_descriptor(service->attrib,
+ &uuid, 0, NULL,
+ NULL, NULL);
+
+ if (!desc->attrib) {
+ error("Failed to create descriptor entry in database");
+ return false;
+ }
+
+ desc->handled = true;
+
+ return true;
+}
+
static bool database_add_chrc(struct external_service *service,
struct external_chrc *chrc)
{
bt_uuid_t uuid;
uint32_t perm;
+ const struct queue_entry *entry;
if (!parse_uuid(chrc->proxy, &uuid)) {
error("Failed to read \"UUID\" property of characteristic");
return false;
}
- if (!parse_service(chrc->proxy, service)) {
+ if (!check_service_path(chrc->proxy, service)) {
error("Invalid service path for characteristic");
return false;
}
@@ -1717,9 +1841,33 @@ static bool database_add_chrc(struct external_service *service,
if (!create_cep_entry(service, chrc))
return false;
+ /* Handle the descriptors that belong to this characteristic. */
+ entry = queue_get_entries(service->descs);
+ while (entry) {
+ struct external_desc *desc = entry->data;
+
+ if (desc->handled || g_strcmp0(desc->chrc_path, chrc->path))
+ continue;
+
+ if (!database_add_desc(service, desc)) {
+ chrc->attrib = NULL;
+ error("Failed to create descriptor entry");
+ return false;
+ }
+
+ entry = entry->next;
+ }
+
return true;
}
+static bool match_desc_unhandled(const void *a, const void *b)
+{
+ const struct external_desc *desc = a;
+
+ return !desc->handled;
+}
+
static bool create_service_entry(struct external_service *service)
{
bt_uuid_t uuid;
@@ -1747,18 +1895,27 @@ static bool create_service_entry(struct external_service *service)
if (!database_add_chrc(service, chrc)) {
error("Failed to add characteristic");
- gatt_db_remove_service(service->database->db,
- service->attrib);
- service->attrib = NULL;
- return false;
+ goto fail;
}
entry = entry->next;
}
+ /* If there are any unhandled descriptors, return an error */
+ if (queue_find(service->descs, match_desc_unhandled, NULL)) {
+ error("Found descriptor with no matching characteristic!");
+ goto fail;
+ }
+
gatt_db_service_set_active(service->attrib, true);
return true;
+
+fail:
+ gatt_db_remove_service(service->database->db, service->attrib);
+ service->attrib = NULL;
+
+ return false;
}
static void client_ready_cb(GDBusClient *client, void *user_data)
@@ -1825,6 +1982,10 @@ static struct external_service *service_create(DBusConnection *conn,
if (!service->chrcs)
goto fail;
+ service->descs = queue_new();
+ if (!service->descs)
+ goto fail;
+
service->reg = dbus_message_ref(msg);
g_dbus_client_set_disconnect_watch(service->client,
--
2.2.0.rc0.207.ga3a616c
This patch adds support for adding a Characteristic Extended
Properties descriptor entry for an external characteristic, if
it has the 'reliable-write' or 'writable-auxiliaries' property.
---
src/gatt-database.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 59 insertions(+), 3 deletions(-)
diff --git a/src/gatt-database.c b/src/gatt-database.c
index e7f849e..915461c 100644
--- a/src/gatt-database.c
+++ b/src/gatt-database.c
@@ -1187,6 +1187,12 @@ static void proxy_added_cb(GDBusProxy *proxy, void *user_data)
return;
}
+ if (chrc->ext_props && !incr_attr_count(service, 1)) {
+ error("Failed to increment attribute count for CEP");
+ service->failed = true;
+ return;
+ }
+
queue_push_tail(service->chrcs, chrc);
} else
return;
@@ -1610,6 +1616,12 @@ static void property_changed_cb(GDBusProxy *proxy, const char *name,
static bool create_ccc_entry(struct external_service *service,
struct external_chrc *chrc)
{
+ if (!(chrc->props & BT_GATT_CHRC_PROP_NOTIFY) &&
+ !(chrc->props & BT_GATT_CHRC_PROP_INDICATE)) {
+ DBG("No need to create CCC entry for characteristic");
+ return true;
+ }
+
chrc->ccc = service_add_ccc(service->attrib, service->database,
ccc_write_cb, chrc, NULL);
if (!chrc->ccc) {
@@ -1626,6 +1638,48 @@ static bool create_ccc_entry(struct external_service *service,
return true;
}
+static void cep_write_cb(struct gatt_db_attribute *attrib, int err,
+ void *user_data)
+{
+ if (err)
+ DBG("Failed to store CEP value in the database");
+ else
+ DBG("Stored CEP value in the database");
+}
+
+static bool create_cep_entry(struct external_service *service,
+ struct external_chrc *chrc)
+{
+ struct gatt_db_attribute *cep;
+ bt_uuid_t uuid;
+ uint8_t value[2];
+
+ if (!chrc->ext_props) {
+ DBG("No need to create CEP entry for characteristic");
+ return true;
+ }
+
+ bt_uuid16_create(&uuid, GATT_CHARAC_EXT_PROPER_UUID);
+ cep = gatt_db_service_add_descriptor(service->attrib, &uuid,
+ BT_ATT_PERM_READ,
+ NULL, NULL, NULL);
+ if (!cep) {
+ error("Failed to create CEP entry for characteristic");
+ return false;
+ }
+
+ memset(value, 0, sizeof(value));
+ value[0] = chrc->ext_props;
+
+ if (!gatt_db_attribute_write(cep, 0, value, sizeof(value), 0, NULL,
+ cep_write_cb, NULL)) {
+ DBG("Failed to store CEP value in the database");
+ return false;
+ }
+
+ return true;
+}
+
static bool database_add_chrc(struct external_service *service,
struct external_chrc *chrc)
{
@@ -1657,9 +1711,11 @@ static bool database_add_chrc(struct external_service *service,
return false;
}
- if (chrc->props & BT_GATT_CHRC_PROP_NOTIFY ||
- chrc->props & BT_GATT_CHRC_PROP_INDICATE)
- return create_ccc_entry(service, chrc);
+ if (!create_ccc_entry(service, chrc))
+ return false;
+
+ if (!create_cep_entry(service, chrc))
+ return false;
return true;
}
--
2.2.0.rc0.207.ga3a616c
This patch adds support for sending out notification/indication packets
for external characteristics whenever a PropertiesChanged signal is
received for the "Value" property of an external characteristic that has
either the 'notify' or the 'indicate' property.
---
src/gatt-database.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 49 insertions(+), 2 deletions(-)
diff --git a/src/gatt-database.c b/src/gatt-database.c
index 2ca9daa..e7f849e 100644
--- a/src/gatt-database.c
+++ b/src/gatt-database.c
@@ -90,6 +90,7 @@ struct external_service {
};
struct external_chrc {
+ struct external_service *service;
GDBusProxy *proxy;
uint8_t props;
uint8_t ext_props;
@@ -299,6 +300,9 @@ static void chrc_free(void *data)
queue_destroy(chrc->pending_reads, cancel_pending_read);
queue_destroy(chrc->pending_writes, cancel_pending_write);
+ if (chrc->proxy)
+ g_dbus_proxy_set_property_watch(chrc->proxy, NULL, NULL);
+
g_dbus_proxy_unref(chrc->proxy);
free(chrc);
@@ -997,7 +1001,8 @@ static void service_remove(void *data)
service_remove_helper(service);
}
-static struct external_chrc *chrc_create(GDBusProxy *proxy)
+static struct external_chrc *chrc_create(struct external_service *service,
+ GDBusProxy *proxy)
{
struct external_chrc *chrc;
@@ -1018,6 +1023,7 @@ static struct external_chrc *chrc_create(GDBusProxy *proxy)
return NULL;
}
+ chrc->service = service;
chrc->proxy = g_dbus_proxy_ref(proxy);
return chrc;
@@ -1146,7 +1152,7 @@ static void proxy_added_cb(GDBusProxy *proxy, void *user_data)
return;
}
- chrc = chrc_create(proxy);
+ chrc = chrc_create(service, proxy);
if (!chrc) {
service->failed = true;
return;
@@ -1566,6 +1572,41 @@ static uint8_t ccc_write_cb(uint16_t value, void *user_data)
return 0;
}
+static void property_changed_cb(GDBusProxy *proxy, const char *name,
+ DBusMessageIter *iter, void *user_data)
+{
+ struct external_chrc *chrc = user_data;
+ DBusMessageIter array;
+ uint8_t *value = NULL;
+ int len = 0;
+
+ if (strcmp(name, "Value"))
+ return;
+
+ if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) {
+ DBG("Malformed \"Value\" property received");
+ return;
+ }
+
+ dbus_message_iter_recurse(iter, &array);
+ dbus_message_iter_get_fixed_array(&array, &value, &len);
+
+ if (len < 0) {
+ DBG("Malformed \"Value\" property received");
+ return;
+ }
+
+ /* Truncate the value if it's too large */
+ len = MIN(BT_ATT_MAX_VALUE_LEN, len);
+ value = len ? value : NULL;
+
+ send_notification_to_devices(chrc->service->database,
+ gatt_db_attribute_get_handle(chrc->attrib),
+ value, len,
+ gatt_db_attribute_get_handle(chrc->ccc),
+ chrc->props & BT_GATT_CHRC_PROP_INDICATE);
+}
+
static bool create_ccc_entry(struct external_service *service,
struct external_chrc *chrc)
{
@@ -1576,6 +1617,12 @@ static bool create_ccc_entry(struct external_service *service,
return false;
}
+ if (g_dbus_proxy_set_property_watch(chrc->proxy, property_changed_cb,
+ chrc) == FALSE) {
+ error("Failed to set up property watch for characteristic");
+ return false;
+ }
+
return true;
}
--
2.2.0.rc0.207.ga3a616c
This patch adds the btd_gatt_database_add_ccc function to the
database's public API. The signature has been extended to accept
a callback that gets invoked to notify the upper layer when a CCC write
is performed. The result is cached by the database on a per-device basis
while the callback is invoked for all writes from all devices.
---
src/gatt-database.c | 129 +++++++++++++++++++++++++++++++++++++++++++++-------
src/gatt-database.h | 11 +++++
2 files changed, 124 insertions(+), 16 deletions(-)
diff --git a/src/gatt-database.c b/src/gatt-database.c
index fa10ce8..db21d49 100644
--- a/src/gatt-database.c
+++ b/src/gatt-database.c
@@ -70,6 +70,7 @@ struct btd_gatt_database {
uint32_t gap_handle;
uint32_t gatt_handle;
struct queue *device_states;
+ struct queue *ccc_callbacks;
struct gatt_db_attribute *svc_chngd;
struct gatt_db_attribute *svc_chngd_ccc;
struct queue *services;
@@ -114,11 +115,48 @@ struct ccc_state {
uint8_t value[2];
};
+struct ccc_cb_data {
+ uint16_t handle;
+ btd_gatt_database_ccc_write_t callback;
+ btd_gatt_database_destroy_t destroy;
+ void *user_data;
+};
+
struct device_info {
bdaddr_t bdaddr;
uint8_t bdaddr_type;
};
+static void ccc_cb_free(void *data)
+{
+ struct ccc_cb_data *ccc_cb = data;
+
+ if (ccc_cb->destroy)
+ ccc_cb->destroy(ccc_cb->user_data);
+
+ free(ccc_cb);
+}
+
+static bool ccc_cb_match_service(const void *data, const void *match_data)
+{
+ const struct ccc_cb_data *ccc_cb = 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_cb->handle >= start && ccc_cb->handle <= end;
+}
+
+static bool ccc_cb_match_handle(const void *data, const void *match_data)
+{
+ const struct ccc_cb_data *ccc_cb = data;
+ uint16_t handle = PTR_TO_UINT(match_data);
+
+ return ccc_cb->handle == handle;
+}
+
static bool dev_state_match(const void *a, const void *b)
{
const struct device_state *dev_state = a;
@@ -319,6 +357,10 @@ static void gatt_database_free(void *data)
/* TODO: Persistently store CCC states before freeing them */
queue_destroy(database->device_states, device_state_free);
queue_destroy(database->services, service_free);
+ queue_destroy(database->ccc_callbacks, ccc_cb_free);
+ database->device_states = NULL;
+ database->ccc_callbacks = NULL;
+
gatt_db_unregister(database->db, database->db_id);
gatt_db_unref(database->db);
btd_adapter_unref(database->adapter);
@@ -611,6 +653,7 @@ static void gatt_ccc_write_cb(struct gatt_db_attribute *attrib,
{
struct btd_gatt_database *database = user_data;
struct ccc_state *ccc;
+ struct ccc_cb_data *ccc_cb;
uint16_t handle;
uint8_t ecode = 0;
bdaddr_t bdaddr;
@@ -641,23 +684,74 @@ static void gatt_ccc_write_cb(struct gatt_db_attribute *attrib,
goto done;
}
- /*
- * TODO: Perform this after checking with a callback to the upper
- * layer.
- */
- ccc->value[0] = value[0];
- ccc->value[1] = value[1];
+ ccc_cb = queue_find(database->ccc_callbacks, ccc_cb_match_handle,
+ UINT_TO_PTR(gatt_db_attribute_get_handle(attrib)));
+ if (!ccc_cb) {
+ ecode = BT_ATT_ERROR_UNLIKELY;
+ goto done;
+ }
+
+ /* If value is identical, then just succeed */
+ if (ccc->value[0] == value[0] && ccc->value[1] == value[1])
+ goto done;
+
+ if (ccc_cb->callback)
+ ecode = ccc_cb->callback(get_le16(value), ccc_cb->user_data);
+
+ if (!ecode) {
+ 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)
+service_add_ccc(struct gatt_db_attribute *service,
+ struct btd_gatt_database *database,
+ btd_gatt_database_ccc_write_t write_callback,
+ void *user_data,
+ btd_gatt_database_destroy_t destroy)
+{
+ struct gatt_db_attribute *ccc;
+ struct ccc_cb_data *ccc_cb;
+ bt_uuid_t uuid;
+
+ ccc_cb = new0(struct ccc_cb_data, 1);
+ if (!ccc_cb) {
+ error("Could not allocate memory for callback data");
+ return NULL;
+ }
+
+ bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID);
+ ccc = gatt_db_service_add_descriptor(service, &uuid,
+ BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
+ gatt_ccc_read_cb, gatt_ccc_write_cb, database);
+ if (!ccc) {
+ error("Failed to create CCC entry in database");
+ free(ccc_cb);
+ return NULL;
+ }
+
+ ccc_cb->handle = gatt_db_attribute_get_handle(ccc);
+ ccc_cb->callback = write_callback;
+ ccc_cb->destroy = destroy;
+ ccc_cb->user_data = user_data;
+
+ queue_push_tail(database->ccc_callbacks, ccc_cb);
+
+ return ccc;
+}
+
+struct gatt_db_attribute *
+btd_gatt_database_add_ccc(struct btd_gatt_database *database,
+ uint16_t service_handle,
+ btd_gatt_database_ccc_write_t write_callback,
+ void *user_data,
+ btd_gatt_database_destroy_t destroy)
{
struct gatt_db_attribute *service;
- bt_uuid_t uuid;
if (!database || !service_handle)
return NULL;
@@ -668,17 +762,14 @@ gatt_database_add_ccc(struct btd_gatt_database *database,
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);
+ return service_add_ccc(service, database, write_callback, user_data,
+ destroy);
}
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);
@@ -686,14 +777,14 @@ static void populate_gatt_service(struct btd_gatt_database *database)
database->gatt_handle = database_add_record(database, UUID_GATT,
service,
"Generic Attribute Profile");
- 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);
+ database->svc_chngd_ccc = service_add_ccc(service, database, NULL, NULL,
+ NULL);
gatt_db_service_set_active(service, true);
}
@@ -845,6 +936,8 @@ static void gatt_db_service_removed(struct gatt_db_attribute *attrib,
send_service_changed(database, attrib);
queue_foreach(database->device_states, remove_device_ccc, attrib);
+ queue_remove_all(database->ccc_callbacks, ccc_cb_match_service, attrib,
+ ccc_cb_free);
}
static bool match_service_path(const void *a, const void *b)
@@ -1648,6 +1741,10 @@ struct btd_gatt_database *btd_gatt_database_new(struct btd_adapter *adapter)
if (!database->services)
goto fail;
+ database->ccc_callbacks = queue_new();
+ if (!database->ccc_callbacks)
+ goto fail;
+
database->db_id = gatt_db_register(database->db, gatt_db_service_added,
gatt_db_service_removed,
database, NULL);
diff --git a/src/gatt-database.h b/src/gatt-database.h
index 0d9106b..163b601 100644
--- a/src/gatt-database.h
+++ b/src/gatt-database.h
@@ -23,3 +23,14 @@ 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);
+
+typedef uint8_t (*btd_gatt_database_ccc_write_t) (uint16_t value,
+ void *user_data);
+typedef void (*btd_gatt_database_destroy_t) (void *data);
+
+struct gatt_db_attribute *
+btd_gatt_database_add_ccc(struct btd_gatt_database *database,
+ uint16_t service_handle,
+ btd_gatt_database_ccc_write_t write_callback,
+ void *user_data,
+ btd_gatt_database_destroy_t destroy);
--
2.2.0.rc0.207.ga3a616c
This patch adds support for adding a CCC descriptor entry for an
external characteristic, if it has the 'notify' or 'indicate' property.
When the CCC descriptor is written to, bluetoothd calls the
'StartNotify' and 'StopNotify' methods on the characteristic in a
reference counted manner.
---
src/gatt-database.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 84 insertions(+), 6 deletions(-)
diff --git a/src/gatt-database.c b/src/gatt-database.c
index db21d49..2ca9daa 100644
--- a/src/gatt-database.c
+++ b/src/gatt-database.c
@@ -94,8 +94,10 @@ struct external_chrc {
uint8_t props;
uint8_t ext_props;
struct gatt_db_attribute *attrib;
+ struct gatt_db_attribute *ccc;
struct queue *pending_reads;
struct queue *pending_writes;
+ unsigned int ntfy_cnt;
};
struct pending_op {
@@ -1171,10 +1173,13 @@ static void proxy_added_cb(GDBusProxy *proxy, void *user_data)
return;
}
- /*
- * TODO: Determine descriptors count to add based on special
- * characteristic properties (e.g. extended properties).
- */
+ if ((chrc->props & BT_GATT_CHRC_PROP_NOTIFY ||
+ chrc->props & BT_GATT_CHRC_PROP_INDICATE) &&
+ !incr_attr_count(service, 1)) {
+ error("Failed to increment attribute count for CCC");
+ service->failed = true;
+ return;
+ }
queue_push_tail(service->chrcs, chrc);
} else
@@ -1507,6 +1512,73 @@ static uint32_t permissions_from_props(uint8_t props, uint8_t ext_props)
return perm;
}
+static uint8_t ccc_write_cb(uint16_t value, void *user_data)
+{
+ struct external_chrc *chrc = user_data;
+
+ DBG("External CCC write received with value: 0x%04x", value);
+
+ /* Notifications/indications disabled */
+ if (!value) {
+ if (!chrc->ntfy_cnt)
+ return 0;
+
+ if (__sync_sub_and_fetch(&chrc->ntfy_cnt, 1))
+ return 0;
+
+ /*
+ * Send request to stop notifying. This is best-effort
+ * operation, so simply ignore the return the value.
+ */
+ g_dbus_proxy_method_call(chrc->proxy, "StopNotify", NULL,
+ NULL, NULL, NULL);
+ return 0;
+ }
+
+ /*
+ * TODO: All of the errors below should fall into the so called
+ * "Application Error" range. Since there is no well defined error for
+ * these, we return a generic ATT protocol error for now.
+ */
+
+ if (chrc->ntfy_cnt == UINT_MAX) {
+ /* Maximum number of per-device CCC descriptors configured */
+ return BT_ATT_ERROR_REQUEST_NOT_SUPPORTED;
+ }
+
+ /* Don't support undefined CCC values yet */
+ if (value > 2 ||
+ (value == 1 && !(chrc->props & BT_GATT_CHRC_PROP_NOTIFY)) ||
+ (value == 2 && !(chrc->props & BT_GATT_CHRC_PROP_INDICATE)))
+ return BT_ATT_ERROR_REQUEST_NOT_SUPPORTED;
+
+ /*
+ * Always call StartNotify for an incoming enable and ignore the return
+ * value for now.
+ */
+ if (g_dbus_proxy_method_call(chrc->proxy,
+ "StartNotify", NULL, NULL,
+ NULL, NULL) == FALSE)
+ return BT_ATT_ERROR_REQUEST_NOT_SUPPORTED;
+
+ __sync_fetch_and_add(&chrc->ntfy_cnt, 1);
+
+ return 0;
+}
+
+static bool create_ccc_entry(struct external_service *service,
+ struct external_chrc *chrc)
+{
+ chrc->ccc = service_add_ccc(service->attrib, service->database,
+ ccc_write_cb, chrc, NULL);
+ if (!chrc->ccc) {
+ error("Failed to create CCC entry for characteristic");
+ return false;
+ }
+
+ return true;
+}
+
static bool database_add_chrc(struct external_service *service,
struct external_chrc *chrc)
{
@@ -1533,10 +1605,16 @@ static bool database_add_chrc(struct external_service *service,
&uuid, perm,
chrc->props, chrc_read_cb,
chrc_write_cb, chrc);
+ if (!chrc->attrib) {
+ error("Failed to create characteristic entry in database");
+ return false;
+ }
- /* TODO: Create descriptor entries */
+ if (chrc->props & BT_GATT_CHRC_PROP_NOTIFY ||
+ chrc->props & BT_GATT_CHRC_PROP_INDICATE)
+ return create_ccc_entry(service, chrc);
- return chrc->attrib != NULL;
+ return true;
}
static bool create_service_entry(struct external_service *service)
--
2.2.0.rc0.207.ga3a616c