From: Chaojie Gu <[email protected]>
This patch focus on implementing the Gatt DBus API defined in gatt-api.txt discussed in mailist latestly. It implement all the DBus property and method except for array{object} of characteristics and descriptors. Through our internal test, patche work well. I would like to contribute this featrue for bluez upstream. This first patch is just an implementation, maybe not very closely suitable for design or put in the right file. It would be OK to modify to make more sense.
Arman, Marcel, would you like to review the patch and give some suggestion for the patch? It would be great honor for me to contribute little to bluez.
---
src/device.c | 1602 +++++++++++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 1412 insertions(+), 190 deletions(-)
diff --git a/src/device.c b/src/device.c
index d63e627..a53a4e9 100644
--- a/src/device.c
+++ b/src/device.c
@@ -71,6 +71,13 @@
#define DISCONNECT_TIMER 2
#define DISCOVERY_TIMER 1
+#define DESC_CHANGED_TIMER 2
+
+
+#define GATT_SERVICE_IFACE "org.bluez.GattService1"
+#define GATT_CHR_IFACE "org.bluez.GattCharacteristic1"
+#define GATT_DESCRIPTOR_IFACE "org.bluez.GattDescriptor1"
+#define GATT_DESC_UUID_HEAD "000029"
static DBusConnection *dbus_conn = NULL;
unsigned service_state_cb_id;
@@ -126,8 +133,48 @@ struct browse_req {
struct included_search {
struct browse_req *req;
+ uint8_t primary_count;
GSList *services;
GSList *current;
+ GSList *includes;
+};
+
+struct service_info_search {
+ struct btd_device *device;
+ struct gatt_primary *prim;
+ char *service_path;
+ bool primary;
+ GSList *includes;
+ GSList *char_info_list;
+
+};
+
+struct char_info_search {
+ struct service_info_search *service_info;
+ struct gatt_char *chr;
+ char *char_path;
+ uint8_t *value;
+ int vlen;
+ uint8_t *set_value;
+ int set_len;
+ char **prop_array;
+ int array_size;
+ bool changed;
+ bool notified;
+ GSList *desc_info_list;
+ DBusMessage *msg;
+};
+
+struct desc_info_search {
+ struct char_info_search *char_info;
+ struct gatt_desc *desc;
+ char *desc_path;
+ uint8_t *value;
+ int vlen;
+ uint8_t *set_value;
+ int set_len;
+ guint changed_timer;
+ DBusMessage *msg;
};
struct attio_data {
@@ -238,9 +285,19 @@ static const uint16_t uuid_list[] = {
0
};
+static GSList *service_info_list = NULL;
+
static int device_browse_primary(struct btd_device *device, DBusMessage *msg);
static int device_browse_sdp(struct btd_device *device, DBusMessage *msg);
+static int include_by_range_cmp(gconstpointer a, gconstpointer b)
+{
+ const struct gatt_included *include = a;
+ const struct att_range *range = b;
+
+ return memcmp(&include->range, range, sizeof(*range));
+}
+
static struct bearer_state *get_state(struct btd_device *dev,
uint8_t bdaddr_type)
{
@@ -1983,263 +2040,1039 @@ static const GDBusPropertyTable device_properties[] = {
{ }
};
-uint8_t btd_device_get_bdaddr_type(struct btd_device *dev)
+static int prop_bit_count(uint8_t num)
{
- return dev->bdaddr_type;
+ int count = 0;
+
+ while (num != 0) {
+ count++;
+ num = num & (num - 1);
+ }
+
+ return count;
}
-bool btd_device_is_connected(struct btd_device *dev)
+static int read_value_cmp(gconstpointer a, gconstpointer b, size_t n)
{
- return dev->bredr_state.connected || dev->le_state.connected;
+ return memcmp(a, b, n);
}
-void device_add_connection(struct btd_device *dev, uint8_t bdaddr_type)
+static void char_read_value_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data)
{
- struct bearer_state *state = get_state(dev, bdaddr_type);
-
- device_update_last_seen(dev, bdaddr_type);
+ struct char_info_search *char_info = user_data;
+ uint8_t value[plen];
+ ssize_t vlen;
+ DBusMessage *reply;
- if (state->connected) {
- char addr[18];
- ba2str(&dev->bdaddr, addr);
- error("Device %s is already connected", addr);
- return;
+ if (status != 0) {
+ reply = btd_error_failed(char_info->msg,
+ att_ecode2str(status));
+ goto done;
}
- /* If this is the first connection over this bearer */
- if (bdaddr_type == BDADDR_BREDR)
- device_set_bredr_support(dev);
- else
- device_set_le_support(dev, bdaddr_type);
+ vlen = dec_read_resp(pdu, plen, value, sizeof(value));
+ if (vlen < 0) {
+ reply = btd_error_failed(char_info->msg,
+ "Protocol error");
+ goto done;
+ }
- state->connected = true;
+ if ((char_info->vlen != vlen) ||
+ (read_value_cmp(char_info->value, value, vlen))) {
+ if (char_info->vlen < vlen) {
+ g_free(char_info->value);
+ char_info->value = g_malloc0(vlen);
+ }
- if (dev->le_state.connected && dev->bredr_state.connected)
- return;
+ memcpy(char_info->value, value, vlen);
+ char_info->vlen = vlen;
- g_dbus_emit_property_changed(dbus_conn, dev->path, DEVICE_INTERFACE,
- "Connected");
-}
+ g_dbus_emit_property_changed(dbus_conn, char_info->char_path,
+ GATT_CHR_IFACE, "Value");
+ }
-void device_remove_connection(struct btd_device *device, uint8_t bdaddr_type)
-{
- struct bearer_state *state = get_state(device, bdaddr_type);
+ reply = dbus_message_new_method_return(char_info->msg);
- if (!state->connected)
- return;
+ dbus_message_append_args(reply,
+ DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
+ &char_info->value,
+ char_info->vlen,
+ DBUS_TYPE_INVALID);
- state->connected = false;
- device->svc_refreshed = false;
- device->general_connect = FALSE;
+done:
+ dbus_message_unref(char_info->msg);
+ char_info->msg = NULL;
- if (device->disconn_timer > 0) {
- g_source_remove(device->disconn_timer);
- device->disconn_timer = 0;
- }
+ g_dbus_send_message(dbus_conn, reply);
+}
- while (device->disconnects) {
- DBusMessage *msg = device->disconnects->data;
+static void char_write_req_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data)
+{
+ struct char_info_search *char_info = user_data;
+ DBusMessage *reply;
- g_dbus_send_reply(dbus_conn, msg, DBUS_TYPE_INVALID);
- device->disconnects = g_slist_remove(device->disconnects, msg);
- dbus_message_unref(msg);
+ DBG("");
+ if (status != 0) {
+ reply = btd_error_failed(char_info->msg,
+ att_ecode2str(status));
+ goto done;
}
- if (state->paired && !state->bonded)
- btd_adapter_remove_bonding(device->adapter, &device->bdaddr,
- bdaddr_type);
+ if (!dec_write_resp(pdu, plen) && !dec_exec_write_resp(pdu, plen)) {
+ reply = btd_error_failed(char_info->msg,
+ "Protocol error");
+ goto done;
+ }
- if (device->bredr_state.connected || device->le_state.connected)
- return;
+ DBG("Characteristic value was written successfully");
- g_dbus_emit_property_changed(dbus_conn, device->path,
- DEVICE_INTERFACE, "Connected");
-}
+ if (char_info->vlen < char_info->set_len) {
+ g_free(char_info->value);
+ char_info->value = g_malloc0(char_info->set_len);
+ }
-guint device_add_disconnect_watch(struct btd_device *device,
- disconnect_watch watch, void *user_data,
- GDestroyNotify destroy)
-{
- struct btd_disconnect_data *data;
- static guint id = 0;
+ char_info->vlen = char_info->set_len;
+ memcpy(char_info->value, char_info->set_value, char_info->set_len);
- data = g_new0(struct btd_disconnect_data, 1);
- data->id = ++id;
- data->watch = watch;
- data->user_data = user_data;
- data->destroy = destroy;
+ g_free(char_info->set_value);
+ char_info->set_len = 0;
- device->watches = g_slist_append(device->watches, data);
+ reply = dbus_message_new_method_return(char_info->msg);
- return data->id;
+ g_dbus_emit_property_changed(dbus_conn, char_info->char_path,
+ GATT_CHR_IFACE, "Value");
+done:
+ g_dbus_send_message(dbus_conn, reply);
+ dbus_message_unref(char_info->msg);
+ char_info->msg = NULL;
}
-void device_remove_disconnect_watch(struct btd_device *device, guint id)
+static void desc_read_value_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data)
{
- GSList *l;
+ struct desc_info_search *desc_info = user_data;
+ uint8_t value[plen];
+ ssize_t vlen;
+ DBusMessage *reply;
- for (l = device->watches; l; l = l->next) {
- struct btd_disconnect_data *data = l->data;
+ if (status != 0) {
+ reply = btd_error_failed(desc_info->msg,
+ att_ecode2str(status));
+ goto done;
+ }
- if (data->id == id) {
- device->watches = g_slist_remove(device->watches,
- data);
- if (data->destroy)
- data->destroy(data->user_data);
- g_free(data);
- return;
- }
+ vlen = dec_read_resp(pdu, plen, value, sizeof(value));
+ if (vlen < 0) {
+ reply = btd_error_failed(desc_info->msg,
+ "Protocol error");
+ goto done;
}
-}
-static char *load_cached_name(struct btd_device *device, const char *local,
- const char *peer)
-{
- char filename[PATH_MAX];
- GKeyFile *key_file;
- char *str = NULL;
- int len;
+ if ((desc_info->vlen != vlen) ||
+ (read_value_cmp(desc_info->value, value, vlen))) {
+ if (desc_info->vlen < vlen) {
+ g_free(desc_info->value);
+ desc_info->value = g_malloc0(vlen);
+ }
- snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", local, peer);
+ memcpy(desc_info->value, value, vlen);
+ desc_info->vlen = vlen;
- key_file = g_key_file_new();
+ g_dbus_emit_property_changed(dbus_conn, desc_info->desc_path,
+ GATT_DESCRIPTOR_IFACE, "Value");
+ }
- if (!g_key_file_load_from_file(key_file, filename, 0, NULL))
- goto failed;
+ reply = dbus_message_new_method_return(desc_info->msg);
- str = g_key_file_get_string(key_file, "General", "Name", NULL);
- if (str) {
- len = strlen(str);
- if (len > HCI_MAX_NAME_LENGTH)
- str[HCI_MAX_NAME_LENGTH] = '\0';
- }
+ dbus_message_append_args(reply,
+ DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE,
+ &desc_info->value,
+ desc_info->vlen,
+ DBUS_TYPE_INVALID);
-failed:
- g_key_file_free(key_file);
+done:
+ dbus_message_unref(desc_info->msg);
+ desc_info->msg = NULL;
- return str;
+ g_dbus_send_message(dbus_conn, reply);
}
-static void load_info(struct btd_device *device, const char *local,
- const char *peer, GKeyFile *key_file)
+static void desc_write_req_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data)
{
- char *str;
- gboolean store_needed = FALSE;
- gboolean blocked;
- char **uuids;
- int source, vendor, product, version;
- char **techno, **t;
+ struct desc_info_search *desc_info = user_data;
+ DBusMessage *reply;
- /* Load device name from storage info file, if that fails fall back to
- * the cache.
- */
- str = g_key_file_get_string(key_file, "General", "Name", NULL);
- if (str == NULL) {
- str = load_cached_name(device, local, peer);
- if (str)
- store_needed = TRUE;
+ if (status != 0) {
+ reply = btd_error_failed(desc_info->msg,
+ att_ecode2str(status));
+ g_dbus_send_message(dbus_conn, reply);
+ goto done;
}
- if (str) {
- strcpy(device->name, str);
- g_free(str);
+ if (!dec_write_resp(pdu, plen) && !dec_exec_write_resp(pdu, plen)) {
+ reply = btd_error_failed(desc_info->msg,
+ "Protocol error");
+ g_dbus_send_message(dbus_conn, reply);
+ goto done;
}
- /* Load alias */
- device->alias = g_key_file_get_string(key_file, "General", "Alias",
- NULL);
-
- /* Load class */
- str = g_key_file_get_string(key_file, "General", "Class", NULL);
- if (str) {
- uint32_t class;
+ DBG("Descriptor value was written successfully");
- if (sscanf(str, "%x", &class) == 1)
- device->class = class;
- g_free(str);
+ if (desc_info->vlen < desc_info->set_len) {
+ g_free(desc_info->value);
+ desc_info->value = g_malloc0(desc_info->set_len);
}
- /* Load appearance */
- str = g_key_file_get_string(key_file, "General", "Appearance", NULL);
- if (str) {
- device->appearance = strtol(str, NULL, 16);
- g_free(str);
- }
+ desc_info->vlen = desc_info->set_len;
+ memcpy(desc_info->value, desc_info->set_value, desc_info->set_len);
- /* Load device technology */
- techno = g_key_file_get_string_list(key_file, "General",
- "SupportedTechnologies", NULL, NULL);
- if (!techno)
- goto next;
+ g_free(desc_info->set_value);
+ desc_info->set_len = 0;
- for (t = techno; *t; t++) {
- if (g_str_equal(*t, "BR/EDR"))
- device->bredr = true;
- else if (g_str_equal(*t, "LE"))
- device->le = true;
- else
- error("Unknown device technology");
- }
+ g_dbus_emit_property_changed(dbus_conn, desc_info->desc_path,
+ GATT_DESCRIPTOR_IFACE, "Value");
+done:
+ dbus_message_unref(desc_info->msg);
+ desc_info->msg = NULL;
+}
- if (!device->le) {
- device->bdaddr_type = BDADDR_BREDR;
- } else {
- str = g_key_file_get_string(key_file, "General",
- "AddressType", NULL);
+static int get_char_flags_array(struct char_info_search *char_info)
+{
+ char **prop_array;
+ int size, length, i = 0;
+ uint8_t properties = char_info->chr->properties;
- if (str && g_str_equal(str, "public"))
- device->bdaddr_type = BDADDR_LE_PUBLIC;
- else if (str && g_str_equal(str, "static"))
- device->bdaddr_type = BDADDR_LE_RANDOM;
- else
- error("Unknown LE device technology");
+ size = prop_bit_count(properties);
- g_free(str);
- }
+ char_info->prop_array = (char **)g_malloc0(sizeof(char*)*size);
- g_strfreev(techno);
+ prop_array = char_info->prop_array;
-next:
- /* Load trust */
- device->trusted = g_key_file_get_boolean(key_file, "General",
- "Trusted", NULL);
+ if (prop_array != NULL) {
+ if (properties & GATT_CHR_PROP_BROADCAST) {
+ length = strlen("broadcast");
+ *(prop_array+i) =
+ (char *)g_malloc0(sizeof(char*)*length);
+ strcpy(*(prop_array+i), "broadcast");
+ i++;
+ }
- /* Load device blocked */
- blocked = g_key_file_get_boolean(key_file, "General", "Blocked", NULL);
- if (blocked)
- device_block(device, FALSE);
+ if (properties & GATT_CHR_PROP_READ) {
+ length = strlen("read");
+ *(prop_array+i) =
+ (char *)g_malloc0(sizeof(char*)*length);
+ strcpy(*(prop_array+i), "read");
+ i++;
+ }
- /* Load device profile list */
- uuids = g_key_file_get_string_list(key_file, "General", "Services",
- NULL, NULL);
- if (uuids) {
- char **uuid;
+ if (properties & GATT_CHR_PROP_WRITE_WITHOUT_RESP) {
+ length = strlen("write-without-response");
+ *(prop_array+i) =
+ (char *)g_malloc0(sizeof(char*)*length);
+ strcpy(*(prop_array+i), "write-without-response");
+ i++;
+ }
- for (uuid = uuids; *uuid; uuid++) {
- GSList *match;
+ if (properties & GATT_CHR_PROP_WRITE) {
+ length = strlen("write");
+ *(prop_array+i) =
+ (char *)g_malloc0(sizeof(char*)*length);
+ strcpy(*(prop_array+i), "write");
+ i++;
+ }
- match = g_slist_find_custom(device->uuids, *uuid,
- bt_uuid_strcmp);
- if (match)
- continue;
+ if (properties & GATT_CHR_PROP_NOTIFY) {
+ length = strlen("notify");
+ *(prop_array+i) =
+ (char *)g_malloc0(sizeof(char*)*length);
+ strcpy(*(prop_array+i), "notify");
+ i++;
+ }
- device->uuids = g_slist_insert_sorted(device->uuids,
- g_strdup(*uuid),
- bt_uuid_strcmp);
+ if (properties & GATT_CHR_PROP_INDICATE) {
+ length = strlen("indicate");
+ *(prop_array+i) =
+ (char *)g_malloc0(sizeof(char*)*length);
+ strcpy(*(prop_array+i), "indicate");
+ i++;
}
- g_strfreev(uuids);
- /* Discovered services restored from storage */
- device->bredr_state.svc_resolved = true;
+ if (properties & GATT_CHR_PROP_AUTH) {
+ length = strlen("authenticated-signed-writes");
+ *(prop_array+i) =
+ (char *)g_malloc0(sizeof(char*)*length);
+ strcpy(*(prop_array+i), "authenticated-signed-writes");
+ i++;
+ }
}
- /* Load device id */
- source = g_key_file_get_integer(key_file, "DeviceID", "Source", NULL);
- if (source) {
- vendor = g_key_file_get_integer(key_file, "DeviceID",
- "Vendor", NULL);
+ return size;
+}
- product = g_key_file_get_integer(key_file, "DeviceID",
+static gboolean service_get_uuid(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct service_info_search *service_info = data;
+ const char *ptr = service_info->prim->uuid;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ptr);
+
+ return TRUE;
+}
+
+static gboolean service_get_primary(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct service_info_search *service_info = data;
+ gboolean val = service_info->primary;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val);
+
+ return TRUE;
+}
+
+static gboolean service_get_device(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct service_info_search *service_info = data;
+ const char *str = service_info->device->path;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &str);
+
+ return TRUE;
+}
+
+static gboolean service_get_includes(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct service_info_search *service_info = data;
+ DBusMessageIter entry;
+ GSList *l;
+
+ dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_OBJECT_PATH_AS_STRING,
+ &entry);
+
+ for (l = service_info_list; l; l = l->next) {
+ struct service_info_search *value = l->data;
+ struct gatt_primary *prim = value->prim;
+
+ if (g_slist_find_custom(service_info->includes, &prim->range,
+ include_by_range_cmp)) {
+ const char *path = value->service_path;
+
+ dbus_message_iter_append_basic(&entry,
+ DBUS_TYPE_OBJECT_PATH, &path);
+ }
+ }
+
+ dbus_message_iter_close_container(iter, &entry);
+
+ return TRUE;
+}
+
+static gboolean service_exist_includes(const GDBusPropertyTable *property,
+ void *data)
+{
+ struct service_info_search *service_info = data;
+
+ if (service_info->includes)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static DBusMessage *char_read_value(DBusConnection *conn, DBusMessage *msg,
+ void *user_data)
+{
+ struct char_info_search *char_info = user_data;
+ struct btd_device *device = char_info->service_info->device;
+ uint8_t properties = char_info->chr->properties;
+
+ if (properties & GATT_CHR_PROP_READ) {
+ char_info->msg = dbus_message_ref(msg);
+
+ gatt_read_char(device->attrib,
+ char_info->chr->value_handle,
+ char_read_value_cb, char_info);
+ } else
+ return btd_error_not_supported(msg);
+
+ return NULL;
+}
+
+static DBusMessage *char_write_value(DBusConnection *conn, DBusMessage *msg,
+ void *user_data)
+{
+ struct char_info_search *char_info = user_data;
+ struct btd_device *device = char_info->service_info->device;
+ DBusMessageIter iter, sub;
+ uint8_t propmask;
+ uint8_t *value;
+ int len;
+
+ DBG("");
+
+ if (!dbus_message_iter_init(msg, &iter))
+ return btd_error_invalid_args(msg);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
+ dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_BYTE)
+ return btd_error_invalid_args(msg);
+
+ dbus_message_iter_recurse(&iter, &sub);
+
+ dbus_message_iter_get_fixed_array(&sub, &value, &len);
+
+ propmask = char_info->chr->properties;
+
+ if (len == 0)
+ return btd_error_invalid_args(msg);
+
+ if (propmask & GATT_CHR_PROP_WRITE) {
+ char_info->msg = dbus_message_ref(msg);
+ char_info->set_value = g_memdup(value, len);
+ char_info->set_len = len;
+
+ gatt_write_char(device->attrib, char_info->chr->value_handle,
+ value, len, char_write_req_cb, char_info);
+ } else if (propmask & GATT_CHR_PROP_WRITE_WITHOUT_RESP) {
+ gatt_write_cmd(device->attrib, char_info->chr->value_handle,
+ value, len, NULL, NULL);
+ char_info->vlen = 0;
+ } else
+ return btd_error_not_supported(msg);
+
+ return NULL;
+}
+
+static DBusMessage *start_notify(DBusConnection *conn, DBusMessage *msg,
+ void *user_data)
+{
+ struct char_info_search *char_info = user_data;
+ struct btd_device *device = char_info->service_info->device;
+ uint8_t properties = char_info->chr->properties;
+ GSList *desc_info_list = char_info->desc_info_list;
+ GSList *l;
+ uint8_t attr_val[2];
+ int len;
+
+ if (properties & GATT_CHR_PROP_NOTIFY) {
+ if (char_info->notified)
+ return btd_error_busy(msg);
+
+ for (l = desc_info_list; l; l = l->next) {
+ struct desc_info_search *desc_info = l->data;
+
+ if (desc_info->desc->uuid16 ==
+ GATT_CLIENT_CHARAC_CFG_UUID) {
+ put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT,
+ attr_val);
+ len = sizeof(attr_val);
+
+ desc_info->set_value =
+ g_memdup(attr_val, len);
+ desc_info->set_len = len;
+ desc_info->msg = dbus_message_ref(msg);
+
+ gatt_write_char(device->attrib,
+ desc_info->desc->handle,
+ attr_val, len,
+ desc_write_req_cb,
+ desc_info);
+ }
+ }
+ } else
+ return btd_error_not_supported(msg);
+
+ return NULL;
+}
+
+static DBusMessage *stop_notify(DBusConnection *conn, DBusMessage *msg,
+ void *user_data)
+{
+ struct char_info_search *char_info = user_data;
+ struct btd_device *device = char_info->service_info->device;
+ uint8_t properties = char_info->chr->properties;
+ GSList *desc_info_list = char_info->desc_info_list;
+ GSList *l;
+ uint8_t attr_val[2];
+ int len;
+
+ if (properties & GATT_CHR_PROP_NOTIFY) {
+ if (!char_info->notified)
+ return btd_error_failed(msg, "notify already off");
+
+ for (l = desc_info_list; l; l = l->next) {
+ struct desc_info_search *desc_info = l->data;
+
+ if (desc_info->desc->uuid16 ==
+ GATT_CLIENT_CHARAC_CFG_UUID) {
+ put_le16(0x0000, attr_val);
+ len = sizeof(attr_val);
+
+ desc_info->set_value =
+ g_memdup(attr_val, len);
+ desc_info->set_len = len;
+ desc_info->msg = dbus_message_ref(msg);
+
+ gatt_write_char(device->attrib,
+ desc_info->desc->handle,
+ attr_val, len,
+ desc_write_req_cb,
+ desc_info);
+ }
+ }
+ } else
+ return btd_error_not_supported(msg);
+
+ return NULL;
+}
+
+static gboolean chr_get_uuid(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct char_info_search *char_info = data;
+ const char *ptr = char_info->chr->uuid;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ptr);
+
+ return TRUE;
+}
+
+static gboolean chr_get_service(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct char_info_search *char_info = data;
+ const char *str = char_info->service_info->service_path;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &str);
+
+ return TRUE;
+}
+
+static gboolean chr_get_value(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct char_info_search *char_info = data;
+
+ DBusMessageIter array;
+
+ dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_BYTE_AS_STRING, &array);
+
+ dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE,
+ &char_info->value,
+ char_info->vlen);
+
+ dbus_message_iter_close_container(iter, &array);
+
+ return TRUE;
+}
+
+static gboolean chr_exist_value(const GDBusPropertyTable *property,
+ void *data)
+{
+ struct char_info_search *char_info = data;
+
+ if (char_info->vlen)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static gboolean chr_get_notifying(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct char_info_search *char_info = data;
+ uint8_t properties = char_info->chr->properties;
+ gboolean val;
+
+ if (properties & GATT_CHR_PROP_NOTIFY)
+ val = char_info->notified;
+ else
+ val = FALSE;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val);
+
+ return TRUE;
+}
+
+static gboolean chr_get_props(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct char_info_search *char_info = data;
+ DBusMessageIter array;
+ char **props;
+ int i, size;
+
+ dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_STRING_AS_STRING, &array);
+
+ size = get_char_flags_array(char_info);
+
+ props = char_info->prop_array;
+
+ for (i = 0; i < size; i++)
+ dbus_message_iter_append_basic(&array,
+ DBUS_TYPE_STRING, &props[i]);
+
+ dbus_message_iter_close_container(iter, &array);
+
+ return TRUE;
+}
+
+static DBusMessage *desc_read_value(DBusConnection *conn, DBusMessage *msg,
+ void *user_data)
+{
+ struct desc_info_search *desc_info = user_data;
+ struct char_info_search *char_info = desc_info->char_info;
+ struct btd_device *device = char_info->service_info->device;
+
+ desc_info->msg = dbus_message_ref(msg);
+
+ gatt_read_char(device->attrib,
+ desc_info->desc->handle,
+ desc_read_value_cb, desc_info);
+
+ return NULL;
+}
+
+static DBusMessage *desc_write_value(DBusConnection *conn, DBusMessage *msg,
+ void *user_data)
+{
+ struct desc_info_search *desc_info = user_data;
+ struct char_info_search *char_info = desc_info->char_info;
+ struct btd_device *device = char_info->service_info->device;
+ DBusMessageIter iter, sub;
+ uint8_t *value;
+ int len;
+
+ if (!dbus_message_iter_init(msg, &iter))
+ return btd_error_invalid_args(msg);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
+ dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_BYTE)
+ return btd_error_invalid_args(msg);
+
+ dbus_message_iter_recurse(&iter, &sub);
+
+ dbus_message_iter_get_fixed_array(&sub, &value, &len);
+
+ desc_info->msg = dbus_message_ref(msg);
+ desc_info->set_value = g_memdup(value, len);
+ desc_info->set_len = len;
+
+ gatt_write_char(device->attrib, desc_info->desc->handle,
+ value, len, desc_write_req_cb, desc_info);
+
+ return NULL;
+}
+
+static gboolean desc_get_uuid(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct desc_info_search *desc_info = data;
+ const char *ptr = desc_info->desc->uuid;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ptr);
+
+ return TRUE;
+}
+
+static gboolean desc_get_chr(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct desc_info_search *desc_info = data;
+ const char *str = desc_info->char_info->char_path;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &str);
+
+ return TRUE;
+}
+
+static gboolean desc_get_value(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct desc_info_search *desc_info = data;
+
+ DBusMessageIter array;
+
+ dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_BYTE_AS_STRING, &array);
+
+ dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE,
+ &desc_info->value,
+ desc_info->vlen);
+
+ dbus_message_iter_close_container(iter, &array);
+
+ return TRUE;
+}
+
+static gboolean desc_exist_value(const GDBusPropertyTable *property,
+ void *data)
+{
+ struct desc_info_search *desc_info = data;
+
+ if (desc_info->vlen)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static void service_info_destroy(gpointer user_data)
+{
+ struct service_info_search *service_info = user_data;
+
+ service_info_list = g_slist_remove(service_info_list, service_info);
+
+ g_slist_free_full(service_info->includes, g_free);
+ g_slist_free(service_info->char_info_list);
+ g_free(service_info->prim);
+ g_free(service_info->service_path);
+ g_free(service_info);
+}
+
+static void char_info_destroy(gpointer user_data)
+{
+ struct char_info_search *char_info = user_data;
+ GSList *char_info_list = char_info->service_info->char_info_list;
+ int i;
+
+ char_info_list = g_slist_remove(char_info_list, char_info);
+
+ for (i = 0; char_info->array_size; i++)
+ g_free(char_info->prop_array[i]);
+
+ g_slist_free(char_info->desc_info_list);
+ g_free(char_info->prop_array);
+ g_free(char_info->chr);
+ g_free(char_info->value);
+ g_free(char_info->char_path);
+ g_free(char_info);
+}
+
+static void desc_info_destroy(gpointer user_data)
+{
+ struct desc_info_search *desc_info = user_data;
+ GSList *desc_info_list = desc_info->char_info->desc_info_list;
+
+ desc_info_list = g_slist_remove(desc_info_list, desc_info);
+
+ g_free(desc_info->desc);
+ g_free(desc_info->value);
+ g_free(desc_info->desc_path);
+ g_free(desc_info);
+}
+
+static const GDBusPropertyTable service_properties[] = {
+ { "UUID", "s", service_get_uuid },
+ { "Primary", "b", service_get_primary },
+ { "Device", "o", service_get_device },
+ { "Includes", "ao", service_get_includes, NULL,
+ service_exist_includes },
+ { }
+};
+
+static const GDBusMethodTable chr_methods[] = {
+ { GDBUS_ASYNC_METHOD("ReadValue",
+ NULL, GDBUS_ARGS({ "value", "ay" }),
+ char_read_value) },
+ { GDBUS_ASYNC_METHOD("WriteValue",
+ GDBUS_ARGS({ "value", "ay" }), NULL,
+ char_write_value) },
+ { GDBUS_ASYNC_METHOD("StartNotify", NULL, NULL, start_notify) },
+ { GDBUS_ASYNC_METHOD("StopNotify", NULL, NULL, stop_notify) },
+ { }
+};
+
+static const GDBusPropertyTable chr_properties[] = {
+ { "UUID", "s", chr_get_uuid },
+ { "Service", "o", chr_get_service },
+ { "Value", "ay", chr_get_value, NULL, chr_exist_value },
+ { "Notifying", "b", chr_get_notifying },
+ { "Flags", "as", chr_get_props },
+ { }
+};
+
+static const GDBusMethodTable desc_methods[] = {
+ { GDBUS_ASYNC_METHOD("ReadValue",
+ NULL, GDBUS_ARGS({ "value", "ay" }),
+ desc_read_value) },
+ { GDBUS_ASYNC_METHOD("WriteValue",
+ GDBUS_ARGS({ "value", "ay" }), NULL,
+ desc_write_value) },
+ { }
+};
+
+static const GDBusPropertyTable desc_properties[] = {
+ { "UUID", "s", desc_get_uuid },
+ { "Characteristic", "o", desc_get_chr },
+ { "Value", "ay", desc_get_value, NULL, desc_exist_value },
+ { }
+};
+
+uint8_t btd_device_get_bdaddr_type(struct btd_device *dev)
+{
+ return dev->bdaddr_type;
+}
+
+bool btd_device_is_connected(struct btd_device *dev)
+{
+ return dev->bredr_state.connected || dev->le_state.connected;
+}
+
+void device_add_connection(struct btd_device *dev, uint8_t bdaddr_type)
+{
+ struct bearer_state *state = get_state(dev, bdaddr_type);
+
+ device_update_last_seen(dev, bdaddr_type);
+
+ if (state->connected) {
+ char addr[18];
+ ba2str(&dev->bdaddr, addr);
+ error("Device %s is already connected", addr);
+ return;
+ }
+
+ /* If this is the first connection over this bearer */
+ if (bdaddr_type == BDADDR_BREDR)
+ device_set_bredr_support(dev);
+ else
+ device_set_le_support(dev, bdaddr_type);
+
+ state->connected = true;
+
+ if (dev->le_state.connected && dev->bredr_state.connected)
+ return;
+
+ g_dbus_emit_property_changed(dbus_conn, dev->path, DEVICE_INTERFACE,
+ "Connected");
+}
+
+void device_remove_connection(struct btd_device *device, uint8_t bdaddr_type)
+{
+ struct bearer_state *state = get_state(device, bdaddr_type);
+
+ if (!state->connected)
+ return;
+
+ state->connected = false;
+ device->svc_refreshed = false;
+ device->general_connect = FALSE;
+
+ if (device->disconn_timer > 0) {
+ g_source_remove(device->disconn_timer);
+ device->disconn_timer = 0;
+ }
+
+ while (device->disconnects) {
+ DBusMessage *msg = device->disconnects->data;
+
+ g_dbus_send_reply(dbus_conn, msg, DBUS_TYPE_INVALID);
+ device->disconnects = g_slist_remove(device->disconnects, msg);
+ dbus_message_unref(msg);
+ }
+
+ if (state->paired && !state->bonded)
+ btd_adapter_remove_bonding(device->adapter, &device->bdaddr,
+ bdaddr_type);
+
+ if (device->bredr_state.connected || device->le_state.connected)
+ return;
+
+ g_dbus_emit_property_changed(dbus_conn, device->path,
+ DEVICE_INTERFACE, "Connected");
+}
+
+guint device_add_disconnect_watch(struct btd_device *device,
+ disconnect_watch watch, void *user_data,
+ GDestroyNotify destroy)
+{
+ struct btd_disconnect_data *data;
+ static guint id = 0;
+
+ data = g_new0(struct btd_disconnect_data, 1);
+ data->id = ++id;
+ data->watch = watch;
+ data->user_data = user_data;
+ data->destroy = destroy;
+
+ device->watches = g_slist_append(device->watches, data);
+
+ return data->id;
+}
+
+void device_remove_disconnect_watch(struct btd_device *device, guint id)
+{
+ GSList *l;
+
+ for (l = device->watches; l; l = l->next) {
+ struct btd_disconnect_data *data = l->data;
+
+ if (data->id == id) {
+ device->watches = g_slist_remove(device->watches,
+ data);
+ if (data->destroy)
+ data->destroy(data->user_data);
+ g_free(data);
+ return;
+ }
+ }
+}
+
+static char *load_cached_name(struct btd_device *device, const char *local,
+ const char *peer)
+{
+ char filename[PATH_MAX];
+ GKeyFile *key_file;
+ char *str = NULL;
+ int len;
+
+ snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", local, peer);
+
+ key_file = g_key_file_new();
+
+ if (!g_key_file_load_from_file(key_file, filename, 0, NULL))
+ goto failed;
+
+ str = g_key_file_get_string(key_file, "General", "Name", NULL);
+ if (str) {
+ len = strlen(str);
+ if (len > HCI_MAX_NAME_LENGTH)
+ str[HCI_MAX_NAME_LENGTH] = '\0';
+ }
+
+failed:
+ g_key_file_free(key_file);
+
+ return str;
+}
+
+static void load_info(struct btd_device *device, const char *local,
+ const char *peer, GKeyFile *key_file)
+{
+ char *str;
+ gboolean store_needed = FALSE;
+ gboolean blocked;
+ char **uuids;
+ int source, vendor, product, version;
+ char **techno, **t;
+
+ /* Load device name from storage info file, if that fails fall back to
+ * the cache.
+ */
+ str = g_key_file_get_string(key_file, "General", "Name", NULL);
+ if (str == NULL) {
+ str = load_cached_name(device, local, peer);
+ if (str)
+ store_needed = TRUE;
+ }
+
+ if (str) {
+ strcpy(device->name, str);
+ g_free(str);
+ }
+
+ /* Load alias */
+ device->alias = g_key_file_get_string(key_file, "General", "Alias",
+ NULL);
+
+ /* Load class */
+ str = g_key_file_get_string(key_file, "General", "Class", NULL);
+ if (str) {
+ uint32_t class;
+
+ if (sscanf(str, "%x", &class) == 1)
+ device->class = class;
+ g_free(str);
+ }
+
+ /* Load appearance */
+ str = g_key_file_get_string(key_file, "General", "Appearance", NULL);
+ if (str) {
+ device->appearance = strtol(str, NULL, 16);
+ g_free(str);
+ }
+
+ /* Load device technology */
+ techno = g_key_file_get_string_list(key_file, "General",
+ "SupportedTechnologies", NULL, NULL);
+ if (!techno)
+ goto next;
+
+ for (t = techno; *t; t++) {
+ if (g_str_equal(*t, "BR/EDR"))
+ device->bredr = true;
+ else if (g_str_equal(*t, "LE"))
+ device->le = true;
+ else
+ error("Unknown device technology");
+ }
+
+ if (!device->le) {
+ device->bdaddr_type = BDADDR_BREDR;
+ } else {
+ str = g_key_file_get_string(key_file, "General",
+ "AddressType", NULL);
+
+ if (str && g_str_equal(str, "public"))
+ device->bdaddr_type = BDADDR_LE_PUBLIC;
+ else if (str && g_str_equal(str, "static"))
+ device->bdaddr_type = BDADDR_LE_RANDOM;
+ else
+ error("Unknown LE device technology");
+
+ g_free(str);
+ }
+
+ g_strfreev(techno);
+
+next:
+ /* Load trust */
+ device->trusted = g_key_file_get_boolean(key_file, "General",
+ "Trusted", NULL);
+
+ /* Load device blocked */
+ blocked = g_key_file_get_boolean(key_file, "General", "Blocked", NULL);
+ if (blocked)
+ device_block(device, FALSE);
+
+ /* Load device profile list */
+ uuids = g_key_file_get_string_list(key_file, "General", "Services",
+ NULL, NULL);
+ if (uuids) {
+ char **uuid;
+
+ for (uuid = uuids; *uuid; uuid++) {
+ GSList *match;
+
+ match = g_slist_find_custom(device->uuids, *uuid,
+ bt_uuid_strcmp);
+ if (match)
+ continue;
+
+ device->uuids = g_slist_insert_sorted(device->uuids,
+ g_strdup(*uuid),
+ bt_uuid_strcmp);
+ }
+ g_strfreev(uuids);
+
+ /* Discovered services restored from storage */
+ device->bredr_state.svc_resolved = true;
+ }
+
+ /* Load device id */
+ source = g_key_file_get_integer(key_file, "DeviceID", "Source", NULL);
+ if (source) {
+ vendor = g_key_file_get_integer(key_file, "DeviceID",
+ "Vendor", NULL);
+
+ product = g_key_file_get_integer(key_file, "DeviceID",
"Product", NULL);
version = g_key_file_get_integer(key_file, "DeviceID",
@@ -3534,6 +4367,383 @@ static void send_le_browse_response(struct browse_req *req)
g_dbus_send_reply(dbus_conn, msg, DBUS_TYPE_INVALID);
}
+static gboolean gatt_desc_changed(gpointer data)
+{
+ struct desc_info_search *desc_info = data;
+ struct char_info_search *char_info = desc_info->char_info;
+
+ DBG("");
+
+ if (char_info->changed)
+ char_info->changed = FALSE;
+ else {
+ put_le16(0x0000, desc_info->value);
+
+ g_dbus_emit_property_changed(dbus_conn,
+ desc_info->desc_path,
+ GATT_DESCRIPTOR_IFACE,
+ "Value");
+
+ desc_info->changed_timer = 0;
+
+ char_info->notified = FALSE;
+
+ return FALSE;
+ }
+
+ return TRUE;
+
+}
+
+static void handle_desc_info_list(struct char_info_search *char_info)
+{
+ GSList *desc_info_list = char_info->desc_info_list;
+ GSList *l;
+
+ for (l = desc_info_list; l; l = l->next) {
+ struct desc_info_search *desc_info = l->data;
+
+ if (!char_info->notified && desc_info->value) {
+ if (desc_info->desc->uuid16 ==
+ GATT_CLIENT_CHARAC_CFG_UUID)
+ put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT,
+ desc_info->value);
+
+ g_dbus_emit_property_changed(dbus_conn,
+ desc_info->desc_path,
+ GATT_DESCRIPTOR_IFACE,
+ "Value");
+
+ desc_info->changed_timer = g_timeout_add_seconds(
+ DESC_CHANGED_TIMER,
+ gatt_desc_changed,
+ desc_info);
+
+ char_info->notified = TRUE;
+ }
+
+ }
+}
+
+static void handle_char_info_list(const uint8_t *pdu, uint16_t len,
+ GSList *char_info_list)
+{
+ GSList *l;
+ uint16_t handle;
+
+ handle = get_le16(&pdu[1]);
+
+ for (l = char_info_list; l; l = l->next) {
+ struct char_info_search *char_info = l->data;
+
+ if (char_info->chr->value_handle == handle) {
+ if (char_info->vlen >= len)
+ memcpy(char_info->value, &pdu[3], len);
+ else {
+ g_free(char_info->value);
+
+ char_info->value = g_malloc0(len);
+
+ memcpy(char_info->value, &pdu[3], len);
+
+ char_info->vlen = len;
+ }
+
+ char_info->changed = TRUE;
+
+ g_dbus_emit_property_changed(dbus_conn,
+ char_info->char_path,
+ GATT_CHR_IFACE, "Value");
+
+ if (char_info->desc_info_list)
+ handle_desc_info_list(char_info);
+ }
+ }
+}
+
+static void gatt_notify_handler(const uint8_t *pdu, uint16_t len,
+ gpointer user_data)
+{
+ uint16_t handle;
+ GSList *l;
+
+ handle = get_le16(&pdu[1]);
+
+ if (pdu[0] == ATT_OP_HANDLE_NOTIFY)
+ DBG("Notification handle = 0x%04x", handle);
+ else {
+ DBG("Invalid opcode\n");
+ return;
+ }
+
+ for (l = service_info_list; l; l = l->next) {
+ struct service_info_search *service_info = l->data;
+ GSList *char_info_list = service_info->char_info_list;
+
+ handle_char_info_list(pdu, len, char_info_list);
+ }
+}
+
+static gboolean listen_start(struct btd_device *dev)
+{
+ g_attrib_register(dev->attrib, ATT_OP_HANDLE_NOTIFY,
+ GATTRIB_ALL_HANDLES,
+ gatt_notify_handler,
+ NULL, NULL);
+ return TRUE;
+}
+
+static void desc_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data)
+{
+ struct desc_info_search *desc_info = user_data;
+ uint8_t value[plen];
+ ssize_t vlen;
+
+ if (status != 0) {
+ error("Descriptor read failed: %s",
+ att_ecode2str(status));
+ return;
+ }
+
+ vlen = dec_read_resp(pdu, plen, value, sizeof(value));
+ if (vlen < 0) {
+ error("Protocol error");
+ return;
+ }
+
+ desc_info->value = g_malloc0(vlen);
+
+ desc_info->vlen = vlen;
+
+ memcpy(desc_info->value, value, vlen);
+}
+
+static void char_read_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data)
+{
+ struct char_info_search *char_info = user_data;
+ uint8_t value[plen];
+ ssize_t vlen;
+
+ if (status != 0) {
+ error("Characteristic value read failed: %s",
+ att_ecode2str(status));
+ return;
+ }
+
+ vlen = dec_read_resp(pdu, plen, value, sizeof(value));
+ if (vlen < 0) {
+ error("Protocol error");
+ return;
+ }
+
+ char_info->value = g_malloc0(vlen);
+
+ char_info->vlen = vlen;
+
+ memcpy(char_info->value, value, vlen);
+}
+
+static void char_desc_cb(uint8_t status, GSList *descriptors, void *user_data)
+{
+ struct char_info_search *char_info = user_data;
+ struct btd_device *device = char_info->service_info->device;
+ struct desc_info_search *desc_info = NULL;
+ char *desc_path;
+ int id = 1;
+ GSList *l;
+
+ DBG("status %u", status);
+
+ if (status) {
+ error("Discover descriptors failed: %s",
+ att_ecode2str(status));
+ return;
+ }
+
+ for (l = descriptors; l; l = l->next) {
+ struct gatt_desc *desc = l->data;
+
+ if (desc->uuid == strstr(desc->uuid, GATT_DESC_UUID_HEAD)) {
+ desc_info = g_new0(struct desc_info_search, 1);
+
+ desc_info->char_info = char_info;
+
+ desc_info->desc = g_memdup(l->data,
+ sizeof(struct gatt_desc));
+
+ desc_path = g_strdup_printf("%s/descriptor%d",
+ char_info->char_path, id++);
+
+ desc_info->desc_path = desc_path;
+
+ DBG("Creating descriptor %s", desc_path);
+
+ char_info->desc_info_list =
+ g_slist_append(char_info->desc_info_list,
+ desc_info);
+
+ if (!g_dbus_register_interface(dbus_conn, desc_path,
+ GATT_DESCRIPTOR_IFACE,
+ desc_methods, NULL,
+ desc_properties, desc_info,
+ desc_info_destroy)) {
+ error("Couldn't register descriptor interface");
+ desc_info_destroy(desc_info);
+ return;
+ }
+
+ gatt_read_char(device->attrib,
+ desc_info->desc->handle,
+ desc_read_cb, desc_info);
+ }
+ }
+}
+
+static void char_discovered_cb(uint8_t status, GSList *characteristics,
+ void *user_data)
+{
+ struct service_info_search *service_info = user_data;
+ struct gatt_primary *prim = service_info->prim;
+ struct btd_device *device = service_info->device;
+ struct char_info_search *char_info = NULL;
+ struct char_info_search *prev_char_info = NULL;
+ char *char_path;
+ int id = 1;
+ GSList *l;
+
+ DBG("status %u", status);
+
+ if (status) {
+ error("Discover all characteristics failed: %s",
+ att_ecode2str(status));
+ return;
+ }
+
+ for (l = characteristics; l; l = l->next) {
+ char_info = g_new0(struct char_info_search, 1);
+
+ char_info->changed = FALSE;
+
+ char_info->notified = FALSE;
+
+ char_info->vlen = 0;
+
+ char_info->msg = NULL;
+
+ char_info->desc_info_list = NULL;
+
+ char_info->service_info = service_info;
+
+ char_info->chr = g_memdup(l->data,
+ sizeof(struct gatt_char));
+
+ char_path = g_strdup_printf("%s/char%d",
+ service_info->service_path, id++);
+
+ char_info->char_path = char_path;
+
+ DBG("Creating char %s", char_path);
+
+ service_info->char_info_list =
+ g_slist_append(service_info->char_info_list, char_info);
+
+ if (!g_dbus_register_interface(dbus_conn, char_path,
+ GATT_CHR_IFACE, chr_methods,
+ NULL, chr_properties,
+ char_info, char_info_destroy)) {
+ error("Couldn't register characteristic interface");
+ char_info_destroy(char_info);
+ return;
+ }
+
+ gatt_read_char(device->attrib,
+ char_info->chr->value_handle,
+ char_read_cb, char_info);
+
+ if (prev_char_info) {
+ gatt_discover_desc(device->attrib,
+ prev_char_info->chr->handle,
+ char_info->chr->handle, NULL,
+ char_desc_cb, prev_char_info);
+ }
+
+ prev_char_info = char_info;
+ }
+
+ if (char_info) {
+ gatt_discover_desc(device->attrib, char_info->chr->handle,
+ prim->range.end, NULL,
+ char_desc_cb, char_info);
+ }
+}
+
+static gboolean register_service(struct included_search *search)
+{
+ struct service_info_search *service_info;
+ struct gatt_primary *prim;
+ struct gatt_included *service_incl;
+ struct btd_device *device = search->req->device;
+ static int id = 1;
+ GSList *l;
+
+ prim = g_memdup(search->current->data, sizeof(struct gatt_primary));
+
+ service_info = g_new0(struct service_info_search, 1);
+
+ service_info->char_info_list = NULL;
+
+ service_info->device = device;
+
+ service_info->prim = prim;
+
+ service_info->service_path = g_strdup_printf("%s/service%d",
+ device->path, id++);
+
+ DBG("Creating service %s", service_info->service_path);
+
+ if (search->primary_count > 0) {
+ service_info->primary = TRUE;
+ search->primary_count--;
+ } else
+ service_info->primary = FALSE;
+
+ if (search->includes == NULL)
+ goto next;
+
+ for (l = search->includes; l; l = l->next) {
+ struct gatt_included *incl = l->data;
+
+ service_incl = g_new0(struct gatt_included, 1);
+
+ memcpy(service_incl->uuid, incl->uuid,
+ sizeof(service_incl->uuid));
+ memcpy(&service_incl->range, &incl->range,
+ sizeof(service_incl->range));
+
+ service_info->includes = g_slist_append(service_info->includes,
+ service_incl);
+ }
+
+
+next:
+ service_info_list = g_slist_append(service_info_list, service_info);
+
+ if (!g_dbus_register_interface(dbus_conn, service_info->service_path,
+ GATT_SERVICE_IFACE, NULL,
+ NULL, service_properties,
+ service_info, service_info_destroy)) {
+ error("Couldn't register service interface");
+ service_info_destroy(service_info);
+ return FALSE;
+ }
+
+ gatt_discover_char(device->attrib, prim->range.start, prim->range.end,
+ NULL, char_discovered_cb, service_info);
+
+ return TRUE;
+}
static void find_included_cb(uint8_t status, GSList *includes, void *user_data)
{
@@ -3563,8 +4773,11 @@ static void find_included_cb(uint8_t status, GSList *includes, void *user_data)
goto complete;
}
- if (includes == NULL)
+ if (includes == NULL) {
+ search->includes = NULL;
goto next;
+ } else
+ search->includes = includes;
for (l = includes; l; l = l->next) {
struct gatt_included *incl = l->data;
@@ -3581,10 +4794,13 @@ static void find_included_cb(uint8_t status, GSList *includes, void *user_data)
}
next:
+ register_service(search);
+
search->current = search->current->next;
if (search->current == NULL) {
register_all_services(search->req, search->services);
search->services = NULL;
+ listen_start(device);
goto complete;
}
@@ -3615,6 +4831,7 @@ static void find_included_services(struct browse_req *req, GSList *services)
search = g_new0(struct included_search, 1);
search->req = req;
+ search->primary_count = g_slist_length(services);
/* We have to completely duplicate the data in order to have a
* clearly defined responsibility of freeing regardless of
@@ -3767,6 +4984,11 @@ done:
g_dbus_send_message(dbus_conn, reply);
dbus_message_unref(device->connect);
device->connect = NULL;
+ } else {
+ if (!device->le_state.svc_resolved)
+ device_browse_primary(device, NULL);
+ else
+ listen_start(device);
}
g_free(attcb);
--
1.7.9.5
Hi Chaojie,
>> > This patch focus on implementing the Gatt DBus API defined in gatt-api.txt
>> discussed in mailist latestly. It implement all the DBus property and method
>> except for array{object} of characteristics and descriptors. Through our internal
>> test, patche work well. I would like to contribute this featrue for bluez upstream.
>> This first patch is just an implementation, maybe not very closely suitable for
>> design or put in the right file. It would be OK to modify to make more sense.
>> > Arman, Marcel, would you like to review the patch and give some suggestion
>> for the patch? It would be great honor for me to contribute little to bluez.
>>
Thank you for your patch. The current desire is to migrate all daemon
code that uses attrib/* for GATT/ATT over to the new utilities in
shared/ (gatt-client, att, gatt-helpers, etc.). In the end it's up to
the maintainers but can we hold off on implementing the D-Bus API
until the shared code is ready and just do it once the right way using
bt_gatt_client? There are enough GAttrib dependencies inside the
daemon code that adding more now will just become a bigger burden in
the future.
I recently added a set of items to the top-level TODO file regarding
the new GATT/ATT stack; I can talk to you more about tackling the
items there.
Otherwise, if this is blocking people and everyone prefers to have a
working GATT D-Bus API sooner than that, then I suggest not putting
all of the code in src/device.c. You should probably abstract that
away in a separate module (such as a src/gatt-client) and have
device.c initialize it. Also, please break this down into multiple
smaller patches as Luiz suggested.
Thanks,
Arman
PiBIaSwNCj4gDQo+IE9uIEZyaSwgQXVnIDI5LCAyMDE0IGF0IDk6NTkgQU0sIEd1IENoYW9qaWUg
PGNoYW8uamllLmd1QGludGVsLmNvbT4gd3JvdGU6DQo+ID4gRnJvbTogQ2hhb2ppZSBHdSA8Y2hh
by5qaWUuZ3VAaW50ZWwuY29tPg0KPiA+DQo+ID4gVGhpcyBwYXRjaCBmb2N1cyBvbiBpbXBsZW1l
bnRpbmcgdGhlIEdhdHQgREJ1cyBBUEkgZGVmaW5lZCBpbiBnYXR0LWFwaS50eHQNCj4gZGlzY3Vz
c2VkIGluIG1haWxpc3QgbGF0ZXN0bHkuIEl0IGltcGxlbWVudCBhbGwgdGhlIERCdXMgcHJvcGVy
dHkgYW5kIG1ldGhvZA0KPiBleGNlcHQgZm9yIGFycmF5e29iamVjdH0gb2YgY2hhcmFjdGVyaXN0
aWNzIGFuZCBkZXNjcmlwdG9ycy4gVGhyb3VnaCBvdXIgaW50ZXJuYWwNCj4gdGVzdCwgcGF0Y2hl
IHdvcmsgd2VsbC4gSSB3b3VsZCBsaWtlIHRvIGNvbnRyaWJ1dGUgdGhpcyBmZWF0cnVlIGZvciBi
bHVleiB1cHN0cmVhbS4NCj4gVGhpcyBmaXJzdCBwYXRjaCBpcyBqdXN0IGFuIGltcGxlbWVudGF0
aW9uLCBtYXliZSBub3QgdmVyeSBjbG9zZWx5IHN1aXRhYmxlIGZvcg0KPiBkZXNpZ24gb3IgcHV0
IGluIHRoZSByaWdodCBmaWxlLiBJdCB3b3VsZCBiZSBPSyB0byBtb2RpZnkgdG8gbWFrZSBtb3Jl
IHNlbnNlLg0KPiA+IEFybWFuLCBNYXJjZWwsIHdvdWxkIHlvdSBsaWtlIHRvIHJldmlldyB0aGUg
cGF0Y2ggYW5kIGdpdmUgc29tZSBzdWdnZXN0aW9uDQo+IGZvciB0aGUgcGF0Y2g/IEl0IHdvdWxk
IGJlIGdyZWF0IGhvbm9yIGZvciBtZSB0byBjb250cmlidXRlIGxpdHRsZSB0byBibHVlei4NCj4g
DQo+IFRvbyBiaWcgcGF0Y2ggdG8gcmV2aWV3IGFsbCBhdCBvbmNlLCBjb3VsZCB5b3UgcGxlYXNl
IHNwbGl0LiBBbHNvIHBsZWFzZSBrZWVwIHRoZQ0KPiBwYXRjaCBkZXNjcmlwdGlvbiBsaW1pdGVk
IHRvIDgwIGNvbHVtbnMgY29uc2lkZXJpbmcgdGhlIGV4dHJhIHNwYWNlcyB0aGF0IGdpdCBsb2cN
Cj4gYWRkcy4NCj4gDQo+IA0KPiBMdWl6IEF1Z3VzdG8gdm9uIERlbnR6DQoNClRoYW5rcyBmb3Ig
eW91ciBhZHZpY2UgLCBJIHdpbGwgZm9sbG93IHRoaXMgcnVsZQ0KDQpCZXN0IFJlZ2FyZHMNCkNo
YW9qaWUgR3UNCg==
Hi,
On Fri, Aug 29, 2014 at 9:59 AM, Gu Chaojie <[email protected]> wrote:
> From: Chaojie Gu <[email protected]>
>
> This patch focus on implementing the Gatt DBus API defined in gatt-api.txt discussed in mailist latestly. It implement all the DBus property and method except for array{object} of characteristics and descriptors. Through our internal test, patche work well. I would like to contribute this featrue for bluez upstream. This first patch is just an implementation, maybe not very closely suitable for design or put in the right file. It would be OK to modify to make more sense.
> Arman, Marcel, would you like to review the patch and give some suggestion for the patch? It would be great honor for me to contribute little to bluez.
Too big patch to review all at once, could you please split. Also
please keep the patch description limited to 80 columns considering
the extra spaces that git log adds.
Luiz Augusto von Dentz
Hi Arman,
>> actually we want to convert our Android support over to bt_gatt. We have the full PTS documentation and the guys are working on end-to-end test cases using the Android HAL APIs. So hopefully such a conversion will not cause any big issues. However that said, it is a conversion that we want. Sooner than later.
>>
>
> Of course, moving the Android code over to bt_gatt makes sense, though
> until then it's probably ideal not to cause any regressions in the
> GAttrib code path. My idea was to start using bt_gatt in the D-Bus
> daemon and get it unit tested first so that we can flush out any bugs
> and then start Android discussion once we have all the confidence that
> things work as intended.
sounds good to me.
>> I think the only problem is BtIO and that gives us a GIOChannel. However BtIO has caused us a few issues already in the past and we might need to take a step back and just take the file descriptor and free the GIOChannel after that. At the end of the day, GIOChannel has to be removed from the whole source code. So why not start with GATT. I think it is a perfect candidate for giving this a trial run.
>>
>
> Sounds good to me. Actually, for LE connections at least, is there a
> desire to move away from BtIO eventually? Why not just directly set
> the socket options and open the connection in device.c? What does BtIO
> do that I'm missing?
For LE ATT it is not doing much. The fixed channel for ATT is super easy to handle. Either you connect() on it or you listen() + accept() and that is it. Besides the elevation of security and telling the kernel about the new MTU, it does not do anything special.
That is why I am saying that for LE ATT, the file descriptor might be a lot easier than involving BtIO. However for GATT over BR/EDR, that story is not that simple. There we do not use a fixed channel and use instead a connection oriented channel.
Regards
Marcel
Hi Chao Jie,
> In the old stack, doing encode/decode PDU split with sending att protocol command to remote device.
> However, in the new stack , bt_att_send do more things than old did including encode pdu with opcode.
> Because Att pdu signed process include whole data pdu, so in my mind, if we keep consistent style in code,
> we have to do sign att pdu in bt_att_send function. We have to expand bt_att struct to store CSRK and
> so on stuff. So that's the only implementation make sense for signed write command.
That's right, bt_att needs to extended to obtain and store the CSRK
somehow. The signing should happen during bt_att_send as you said
(probably in encode_pdu).
> BTW, compared with signing the outgoing, verifying incoming ATT PDUs would be more complex. I am not
> Sure which file to implement it.
That would be in the incoming PDU handling inside bt_att. The
can_read_data function obtains an incoming PDU and routes it to the
appropriate handler. I think that would be the right place to do it.
Cheers,
Arman
Hi Marcel,
> actually we want to convert our Android support over to bt_gatt. We have =
the full PTS documentation and the guys are working on end-to-end test case=
s using the Android HAL APIs. So hopefully such a conversion will not cause=
any big issues. However that said, it is a conversion that we want. Sooner=
than later.
>
Of course, moving the Android code over to bt_gatt makes sense, though
until then it's probably ideal not to cause any regressions in the
GAttrib code path. My idea was to start using bt_gatt in the D-Bus
daemon and get it unit tested first so that we can flush out any bugs
and then start Android discussion once we have all the confidence that
things work as intended.
> I think the only problem is BtIO and that gives us a GIOChannel. However =
BtIO has caused us a few issues already in the past and we might need to ta=
ke a step back and just take the file descriptor and free the GIOChannel af=
ter that. At the end of the day, GIOChannel has to be removed from the whol=
e source code. So why not start with GATT. I think it is a perfect candidat=
e for giving this a trial run.
>
Sounds good to me. Actually, for LE connections at least, is there a
desire to move away from BtIO eventually? Why not just directly set
the socket options and open the connection in device.c? What does BtIO
do that I'm missing?
Cheers,
Arman
SGkgYXJtYW4sDQoNCj4gTG9va3MgbGlrZSB0aGUgc3ViamVjdCB3YXM6ICJbUEFUQ0ggdjMgMS80
XSBzcmMvc2hhcmVkL2F0dDogSW50cm9kdWNlIHN0cnVjdCBidF9hdHQuIg0KPiBXZSBiYXNpY2Fs
bHkgdGFsa2VkIGFib3V0IHRoZSBuZWVkIGZvciBidF9hdHQgdG8gc29tZWhvdyBvYnRhaW4gdGhl
IENTUksgKHRocm91Z2gNCj4gYnRfYXR0X25ldyBtYXliZT8pIGFuZCBpbnRlcm5hbGx5IGtlZXAg
dGhlIGxvY2FsK3JlbW90ZSBzaWduIGNvdW50ZXJzIHRvIGJlIGFibGUNCj4gdG8gY2FsY3VsYXRl
IHRoZSBhdXRoZW50aWNhdGlvbiBzaWduYXR1cmUuIEJhc2ljYWxseSwgdGhlIGF1dGhlbnRpY2F0
aW9uIHNpZ25hdHVyZQ0KPiBzaG91bGQgZ2V0IGNhbGN1bGF0ZWQgYnkgYnRfYXR0IGdpdmVuIGFs
bCB0aGUgZGF0YSB0aGF0IGl0IG5lZWRzLg0KDQpUaGUgMy1kYXlzIE1pZC1hdXR1bW4gZmVzdGl2
YWwgaXMgcmlnaHQgYXJvdW5kIHRoZSBjb3JuZXIuIFNvIHRoZXJlIGlzIG5vIG11Y2ggdGltZSBs
ZWZ0IHRvIGltcGxlbWVudCBpdC4NClNvIEkgaW52ZXN0aWdhdGUgdGhlIHNvdXJjZSBjb2RlLCB0
aGVyZSBhcmUgc29tZSBkaWZmZXJlbmNlIGJldHdlZW4gb2xkIGF0dCBzdGFjayBhbmQgbmV3IHNo
YXJlZC9hdHQgc3RhY2suDQpJbiB0aGUgb2xkIHN0YWNrLCBkb2luZyBlbmNvZGUvZGVjb2RlIFBE
VSBzcGxpdCB3aXRoIHNlbmRpbmcgYXR0IHByb3RvY29sIGNvbW1hbmQgdG8gcmVtb3RlIGRldmlj
ZS4gDQpIb3dldmVyLCBpbiB0aGUgbmV3IHN0YWNrICwgYnRfYXR0X3NlbmQgZG8gbW9yZSB0aGlu
Z3MgdGhhbiBvbGQgZGlkIGluY2x1ZGluZyBlbmNvZGUgcGR1IHdpdGggb3Bjb2RlLiANCkJlY2F1
c2UgQXR0IHBkdSBzaWduZWQgcHJvY2VzcyBpbmNsdWRlIHdob2xlIGRhdGEgcGR1LCBzbyBpbiBt
eSBtaW5kLCBpZiB3ZSBrZWVwIGNvbnNpc3RlbnQgc3R5bGUgaW4gY29kZSwgDQp3ZSBoYXZlIHRv
IGRvIHNpZ24gYXR0IHBkdSBpbiBidF9hdHRfc2VuZCBmdW5jdGlvbi4gV2UgaGF2ZSB0byBleHBh
bmQgYnRfYXR0IHN0cnVjdCB0byBzdG9yZSBDU1JLIGFuZCANCnNvIG9uIHN0dWZmLiBTbyB0aGF0
J3MgdGhlIG9ubHkgaW1wbGVtZW50YXRpb24gbWFrZSBzZW5zZSBmb3Igc2lnbmVkIHdyaXRlIGNv
bW1hbmQuDQoNCkJUVywgY29tcGFyZWQgd2l0aCBzaWduaW5nIHRoZSBvdXRnb2luZywgdmVyaWZ5
aW5nIGluY29taW5nIEFUVCBQRFVzIHdvdWxkIGJlIG1vcmUgY29tcGxleC4gSSBhbSBub3QNClN1
cmUgd2hpY2ggZmlsZSB0byBpbXBsZW1lbnQgaXQuDQoNClRoYW5rcw0KQ2hhb2ppZQ0K
Hi Arman,
>> either we can convert GAttrib to use bt_att as underlying layer or we end up having to do the conversion all at once.
>>
>> Actually an initial starting point here could be to see how much we actually need to rely on GIOChannel for GATT. Maybe it is worth while to move the GIOChannel into GAttrib itself and just create it from the file descriptor. Once GIOChannel is an internal detail, it might be easy to switch over to struct io. All things that can be explored and need to be done eventually anyway.
>>
>
> Yeah, actually this may not be so bad. We might be able to turn
> GAttrib into a simple wrapper around a bt_att initially. I was
> actually a bit hesitant to modify GAttrib at first, since I don't want
> to break the Android code but I'll follow whichever route is easier in
> the end.
actually we want to convert our Android support over to bt_gatt. We have the full PTS documentation and the guys are working on end-to-end test cases using the Android HAL APIs. So hopefully such a conversion will not cause any big issues. However that said, it is a conversion that we want. Sooner than later.
> I think the GIOChannel comes from bt_io_connect and just get passed to
> g_attrib_new and for the purposes of GAttrib it is already mostly an
> internal detail (afaict). We could change it so that GAttrib maybe
> takes in a bt_att in its constructor (or the raw fd directly). I'll
> start playing with this eventually and see how it goes.
I think the only problem is BtIO and that gives us a GIOChannel. However BtIO has caused us a few issues already in the past and we might need to take a step back and just take the file descriptor and free the GIOChannel after that. At the end of the day, GIOChannel has to be removed from the whole source code. So why not start with GATT. I think it is a perfect candidate for giving this a trial run.
Regards
Marcel
Hi Marcel,
> either we can convert GAttrib to use bt_att as underlying layer or we end=
up having to do the conversion all at once.
>
> Actually an initial starting point here could be to see how much we actua=
lly need to rely on GIOChannel for GATT. Maybe it is worth while to move th=
e GIOChannel into GAttrib itself and just create it from the file descripto=
r. Once GIOChannel is an internal detail, it might be easy to switch over t=
o struct io. All things that can be explored and need to be done eventually=
anyway.
>
Yeah, actually this may not be so bad. We might be able to turn
GAttrib into a simple wrapper around a bt_att initially. I was
actually a bit hesitant to modify GAttrib at first, since I don't want
to break the Android code but I'll follow whichever route is easier in
the end.
I think the GIOChannel comes from bt_io_connect and just get passed to
g_attrib_new and for the purposes of GAttrib it is already mostly an
internal detail (afaict). We could change it so that GAttrib maybe
takes in a bt_att in its constructor (or the raw fd directly). I'll
start playing with this eventually and see how it goes.
Cheers,
Arman
Hi Chao Jie,
> I take some time delved in shared/* files, although I cannot take a look at code line
> one by one in short time,, the whole stack appear clear to me help work on this.
> When I use the btgatt-client tool to connect LE device today, I get a problem as belows:
> ./btgatt-client -i hci0 -d DA:3E:9D:65:75:BE
> Connecting to device ... Failed to connect: Operation now in progress.
> I used bluetoothctrl to connect the LE device , it's ok.
> I compared btgatt-client l2cap connect process to setup socket parameter with bluetoothd
> did, the process of setting socket configuration seems to be the same.
> What details did I forget to do to result in this problem? Did you encounter on this issue?
>
Make sure to stop bluetoothd if you haven't. You might have a race for
the connection there. If that doesn't help, you probably need to debug
things a bit further.
> Maybe the mail title does not include "signed write" key word, can you tell me about
> which month in that discussion came out ?
>
Looks like the subject was: "[PATCH v3 1/4] src/shared/att: Introduce
struct bt_att." We basically talked about the need for bt_att to
somehow obtain the CSRK (through bt_att_new maybe?) and internally
keep the local+remote sign counters to be able to calculate the
authentication signature. Basically, the authentication signature
should get calculated by bt_att given all the data that it needs.
> I agree that there are too many dependency on GArrtib now, it's a difficult migration
> work. You said probing interface for GATT that is not very clear to me. For my understanding,
> although plugin won't perform service discovery any more, they can tranverse the service_list
> in the bt_gatt_client structure. So for plugins, they only need the bt_gatt_client structure
> created once device connected.
>
Yes, exactly. We will probably have device a ready callback in
device.c (look at bt_gatt_client_set_ready_handler), which will be
called once all discovery has been completed. From there, device.c can
just probe each profile/plugin based on the discovered services. The
plugins would have access to the bt_gatt_client instance so that they
can iterate through services, characteristics, etc. Basically
bt_gatt_client acts as the client attribute cache.
Thanks,
Arman
SGkgYXJtYW4sIA0KDQo+IFlvdSBjYW4gc3RhcnQgcGxheWluZyB3aXRoIHRoZSBjb2RlIGJ5IHVz
aW5nIHRvb2xzL2J0Z2F0dC1jbGllbnQsIHdoaWNoIGdvdCBtZXJnZWQNCj4gaW50byB0aGUgdHJl
ZSBvdmVyIHRoZSB3ZWVrZW5kLiBUaGlzIGJhc2ljYWxseSBkZW1vbnN0cmF0ZXMgaG93IHRoZSB0
b29scyAmDQo+IGRhZW1vbiBjYW4gaW50ZXJhY3Qgd2l0aCBzaGFyZWQvZ2F0dC1jbGllbnQuIEFs
c28gdGFrZSBhIGxvb2sgYXQgc2hhcmVkL2F0dCwNCj4gc2hhcmVkL2dhdHQtaGVscGVycywgYW5k
IHNoYXJlZC9nYXR0LWNsaWVudCB3aGVyZSB0aGUgbmV3IGNvZGUgaXMgaW1wbGVtZW50ZWQuDQoN
CkkgdGFrZSBzb21lIHRpbWUgZGVsdmVkIGluIHNoYXJlZC8qIGZpbGVzLCBhbHRob3VnaCBJIGNh
bm5vdCB0YWtlIGEgbG9vayBhdCBjb2RlIGxpbmUgDQpvbmUgYnkgb25lIGluIHNob3J0IHRpbWUs
LCB0aGUgd2hvbGUgc3RhY2sgYXBwZWFyIGNsZWFyIHRvIG1lIGhlbHAgd29yayBvbiB0aGlzLiAg
IA0KV2hlbiBJIHVzZSB0aGUgYnRnYXR0LWNsaWVudCB0b29sIHRvIGNvbm5lY3QgTEUgZGV2aWNl
IHRvZGF5LCBJIGdldCBhIHByb2JsZW0gYXMgYmVsb3dzOg0KLi9idGdhdHQtY2xpZW50IC1pIGhj
aTAgLWQgREE6M0U6OUQ6NjU6NzU6QkUNCkNvbm5lY3RpbmcgdG8gZGV2aWNlIC4uLiBGYWlsZWQg
dG8gY29ubmVjdDogT3BlcmF0aW9uIG5vdyBpbiBwcm9ncmVzcy4NCkkgdXNlZCBibHVldG9vdGhj
dHJsIHRvIGNvbm5lY3QgdGhlIExFIGRldmljZSAsIGl0J3Mgb2suDQpJIGNvbXBhcmVkIGJ0Z2F0
dC1jbGllbnQgbDJjYXAgY29ubmVjdCBwcm9jZXNzIHRvIHNldHVwIHNvY2tldCBwYXJhbWV0ZXIg
d2l0aCBibHVldG9vdGhkDQpkaWQsIHRoZSBwcm9jZXNzIG9mIHNldHRpbmcgc29ja2V0IGNvbmZp
Z3VyYXRpb24gc2VlbXMgdG8gYmUgdGhlIHNhbWUuDQpXaGF0IGRldGFpbHMgZGlkIEkgZm9yZ2V0
IHRvIGRvIHRvIHJlc3VsdCBpbiB0aGlzIHByb2JsZW0/IERpZCB5b3UgZW5jb3VudGVyIG9uIHRo
aXMgaXNzdWU/DQogDQo+IE9uZSBvZiB0aGUgVE9ET3MgeW91IGNhbiBpbml0aWFsbHkgbG9vayBh
dCBpcyBzdXBwb3J0aW5nIHNpZ25lZCB3cml0ZXMgdXNpbmcNCj4gc2hhcmVkL2F0dC4gR2l2ZW4g
dGhlIG5lY2Vzc2FyeSBrZXlzLCB3ZSB3YW50IHN0cnVjdCBidF9hdHQgdG8gYXV0b21hdGljYWxs
eSBzaWduDQo+IG91dGdvaW5nIFBEVXMgKGZvciBjbGllbnQpIGFuZCB2ZXJpZnkgdGhlIHNpZ25h
dHVyZSBvZiBpbmNvbWluZyBQRFVzIChmb3Igc2VydmVyKQ0KPiB3aGVuIHRoZSAic2lnbmVkIiBi
aXQgb2YgdGhlIG9wY29kZSBpcyBzZXQgdG8gMS4gVGhlcmUgd2FzIGFuIGVtYWlsIGNvbnZlcnNh
dGlvbg0KPiBzb21lIHRpbWUgYWdvIG9uIGhvdyB0byBhY2Nlc3MgdGhlIGtleXM7IGZlZWwgZnJl
ZSB0byBkaWcgdGhhdCB1cCBhbmQgc2VlIHdoYXQgeW91DQo+IGNhbiBjb21lIHVwIHdpdGguDQoN
Ck1heWJlIHRoZSBtYWlsIHRpdGxlIGRvZXMgbm90IGluY2x1ZGUgInNpZ25lZCB3cml0ZSIga2V5
IHdvcmQsIGNhbiB5b3UgdGVsbCBtZSBhYm91dA0Kd2hpY2ggbW9udGggaW4gdGhhdCBkaXNjdXNz
aW9uIGNhbWUgb3V0ID8NCg0KPiBUaGUgVE9ETyBpdGVtcyBtYXJrZWQgYXMgQzEvQzIgYXJlIGZh
aXJseSBzdHJhaWdodGZvcndhcmQgdG8gaW1wbGVtZW50LiBUaGUNCj4gb3RoZXJzIHJlcXVpcmUg
c29tZSBkaXNjdXNzaW9uL0FQSSBkZXNpZ24uIEkgdGhpbmsgdGhlIG1vc3QgY29tcGxleCB0YXNr
IHdpbGwgYmUgdG8NCj4gbWlncmF0ZSB0aGUgZGFlbW9uIGNvZGUgZnJvbSBHQXR0cmliIHRvIHNo
YXJlZC9nYXR0LWNsaWVudCwgc2luY2UgYSBsb3Qgb2YgdGhlDQo+IHBsdWdpbnMgdXNlIHRoYXQg
Y29kZS4gV2UnbGwgbGlrZWx5IG5lZWQgYSBuZXcgcGx1Z2luIHByb2JpbmcgaW50ZXJmYWNlIGZv
ciBHQVRULCBhcw0KPiB0aGUgcGx1Z2lucyB3b24ndCBwZXJmb3JtIHNlcnZpY2UgZGlzY292ZXJ5
IGFueSBtb3JlLg0KPg0KSSBhZ3JlZSB0aGF0IHRoZXJlIGFyZSB0b28gbWFueSBkZXBlbmRlbmN5
IG9uIEdBcnJ0aWIgbm93LCBpdCdzIGEgZGlmZmljdWx0IG1pZ3JhdGlvbg0Kd29yay4gWW91IHNh
aWQgcHJvYmluZyBpbnRlcmZhY2UgZm9yIEdBVFQgdGhhdCBpcyBub3QgdmVyeSBjbGVhciB0byBt
ZS4gRm9yIG15IHVuZGVyc3RhbmRpbmcsDQphbHRob3VnaCBwbHVnaW4gd29uJ3QgcGVyZm9ybSBz
ZXJ2aWNlIGRpc2NvdmVyeSBhbnkgbW9yZSwgdGhleSBjYW4gdHJhbnZlcnNlIHRoZSBzZXJ2aWNl
X2xpc3QNCmluIHRoZSBidF9nYXR0X2NsaWVudCBzdHJ1Y3R1cmUuIFNvIGZvciBwbHVnaW5zLCB0
aGV5IG9ubHkgbmVlZCB0aGUgYnRfZ2F0dF9jbGllbnQgc3RydWN0dXJlICANCmNyZWF0ZWQgb25j
ZSBkZXZpY2UgY29ubmVjdGVkLg0KIA0KPiBUaGUgbWFpbiB0aGluZyB0aGF0IG1ha2VzIHRoaXMg
cGFydCBkaWZmaWN1bHQgaXMgdGhhdCBHQXR0cmliIGFuZCBidF9hdHQgY2Fubm90DQo+IG9wZXJh
dGUgb24gdGhlIHNhbWUgc29ja2V0IGZkLCBzaW5jZSB0aGV5IHdpbGwgbGlrZWx5IGJyZWFrIHRo
ZSBzZXF1ZW50aWFsIHByb3RvY29sDQo+IHJlcXVpcmVtZW50cyBvZiBBVFQsIHNvIHRoZSBwbHVn
aW5zIGNhbid0IGJlIGVhc2lseSBjb252ZXJ0ZWQgb25lIGF0IGEgdGltZS4gV2UgbWF5DQo+IG5l
ZWQgYSBkZXByZWNhdGlvbiBzdHJhdGVneSBmb3IgdGhpcyAoZS5nLiBwZXJoYXBzIGluaXRpYWxs
eSBoYXZlIGENCj4gc2hhcmVkL2F0dC1nYXR0cmliLmMgb2Ygc29ydHMsIHRoYXQgaW50ZXJuYWxs
eSB1c2VzIHRoZSBHQXR0cmliIGZyb20gZGV2aWNlLmMgYW5kIHRoZW4NCj4gd2Ugc3dpdGNoIHRv
IHRoZSBzdHJ1Y3QgaW8gYmFzZWQgb25lIG9uY2UgYWxsIHBsdWdpbnMgaGF2ZSBiZWVuIGNvbnZl
cnRlZCksIG9yDQo+IHBlcmhhcHMgd2UganVzdCBjb252ZXJ0IGl0IGFsbCBhdCBvbmNlIGluIG9u
ZSBzd29vcGluZyBwYXRjaCBzZXQuIEVpdGhlciB3YXksIHNvbWUNCj4gZGlzY3Vzc2lvbiBpcyBu
ZWVkZWQgb24gdGhpcyAocGVyaGFwcyBpbiBhIHNlcGFyYXRlIGVtYWlsIHRocmVhZCkuDQo+IA0K
PiBJIGhvcGUgdGhpcyBoZWxwcyENCj4gQXJtYW4NCg0KVGhhbmtzDQpDaGFvamllIEd1DQo=
Hi Arman,
> You can start playing with the code by using tools/btgatt-client,
> which got merged into the tree over the weekend. This basically
> demonstrates how the tools & daemon can interact with
> shared/gatt-client. Also take a look at shared/att,
> shared/gatt-helpers, and shared/gatt-client where the new code is
> implemented.
>
> One of the TODOs you can initially look at is supporting signed writes
> using shared/att. Given the necessary keys, we want struct bt_att to
> automatically sign outgoing PDUs (for client) and verify the signature
> of incoming PDUs (for server) when the "signed" bit of the opcode is
> set to 1. There was an email conversation some time ago on how to
> access the keys; feel free to dig that up and see what you can come up
> with.
>
> The TODO items marked as C1/C2 are fairly straightforward to
> implement. The others require some discussion/API design. I think the
> most complex task will be to migrate the daemon code from GAttrib to
> shared/gatt-client, since a lot of the plugins use that code. We'll
> likely need a new plugin probing interface for GATT, as the plugins
> won't perform service discovery any more.
>
> The main thing that makes this part difficult is that GAttrib and
> bt_att cannot operate on the same socket fd, since they will likely
> break the sequential protocol requirements of ATT, so the plugins
> can't be easily converted one at a time. We may need a deprecation
> strategy for this (e.g. perhaps initially have a shared/att-gattrib.c
> of sorts, that internally uses the GAttrib from device.c and then we
> switch to the struct io based one once all plugins have been
> converted), or perhaps we just convert it all at once in one swooping
> patch set. Either way, some discussion is needed on this (perhaps in a
> separate email thread).
either we can convert GAttrib to use bt_att as underlying layer or we end up having to do the conversion all at once.
Actually an initial starting point here could be to see how much we actually need to rely on GIOChannel for GATT. Maybe it is worth while to move the GIOChannel into GAttrib itself and just create it from the file descriptor. Once GIOChannel is an internal detail, it might be easy to switch over to struct io. All things that can be explored and need to be done eventually anyway.
Regards
Marcel
Hi Chao Jie,
You can start playing with the code by using tools/btgatt-client,
which got merged into the tree over the weekend. This basically
demonstrates how the tools & daemon can interact with
shared/gatt-client. Also take a look at shared/att,
shared/gatt-helpers, and shared/gatt-client where the new code is
implemented.
One of the TODOs you can initially look at is supporting signed writes
using shared/att. Given the necessary keys, we want struct bt_att to
automatically sign outgoing PDUs (for client) and verify the signature
of incoming PDUs (for server) when the "signed" bit of the opcode is
set to 1. There was an email conversation some time ago on how to
access the keys; feel free to dig that up and see what you can come up
with.
The TODO items marked as C1/C2 are fairly straightforward to
implement. The others require some discussion/API design. I think the
most complex task will be to migrate the daemon code from GAttrib to
shared/gatt-client, since a lot of the plugins use that code. We'll
likely need a new plugin probing interface for GATT, as the plugins
won't perform service discovery any more.
The main thing that makes this part difficult is that GAttrib and
bt_att cannot operate on the same socket fd, since they will likely
break the sequential protocol requirements of ATT, so the plugins
can't be easily converted one at a time. We may need a deprecation
strategy for this (e.g. perhaps initially have a shared/att-gattrib.c
of sorts, that internally uses the GAttrib from device.c and then we
switch to the struct io based one once all plugins have been
converted), or perhaps we just convert it all at once in one swooping
patch set. Either way, some discussion is needed on this (perhaps in a
separate email thread).
I hope this helps!
Arman
Hi Chao Jie,
>>>>> This patch focus on implementing the Gatt DBus API defined in
>>>>> gatt-api.txt
>>>> discussed in mailist latestly. It implement all the DBus property and
>>>> method except for array{object} of characteristics and descriptors.
>>>> Through our internal test, patche work well. I would like to contribute this
>> featrue for bluez upstream.
>>>> This first patch is just an implementation, maybe not very closely
>>>> suitable for design or put in the right file. It would be OK to modify to make more
>> sense.
>>>>> Arman, Marcel, would you like to review the patch and give some
>>>>> suggestion
>>>> for the patch? It would be great honor for me to contribute little to bluez.
>>>>
>>
>> Thank you for your patch. The current desire is to migrate all daemon code that uses
>> attrib/* for GATT/ATT over to the new utilities in shared/ (gatt-client, att,
>> gatt-helpers, etc.). In the end it's up to the maintainers but can we hold off on
>> implementing the D-Bus API until the shared code is ready and just do it once the
>> right way using bt_gatt_client? There are enough GAttrib dependencies inside the
>> daemon code that adding more now will just become a bigger burden in the future.
>>
>> I recently added a set of items to the top-level TODO file regarding the new
>> GATT/ATT stack; I can talk to you more about tackling the items there.
>>
>> Otherwise, if this is blocking people and everyone prefers to have a working GATT
>> D-Bus API sooner than that, then I suggest not putting all of the code in src/device.c.
>> You should probably abstract that away in a separate module (such as a
>> src/gatt-client) and have device.c initialize it. Also, please break this down into
>> multiple smaller patches as Luiz suggested.
>
> I have noticed that you are focusing on the Gatt shared library development in maillist
> latestly. And there is always a question in mind that no opportunity to ask you , why we
> replace old GATT/ATT by new Gatt shared library?
>
> According to your advice , I saw the gatt TODO list items including gatt-api.txt part.
> Of course, new Gatt DBus API will based on shared/gatt-client. But I believe most logical
> would be common except for API from shared/gatt-client.
>
> There is no problem holding on until the shared code is ready, we can use old base and internal
> patch to transition.
>
> I saw a lot of TODO List in GATT/ATT part, and gatt-api.txt base on new shared library. So I will
> pay more attention to shared library code. Sometimes I am not sure your work plan which items
> will do first, which not. So if any help I can do or wrong patches I submitted, just talk to me,
> I would appreciate it.
we want to create a common base for GATT and ATT internally. Common means that it is used by internal tools, the upstream bluetoothd and also the bluetoothd variation that we use for Android.
At the same time we are removing all dependencies on GLib from the codebase. Which means I am pretty much against in accepting new code that explicitly relies on GLib for its IO handling or certain data structures.
So my advise is that you help us get the new code ready. The more people helping here, the faster it gets. Arman has done an excellent job in getting this off the ground and creating TODO entries.
Regards
Marcel
Hi Chaojie,
The idea is to create a set of tools that can be shared among
different platform implementations (desktop, Android, etc.) without
direct library dependencies, such as glib. GAttrib is written in a way
that directly interacts with GIOChannel whereas the shared stuff use
shared/io, which is a simple abstraction that is compatible with
GIOChannel and Glib mainloop, as well as BlueZ's own mainloop
implementation (monitor/mainloop.c).
Beyond removing the dependency issue, the shared code will provide a
stand-alone, functioning GATT server/client implementation that is not
tied to any daemon code. The idea is that you can instantiate a struct
bt_gatt_client, and that will do all the attribute discovery and
caching for you. The upper-layer won't need to know about the contents
of ATT protocol PDUs or explicit handling of "service changed"
indications, etc (similarly, there will be a shared/gatt-server to
handle all the GATT server magic).
Others can comment more on this, since I've been basically following
Marcel's suggestion :)
Cheers,
Arman
Hi Chaojie,
The idea is to create a set of tools that can be shared among different
platform implementations (desktop, Android, etc.) without direct library
dependencies, such as glib. GAttrib is written in a way that directly
interacts with GIOChannel whereas the shared stuff use shared/io, which is
a simple abstraction that is compatible with GIOChannel and Glib mainloop,
as well as BlueZ's own mainloop implementation (monitor/mainloop.c).
Beyond removing the dependency issue, the shared code will provide a
stand-alone, functioning GATT server/client implementation that is not tied
to any daemon code. The idea is that you can instantiate a struct
bt_gatt_client, and that will do all the attribute discovery and caching
for you. The upper-layer won't need to know about the contents of ATT
protocol PDUs or explicit handling of "service changed" indications, etc
(similarly, there will be a shared/gatt-server to handle all the GATT
server magic).
Others can comment more on this, since I've been basically following
Marcel's suggestion :)
Cheers,
Arman
SGkgYXJtYW4sIA0KPiA+PiA+IFRoaXMgcGF0Y2ggZm9jdXMgb24gaW1wbGVtZW50aW5nIHRoZSBH
YXR0IERCdXMgQVBJIGRlZmluZWQgaW4NCj4gPj4gPiBnYXR0LWFwaS50eHQNCj4gPj4gZGlzY3Vz
c2VkIGluIG1haWxpc3QgbGF0ZXN0bHkuIEl0IGltcGxlbWVudCBhbGwgdGhlIERCdXMgcHJvcGVy
dHkgYW5kDQo+ID4+IG1ldGhvZCBleGNlcHQgZm9yIGFycmF5e29iamVjdH0gb2YgY2hhcmFjdGVy
aXN0aWNzIGFuZCBkZXNjcmlwdG9ycy4NCj4gPj4gVGhyb3VnaCBvdXIgaW50ZXJuYWwgdGVzdCwg
cGF0Y2hlIHdvcmsgd2VsbC4gSSB3b3VsZCBsaWtlIHRvIGNvbnRyaWJ1dGUgdGhpcw0KPiBmZWF0
cnVlIGZvciBibHVleiB1cHN0cmVhbS4NCj4gPj4gVGhpcyBmaXJzdCBwYXRjaCBpcyBqdXN0IGFu
IGltcGxlbWVudGF0aW9uLCBtYXliZSBub3QgdmVyeSBjbG9zZWx5DQo+ID4+IHN1aXRhYmxlIGZv
ciBkZXNpZ24gb3IgcHV0IGluIHRoZSByaWdodCBmaWxlLiBJdCB3b3VsZCBiZSBPSyB0byBtb2Rp
ZnkgdG8gbWFrZSBtb3JlDQo+IHNlbnNlLg0KPiA+PiA+IEFybWFuLCBNYXJjZWwsIHdvdWxkIHlv
dSBsaWtlIHRvIHJldmlldyB0aGUgcGF0Y2ggYW5kIGdpdmUgc29tZQ0KPiA+PiA+IHN1Z2dlc3Rp
b24NCj4gPj4gZm9yIHRoZSBwYXRjaD8gSXQgd291bGQgYmUgZ3JlYXQgaG9ub3IgZm9yIG1lIHRv
IGNvbnRyaWJ1dGUgbGl0dGxlIHRvIGJsdWV6Lg0KPiA+Pg0KPiANCj4gVGhhbmsgeW91IGZvciB5
b3VyIHBhdGNoLiBUaGUgY3VycmVudCBkZXNpcmUgaXMgdG8gbWlncmF0ZSBhbGwgZGFlbW9uIGNv
ZGUgdGhhdCB1c2VzDQo+IGF0dHJpYi8qIGZvciBHQVRUL0FUVCBvdmVyIHRvIHRoZSBuZXcgdXRp
bGl0aWVzIGluIHNoYXJlZC8gKGdhdHQtY2xpZW50LCBhdHQsDQo+IGdhdHQtaGVscGVycywgZXRj
LikuIEluIHRoZSBlbmQgaXQncyB1cCB0byB0aGUgbWFpbnRhaW5lcnMgYnV0IGNhbiB3ZSBob2xk
IG9mZiBvbg0KPiBpbXBsZW1lbnRpbmcgdGhlIEQtQnVzIEFQSSB1bnRpbCB0aGUgc2hhcmVkIGNv
ZGUgaXMgcmVhZHkgYW5kIGp1c3QgZG8gaXQgb25jZSB0aGUNCj4gcmlnaHQgd2F5IHVzaW5nIGJ0
X2dhdHRfY2xpZW50PyBUaGVyZSBhcmUgZW5vdWdoIEdBdHRyaWIgZGVwZW5kZW5jaWVzIGluc2lk
ZSB0aGUNCj4gZGFlbW9uIGNvZGUgdGhhdCBhZGRpbmcgbW9yZSBub3cgd2lsbCBqdXN0IGJlY29t
ZSBhIGJpZ2dlciBidXJkZW4gaW4gdGhlIGZ1dHVyZS4NCj4gDQo+IEkgcmVjZW50bHkgYWRkZWQg
YSBzZXQgb2YgaXRlbXMgdG8gdGhlIHRvcC1sZXZlbCBUT0RPIGZpbGUgcmVnYXJkaW5nIHRoZSBu
ZXcNCj4gR0FUVC9BVFQgc3RhY2s7IEkgY2FuIHRhbGsgdG8geW91IG1vcmUgYWJvdXQgdGFja2xp
bmcgdGhlIGl0ZW1zIHRoZXJlLg0KPiANCj4gT3RoZXJ3aXNlLCBpZiB0aGlzIGlzIGJsb2NraW5n
IHBlb3BsZSBhbmQgZXZlcnlvbmUgcHJlZmVycyB0byBoYXZlIGEgd29ya2luZyBHQVRUDQo+IEQt
QnVzIEFQSSBzb29uZXIgdGhhbiB0aGF0LCB0aGVuIEkgc3VnZ2VzdCBub3QgcHV0dGluZyBhbGwg
b2YgdGhlIGNvZGUgaW4gc3JjL2RldmljZS5jLg0KPiBZb3Ugc2hvdWxkIHByb2JhYmx5IGFic3Ry
YWN0IHRoYXQgYXdheSBpbiBhIHNlcGFyYXRlIG1vZHVsZSAoc3VjaCBhcyBhDQo+IHNyYy9nYXR0
LWNsaWVudCkgYW5kIGhhdmUgZGV2aWNlLmMgaW5pdGlhbGl6ZSBpdC4gQWxzbywgcGxlYXNlIGJy
ZWFrIHRoaXMgZG93biBpbnRvDQo+IG11bHRpcGxlIHNtYWxsZXIgcGF0Y2hlcyBhcyBMdWl6IHN1
Z2dlc3RlZC4NCg0KSSBoYXZlIG5vdGljZWQgdGhhdCB5b3UgYXJlIGZvY3VzaW5nIG9uIHRoZSBH
YXR0IHNoYXJlZCBsaWJyYXJ5IGRldmVsb3BtZW50IGluIG1haWxsaXN0IA0KbGF0ZXN0bHkuIEFu
ZCB0aGVyZSBpcyBhbHdheXMgYSBxdWVzdGlvbiBpbiBtaW5kIHRoYXQgbm8gb3Bwb3J0dW5pdHkg
dG8gYXNrIHlvdSAsIHdoeSB3ZQ0KcmVwbGFjZSBvbGQgR0FUVC9BVFQgYnkgbmV3IEdhdHQgc2hh
cmVkIGxpYnJhcnk/IA0KDQpBY2NvcmRpbmcgdG8geW91ciBhZHZpY2UgLCBJIHNhdyB0aGUgZ2F0
dCBUT0RPIGxpc3QgaXRlbXMgaW5jbHVkaW5nIGdhdHQtYXBpLnR4dCBwYXJ0Lg0KT2YgY291cnNl
LCBuZXcgR2F0dCBEQnVzIEFQSSB3aWxsIGJhc2VkIG9uIHNoYXJlZC9nYXR0LWNsaWVudC4gQnV0
IEkgYmVsaWV2ZSBtb3N0IGxvZ2ljYWwNCndvdWxkIGJlIGNvbW1vbiBleGNlcHQgZm9yIEFQSSBm
cm9tIHNoYXJlZC9nYXR0LWNsaWVudC4NCg0KVGhlcmUgaXMgbm8gcHJvYmxlbSBob2xkaW5nIG9u
IHVudGlsIHRoZSBzaGFyZWQgY29kZSBpcyByZWFkeSwgd2UgY2FuIHVzZSBvbGQgYmFzZSBhbmQg
aW50ZXJuYWwNCnBhdGNoIHRvIHRyYW5zaXRpb24uDQoNCkkgc2F3IGEgbG90IG9mIFRPRE8gTGlz
dCBpbiBHQVRUL0FUVCBwYXJ0LCBhbmQgZ2F0dC1hcGkudHh0IGJhc2Ugb24gbmV3IHNoYXJlZCBs
aWJyYXJ5LiBTbyBJIHdpbGwNCnBheSBtb3JlIGF0dGVudGlvbiB0byBzaGFyZWQgbGlicmFyeSBj
b2RlLiBTb21ldGltZXMgSSBhbSBub3Qgc3VyZSB5b3VyIHdvcmsgcGxhbiB3aGljaCBpdGVtcw0K
d2lsbCBkbyBmaXJzdCwgd2hpY2ggbm90LiBTbyBpZiBhbnkgaGVscCBJIGNhbiBkbyBvciB3cm9u
ZyBwYXRjaGVzIEkgc3VibWl0dGVkLCBqdXN0IHRhbGsgdG8gbWUsIA0KSSB3b3VsZCBhcHByZWNp
YXRlIGl0Lg0KDQpUaGFua3MNCkNoYW9qaWUgR3UNCg==