*v2:
- Rebased
*v1:
- Addressed comments by Johan and Luiz.
This patch set integrates shared/gatt-client into bluetoothd. The following
items have been tackled here:
1. src/device now uses shared/gatt-client to perform service discovery. State
updates such as disconnections, service changed events, etc. are done using
callback APIs of the new shared framework and attrib/gatt is no longer used to
perform GATT operations directly. A GAttrib instance is maintained for
backwards compatibility with profiles that haven't been converted yet.
2. A new gatt-callbacks API has been added which mirrors the attio APIs for
shared/gatt-client. Profiles/plugins are notified using these APIs when the
bearer disconnects, when services change, and when gatt-client is ready.
3. The profiles/gatt plugin has been rewritten. Since the GATT service is
handled by shared/gatt-client, this profile no longer deals with service
changed events performs out of place MTU exchanges, which used to get
performed in an arbitrary order. The profile has been renamed to profiles/gap
as it now only deals with the GAP service, and reads the "Appearance" and
"Device Name" characteristics using the new shared stack.
Arman Uguray (12):
attrib/gattrib: Add g_attrib_get_att.
shared/att: Add err argument to disconnect cb
shared/gatt-client: Set op->success correctly
core: device: Use bt_att_register_disconnect.
core: device: Use shared/gatt-client for GATT.
core: Rename device_attach_attrib
core: Use gatt_db service callbacks
core: Introduce gatt-callbacks
profiles/gatt: Don't handle GATT service.
profiles/gatt: Rename profile to gap.
profiles/gap: Rewrite using bt_gatt_client.
profiles/gap: Add Google copyright.
Makefile.am | 1 +
Makefile.plugins | 4 +-
attrib/gattrib.c | 8 +
attrib/gattrib.h | 3 +
profiles/gap/gas.c | 322 +++++++++++++++++++++++++
profiles/gatt/gas.c | 457 ------------------------------------
src/attrib-server.c | 2 +-
src/device.c | 598 +++++++++++++++++++++++++++++++++--------------
src/device.h | 2 +-
src/gatt-callbacks.h | 36 +++
src/shared/att-types.h | 3 +-
src/shared/att.c | 25 +-
src/shared/att.h | 2 +-
src/shared/gatt-client.c | 6 +-
tools/btgatt-client.c | 4 +-
tools/btgatt-server.c | 4 +-
16 files changed, 834 insertions(+), 643 deletions(-)
create mode 100644 profiles/gap/gas.c
delete mode 100644 profiles/gatt/gas.c
create mode 100644 src/gatt-callbacks.h
--
2.2.0.rc0.207.ga3a616c
Hi Arman,
On Mon, Dec 15, 2014 at 5:29 PM, Arman Uguray <[email protected]> wrote:
> *v2:
> - Rebased
>
> *v1:
> - Addressed comments by Johan and Luiz.
>
>
> This patch set integrates shared/gatt-client into bluetoothd. The following
> items have been tackled here:
>
> 1. src/device now uses shared/gatt-client to perform service discovery. State
> updates such as disconnections, service changed events, etc. are done using
> callback APIs of the new shared framework and attrib/gatt is no longer used to
> perform GATT operations directly. A GAttrib instance is maintained for
> backwards compatibility with profiles that haven't been converted yet.
>
> 2. A new gatt-callbacks API has been added which mirrors the attio APIs for
> shared/gatt-client. Profiles/plugins are notified using these APIs when the
> bearer disconnects, when services change, and when gatt-client is ready.
>
> 3. The profiles/gatt plugin has been rewritten. Since the GATT service is
> handled by shared/gatt-client, this profile no longer deals with service
> changed events performs out of place MTU exchanges, which used to get
> performed in an arbitrary order. The profile has been renamed to profiles/gap
> as it now only deals with the GAP service, and reads the "Appearance" and
> "Device Name" characteristics using the new shared stack.
>
> Arman Uguray (12):
> attrib/gattrib: Add g_attrib_get_att.
> shared/att: Add err argument to disconnect cb
> shared/gatt-client: Set op->success correctly
> core: device: Use bt_att_register_disconnect.
> core: device: Use shared/gatt-client for GATT.
> core: Rename device_attach_attrib
> core: Use gatt_db service callbacks
> core: Introduce gatt-callbacks
> profiles/gatt: Don't handle GATT service.
> profiles/gatt: Rename profile to gap.
> profiles/gap: Rewrite using bt_gatt_client.
> profiles/gap: Add Google copyright.
>
> Makefile.am | 1 +
> Makefile.plugins | 4 +-
> attrib/gattrib.c | 8 +
> attrib/gattrib.h | 3 +
> profiles/gap/gas.c | 322 +++++++++++++++++++++++++
> profiles/gatt/gas.c | 457 ------------------------------------
> src/attrib-server.c | 2 +-
> src/device.c | 598 +++++++++++++++++++++++++++++++++--------------
> src/device.h | 2 +-
> src/gatt-callbacks.h | 36 +++
> src/shared/att-types.h | 3 +-
> src/shared/att.c | 25 +-
> src/shared/att.h | 2 +-
> src/shared/gatt-client.c | 6 +-
> tools/btgatt-client.c | 4 +-
> tools/btgatt-server.c | 4 +-
> 16 files changed, 834 insertions(+), 643 deletions(-)
> create mode 100644 profiles/gap/gas.c
> delete mode 100644 profiles/gatt/gas.c
> create mode 100644 src/gatt-callbacks.h
>
> --
> 2.2.0.rc0.207.ga3a616c
Patches 1-7 are now applied, thanks. Lets work out the rest.
--
Luiz Augusto von Dentz
Added Google Inc. to the copyright comment since the profile has been
mostly rewritten.
---
profiles/gap/gas.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/profiles/gap/gas.c b/profiles/gap/gas.c
index 0b5e6ee..8267a87 100644
--- a/profiles/gap/gas.c
+++ b/profiles/gap/gas.c
@@ -3,6 +3,7 @@
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT
+ * Copyright (C) 2014 Google Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
--
2.2.0.rc0.207.ga3a616c
This patch rewrites the GAP profile to use the shared GATT stack instead
of GAttrib. The profile now also handles the Device Name characteristic.
---
profiles/gap/gas.c | 226 +++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 167 insertions(+), 59 deletions(-)
diff --git a/profiles/gap/gas.c b/profiles/gap/gas.c
index a4028dd..0b5e6ee 100644
--- a/profiles/gap/gas.c
+++ b/profiles/gap/gas.c
@@ -19,6 +19,7 @@
#include <config.h>
#endif
+#include <ctype.h>
#include <stdbool.h>
#include <stdlib.h>
#include <sys/types.h>
@@ -36,30 +37,34 @@
#include "src/profile.h"
#include "src/service.h"
#include "src/shared/util.h"
-#include "attrib/att.h"
-#include "attrib/gattrib.h"
-#include "src/attio.h"
-#include "attrib/gatt.h"
#include "src/log.h"
#include "src/textfile.h"
+#include "src/shared/att.h"
+#include "src/shared/queue.h"
+#include "src/shared/gatt-db.h"
+#include "src/shared/gatt-client.h"
+#include "src/gatt-callbacks.h"
/* Generic Attribute/Access Service */
struct gas {
struct btd_device *device;
- struct att_range gap; /* GAP Primary service range */
- GAttrib *attrib;
- guint attioid;
+ struct gatt_db *db;
+ struct bt_gatt_client *client;
+ uint16_t start_handle, end_handle;
+ unsigned int gatt_cb_id;
};
static GSList *devices;
static void gas_free(struct gas *gas)
{
- if (gas->attioid)
- btd_device_remove_attio_callback(gas->device, gas->attioid);
+ if (gas->gatt_cb_id)
+ btd_device_remove_gatt_callbacks(gas->device,
+ gas->gatt_cb_id);
- g_attrib_unref(gas->attrib);
btd_device_unref(gas->device);
+ gatt_db_unref(gas->db);
+ bt_gatt_client_unref(gas->client);
g_free(gas);
}
@@ -71,83 +76,194 @@ static int cmp_device(gconstpointer a, gconstpointer b)
return gas->device == device ? 0 : -1;
}
-static void gap_appearance_cb(guint8 status, const guint8 *pdu, guint16 plen,
- gpointer user_data)
+static char *name2utf8(const uint8_t *name, uint8_t len)
+{
+ char utf8_name[HCI_MAX_NAME_LENGTH + 2];
+ int i;
+
+ if (g_utf8_validate((const char *) name, len, NULL))
+ return g_strndup((char *) name, len);
+
+ memset(utf8_name, 0, sizeof(utf8_name));
+ strncpy(utf8_name, (char *) name, len);
+
+ /* Assume ASCII, and replace all non-ASCII with spaces */
+ for (i = 0; utf8_name[i] != '\0'; i++) {
+ if (!isascii(utf8_name[i]))
+ utf8_name[i] = ' ';
+ }
+
+ /* Remove leading and trailing whitespace characters */
+ g_strstrip(utf8_name);
+
+ return g_strdup(utf8_name);
+}
+
+static void read_device_name_cb(bool success, uint8_t att_ecode,
+ const uint8_t *value, uint16_t length,
+ void *user_data)
{
struct gas *gas = user_data;
- struct att_data_list *list = NULL;
- uint16_t app;
- uint8_t *atval;
+ char *name = name2utf8(value, length);
- if (status != 0) {
- error("Read characteristics by UUID failed: %s",
- att_ecode2str(status));
+ DBG("GAP Device Name: %s", name);
+
+ btd_device_device_set_name(gas->device, name);
+}
+
+static void handle_device_name(struct gas *gas, uint16_t value_handle)
+{
+ if (!bt_gatt_client_read_long_value(gas->client, value_handle, 0,
+ read_device_name_cb, gas, NULL))
+ DBG("Failed to send request to read device name");
+}
+
+static void read_appearance_cb(bool success, uint8_t att_ecode,
+ const uint8_t *value, uint16_t length,
+ void *user_data)
+{
+ struct gas *gas = user_data;
+ uint16_t appearance;
+
+ if (!success) {
+ DBG("Reading appearance failed with ATT error: %u", att_ecode);
return;
}
- list = dec_read_by_type_resp(pdu, plen);
- if (list == NULL)
+ /* The appearance value is a 16-bit unsigned integer */
+ if (length != 2) {
+ DBG("Malformed appearance value");
return;
-
- if (list->len != 4) {
- error("GAP Appearance value: invalid data");
- goto done;
}
- atval = list->data[0] + 2; /* skip handle value */
- app = get_le16(atval);
+ appearance = get_le16(value);
- DBG("GAP Appearance: 0x%04x", app);
+ DBG("GAP Appearance: 0x%04x", appearance);
- device_set_appearance(gas->device, app);
+ device_set_appearance(gas->device, appearance);
+}
-done:
- att_data_list_free(list);
+static void handle_appearance(struct gas *gas, uint16_t value_handle)
+{
+ if (!bt_gatt_client_read_value(gas->client, value_handle,
+ read_appearance_cb, gas, NULL))
+ DBG("Failed to send request to read appearance");
}
-static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
+static bool uuid_cmp(uint16_t u16, const bt_uuid_t *uuid)
+{
+ bt_uuid_t lhs;
+
+ bt_uuid16_create(&lhs, u16);
+
+ return bt_uuid_cmp(&lhs, uuid) == 0;
+}
+
+static void handle_characteristic(struct gatt_db_attribute *attr,
+ void *user_data)
{
struct gas *gas = user_data;
- uint16_t app;
+ uint16_t value_handle;
+ bt_uuid_t uuid;
+
+ if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL,
+ &uuid)) {
+ error("Failed to obtain characteristic data");
+ return;
+ }
- gas->attrib = g_attrib_ref(attrib);
+ if (uuid_cmp(GATT_CHARAC_DEVICE_NAME, &uuid))
+ handle_device_name(gas, value_handle);
+ else if (uuid_cmp(GATT_CHARAC_APPEARANCE, &uuid))
+ handle_appearance(gas, value_handle);
+ else {
+ char uuid_str[MAX_LEN_UUID_STR];
- if (device_get_appearance(gas->device, &app) < 0) {
- bt_uuid_t uuid;
+ /* TODO: Support peripheral privacy feature */
- bt_uuid16_create(&uuid, GATT_CHARAC_APPEARANCE);
+ bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str));
+ DBG("Unsupported characteristic: %s", uuid_str);
+ }
+}
+
+static void handle_service(struct gatt_db_attribute *attr, void *user_data)
+{
+ struct gas *gas = user_data;
- gatt_read_char_by_uuid(gas->attrib, gas->gap.start,
- gas->gap.end, &uuid,
- gap_appearance_cb, gas);
+ if (gas->start_handle) {
+ error("More than one GAP service exists on device");
+ return;
}
- /* TODO: Read other GAP characteristics - See Core spec page 1739 */
+ gatt_db_attribute_get_service_handles(attr, &gas->start_handle,
+ &gas->end_handle);
+ gatt_db_service_foreach_char(attr, handle_characteristic, gas);
}
-static void attio_disconnected_cb(gpointer user_data)
+static void handle_gap_service(struct gas *gas)
+{
+ bt_uuid_t uuid;
+
+ bt_string_to_uuid(&uuid, GAP_UUID);
+
+ gatt_db_foreach_service(gas->db, &uuid, handle_service, gas);
+}
+
+static void gatt_client_ready_cb(struct bt_gatt_client *client,
+ struct gatt_db *db, void *user_data)
{
struct gas *gas = user_data;
- g_attrib_unref(gas->attrib);
- gas->attrib = NULL;
+ gas->client = bt_gatt_client_ref(client);
+ gas->db = gatt_db_ref(db);
+
+ handle_gap_service(gas);
}
-static int gas_register(struct btd_device *device, struct att_range *gap)
+static void gatt_client_disconn_cb(void *user_data)
+{
+ struct gas *gas = user_data;
+
+ gatt_db_unref(gas->db);
+ bt_gatt_client_unref(gas->client);
+
+ gas->db = NULL;
+ gas->client = NULL;
+ gas->start_handle = gas->end_handle = 0;
+}
+
+static int gas_register(struct btd_device *device)
{
struct gas *gas;
+ GSList *l;
+
+ /*
+ * There can't be more than one instance of the GAP service on the same
+ * device.
+ */
+ l = g_slist_find_custom(devices, device, cmp_device);
+ if (l)
+ return 0;
gas = g_new0(struct gas, 1);
- gas->gap.start = gap->start;
- gas->gap.end = gap->end;
gas->device = btd_device_ref(device);
-
devices = g_slist_append(devices, gas);
- gas->attioid = btd_device_add_attio_callback(device,
- attio_connected_cb,
- attio_disconnected_cb, gas);
+ /*
+ * Simply register the callbacks. We will perform initialization during
+ * the ready callback once the gatt-client has been initialized for this
+ * device.
+ *
+ * Here we don't register the service removed handler since the
+ * GAP service is mandatory and should not get removed while the device
+ * is connected.
+ */
+ gas->gatt_cb_id = btd_device_add_gatt_callbacks(device,
+ gatt_client_ready_cb,
+ NULL,
+ gatt_client_disconn_cb,
+ gas);
return 0;
}
@@ -169,16 +285,8 @@ static void gas_unregister(struct btd_device *device)
static int gap_driver_probe(struct btd_service *service)
{
struct btd_device *device = btd_service_get_device(service);
- struct gatt_primary *gap;
-
- gap = btd_device_get_primary(device, GAP_UUID);
-
- if (gap == NULL) {
- error("GAP service is mandatory");
- return -EINVAL;
- }
- return gas_register(device, &gap->range);
+ return gas_register(device);
}
static void gap_driver_remove(struct btd_service *service)
--
2.2.0.rc0.207.ga3a616c
Since this built in profile only handles the GAP service, this patch renames it
to gap.
---
Makefile.plugins | 4 +-
profiles/gap/gas.c | 213 +++++++++++++++++++++++++++++++++++++++++++++++++++
profiles/gatt/gas.c | 216 ----------------------------------------------------
3 files changed, 215 insertions(+), 218 deletions(-)
create mode 100644 profiles/gap/gas.c
delete mode 100644 profiles/gatt/gas.c
diff --git a/Makefile.plugins b/Makefile.plugins
index 0448b91..52b51c5 100644
--- a/Makefile.plugins
+++ b/Makefile.plugins
@@ -72,8 +72,8 @@ builtin_sources += profiles/health/mcap.h profiles/health/mcap.c \
profiles/health/hdp_util.h profiles/health/hdp_util.c
endif
-builtin_modules += gatt
-builtin_sources += profiles/gatt/gas.c
+builtin_modules += gap
+builtin_sources += profiles/gap/gas.c
builtin_modules += scanparam
builtin_sources += profiles/scanparam/scan.c
diff --git a/profiles/gap/gas.c b/profiles/gap/gas.c
new file mode 100644
index 0000000..a4028dd
--- /dev/null
+++ b/profiles/gap/gas.c
@@ -0,0 +1,213 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <glib.h>
+
+#include "btio/btio.h"
+#include "lib/uuid.h"
+#include "src/plugin.h"
+#include "src/adapter.h"
+#include "src/device.h"
+#include "src/profile.h"
+#include "src/service.h"
+#include "src/shared/util.h"
+#include "attrib/att.h"
+#include "attrib/gattrib.h"
+#include "src/attio.h"
+#include "attrib/gatt.h"
+#include "src/log.h"
+#include "src/textfile.h"
+
+/* Generic Attribute/Access Service */
+struct gas {
+ struct btd_device *device;
+ struct att_range gap; /* GAP Primary service range */
+ GAttrib *attrib;
+ guint attioid;
+};
+
+static GSList *devices;
+
+static void gas_free(struct gas *gas)
+{
+ if (gas->attioid)
+ btd_device_remove_attio_callback(gas->device, gas->attioid);
+
+ g_attrib_unref(gas->attrib);
+ btd_device_unref(gas->device);
+ g_free(gas);
+}
+
+static int cmp_device(gconstpointer a, gconstpointer b)
+{
+ const struct gas *gas = a;
+ const struct btd_device *device = b;
+
+ return gas->device == device ? 0 : -1;
+}
+
+static void gap_appearance_cb(guint8 status, const guint8 *pdu, guint16 plen,
+ gpointer user_data)
+{
+ struct gas *gas = user_data;
+ struct att_data_list *list = NULL;
+ uint16_t app;
+ uint8_t *atval;
+
+ if (status != 0) {
+ error("Read characteristics by UUID failed: %s",
+ att_ecode2str(status));
+ return;
+ }
+
+ list = dec_read_by_type_resp(pdu, plen);
+ if (list == NULL)
+ return;
+
+ if (list->len != 4) {
+ error("GAP Appearance value: invalid data");
+ goto done;
+ }
+
+ atval = list->data[0] + 2; /* skip handle value */
+ app = get_le16(atval);
+
+ DBG("GAP Appearance: 0x%04x", app);
+
+ device_set_appearance(gas->device, app);
+
+done:
+ att_data_list_free(list);
+}
+
+static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
+{
+ struct gas *gas = user_data;
+ uint16_t app;
+
+ gas->attrib = g_attrib_ref(attrib);
+
+ if (device_get_appearance(gas->device, &app) < 0) {
+ bt_uuid_t uuid;
+
+ bt_uuid16_create(&uuid, GATT_CHARAC_APPEARANCE);
+
+ gatt_read_char_by_uuid(gas->attrib, gas->gap.start,
+ gas->gap.end, &uuid,
+ gap_appearance_cb, gas);
+ }
+
+ /* TODO: Read other GAP characteristics - See Core spec page 1739 */
+}
+
+static void attio_disconnected_cb(gpointer user_data)
+{
+ struct gas *gas = user_data;
+
+ g_attrib_unref(gas->attrib);
+ gas->attrib = NULL;
+}
+
+static int gas_register(struct btd_device *device, struct att_range *gap)
+{
+ struct gas *gas;
+
+ gas = g_new0(struct gas, 1);
+ gas->gap.start = gap->start;
+ gas->gap.end = gap->end;
+
+ gas->device = btd_device_ref(device);
+
+ devices = g_slist_append(devices, gas);
+
+ gas->attioid = btd_device_add_attio_callback(device,
+ attio_connected_cb,
+ attio_disconnected_cb, gas);
+
+ return 0;
+}
+
+static void gas_unregister(struct btd_device *device)
+{
+ struct gas *gas;
+ GSList *l;
+
+ l = g_slist_find_custom(devices, device, cmp_device);
+ if (l == NULL)
+ return;
+
+ gas = l->data;
+ devices = g_slist_remove(devices, gas);
+ gas_free(gas);
+}
+
+static int gap_driver_probe(struct btd_service *service)
+{
+ struct btd_device *device = btd_service_get_device(service);
+ struct gatt_primary *gap;
+
+ gap = btd_device_get_primary(device, GAP_UUID);
+
+ if (gap == NULL) {
+ error("GAP service is mandatory");
+ return -EINVAL;
+ }
+
+ return gas_register(device, &gap->range);
+}
+
+static void gap_driver_remove(struct btd_service *service)
+{
+ struct btd_device *device = btd_service_get_device(service);
+
+ gas_unregister(device);
+}
+
+static struct btd_profile gap_profile = {
+ .name = "gap-profile",
+ .remote_uuid = GAP_UUID,
+ .device_probe = gap_driver_probe,
+ .device_remove = gap_driver_remove
+};
+
+static int gap_init(void)
+{
+ devices = NULL;
+
+ btd_profile_register(&gap_profile);
+
+ return 0;
+}
+
+static void gap_exit(void)
+{
+ btd_profile_unregister(&gap_profile);
+}
+
+BLUETOOTH_PLUGIN_DEFINE(gap, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
+ gap_init, gap_exit)
diff --git a/profiles/gatt/gas.c b/profiles/gatt/gas.c
deleted file mode 100644
index 9d5d31e..0000000
--- a/profiles/gatt/gas.c
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- *
- * BlueZ - Bluetooth protocol stack for Linux
- *
- * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- *
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdbool.h>
-#include <stdlib.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <errno.h>
-
-#include <glib.h>
-
-#include "btio/btio.h"
-#include "lib/uuid.h"
-#include "src/plugin.h"
-#include "src/adapter.h"
-#include "src/device.h"
-#include "src/profile.h"
-#include "src/service.h"
-#include "src/shared/util.h"
-#include "attrib/att.h"
-#include "attrib/gattrib.h"
-#include "src/attio.h"
-#include "attrib/gatt.h"
-#include "src/log.h"
-#include "src/textfile.h"
-
-/* Generic Attribute/Access Service */
-struct gas {
- struct btd_device *device;
- struct att_range gap; /* GAP Primary service range */
- GAttrib *attrib;
- guint attioid;
-};
-
-static GSList *devices = NULL;
-
-static void gas_free(struct gas *gas)
-{
- if (gas->attioid)
- btd_device_remove_attio_callback(gas->device, gas->attioid);
-
- g_attrib_unref(gas->attrib);
- btd_device_unref(gas->device);
- g_free(gas);
-}
-
-static int cmp_device(gconstpointer a, gconstpointer b)
-{
- const struct gas *gas = a;
- const struct btd_device *device = b;
-
- return (gas->device == device ? 0 : -1);
-}
-
-static void gap_appearance_cb(guint8 status, const guint8 *pdu, guint16 plen,
- gpointer user_data)
-{
- struct gas *gas = user_data;
- struct att_data_list *list = NULL;
- uint16_t app;
- uint8_t *atval;
-
- if (status != 0) {
- error("Read characteristics by UUID failed: %s",
- att_ecode2str(status));
- return;
- }
-
- list = dec_read_by_type_resp(pdu, plen);
- if (list == NULL)
- return;
-
- if (list->len != 4) {
- error("GAP Appearance value: invalid data");
- goto done;
- }
-
- atval = list->data[0] + 2; /* skip handle value */
- app = get_le16(atval);
-
- DBG("GAP Appearance: 0x%04x", app);
-
- device_set_appearance(gas->device, app);
-
-done:
- att_data_list_free(list);
-}
-
-static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
-{
- struct gas *gas = user_data;
- uint16_t app;
-
- gas->attrib = g_attrib_ref(attrib);
-
- if (device_get_appearance(gas->device, &app) < 0) {
- bt_uuid_t uuid;
-
- bt_uuid16_create(&uuid, GATT_CHARAC_APPEARANCE);
-
- gatt_read_char_by_uuid(gas->attrib, gas->gap.start,
- gas->gap.end, &uuid,
- gap_appearance_cb, gas);
- }
-
- /* TODO: Read other GAP characteristics - See Core spec page 1739 */
-}
-
-static void attio_disconnected_cb(gpointer user_data)
-{
- struct gas *gas = user_data;
-
- g_attrib_unref(gas->attrib);
- gas->attrib = NULL;
-}
-
-static int gas_register(struct btd_device *device, struct att_range *gap)
-{
- struct gas *gas;
-
- gas = g_new0(struct gas, 1);
- gas->gap.start = gap->start;
- gas->gap.end = gap->end;
-
- gas->device = btd_device_ref(device);
-
- devices = g_slist_append(devices, gas);
-
- gas->attioid = btd_device_add_attio_callback(device,
- attio_connected_cb,
- attio_disconnected_cb, gas);
-
- return 0;
-}
-
-static void gas_unregister(struct btd_device *device)
-{
- struct gas *gas;
- GSList *l;
-
- l = g_slist_find_custom(devices, device, cmp_device);
- if (l == NULL)
- return;
-
- gas = l->data;
- devices = g_slist_remove(devices, gas);
- gas_free(gas);
-}
-
-static int gatt_driver_probe(struct btd_service *service)
-{
- struct btd_device *device = btd_service_get_device(service);
- struct gatt_primary *gap;
-
- gap = btd_device_get_primary(device, GAP_UUID);
-
- if (gap == NULL) {
- error("GAP service is mandatory");
- return -EINVAL;
- }
-
- return gas_register(device, &gap->range);
-}
-
-static void gatt_driver_remove(struct btd_service *service)
-{
- struct btd_device *device = btd_service_get_device(service);
-
- gas_unregister(device);
-}
-
-static struct btd_profile gatt_profile = {
- .name = "gap-gatt-profile",
- .remote_uuid = GATT_UUID,
- .device_probe = gatt_driver_probe,
- .device_remove = gatt_driver_remove
-};
-
-static int gatt_init(void)
-{
- btd_profile_register(&gatt_profile);
-
- return 0;
-}
-
-static void gatt_exit(void)
-{
- btd_profile_unregister(&gatt_profile);
-}
-
-BLUETOOTH_PLUGIN_DEFINE(gatt, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
- gatt_init, gatt_exit)
--
2.2.0.rc0.207.ga3a616c
ATT MTU exchange and handling of indications from the "Service Changed"
characteristic are now handled by shared/gatt-client, so this profile
should only deal with the GAP service.
---
profiles/gatt/gas.c | 251 ++--------------------------------------------------
1 file changed, 5 insertions(+), 246 deletions(-)
diff --git a/profiles/gatt/gas.c b/profiles/gatt/gas.c
index b51b4a8..9d5d31e 100644
--- a/profiles/gatt/gas.c
+++ b/profiles/gatt/gas.c
@@ -52,12 +52,8 @@
struct gas {
struct btd_device *device;
struct att_range gap; /* GAP Primary service range */
- struct att_range gatt; /* GATT Primary service range */
GAttrib *attrib;
guint attioid;
- guint changed_ind;
- uint16_t changed_handle;
- uint16_t mtu;
};
static GSList *devices = NULL;
@@ -80,68 +76,6 @@ static int cmp_device(gconstpointer a, gconstpointer b)
return (gas->device == device ? 0 : -1);
}
-static void write_ctp_handle(struct btd_device *device, uint16_t uuid,
- uint16_t handle)
-{
- char *filename, group[6], value[7];
- GKeyFile *key_file;
- char *data;
- gsize length = 0;
-
- filename = btd_device_get_storage_path(device, "gatt");
- if (!filename) {
- warn("Unable to get gatt storage path for device");
- return;
- }
-
- key_file = g_key_file_new();
- g_key_file_load_from_file(key_file, filename, 0, NULL);
-
- snprintf(group, sizeof(group), "%hu", uuid);
- snprintf(value, sizeof(value), "0x%4.4X", handle);
- g_key_file_set_string(key_file, group, "Value", value);
-
- data = g_key_file_to_data(key_file, &length, NULL);
- if (length > 0) {
- create_file(filename, S_IRUSR | S_IWUSR);
- g_file_set_contents(filename, data, length, NULL);
- }
-
- g_free(data);
- g_free(filename);
- g_key_file_free(key_file);
-}
-
-static int read_ctp_handle(struct btd_device *device, uint16_t uuid,
- uint16_t *value)
-{
- char *filename, group[6];
- GKeyFile *key_file;
- char *str;
- int err = 0;
-
- filename = btd_device_get_storage_path(device, "gatt");
- if (!filename) {
- warn("Unable to get gatt storage path for device");
- return -ENOENT;
- }
-
- snprintf(group, sizeof(group), "%hu", uuid);
-
- key_file = g_key_file_new();
- g_key_file_load_from_file(key_file, filename, 0, NULL);
-
- str = g_key_file_get_string(key_file, group, "Value", NULL);
- if (str == NULL || sscanf(str, "%hx", value) != 1)
- err = -ENOENT;
-
- g_free(str);
- g_free(filename);
- g_key_file_free(key_file);
-
- return err;
-}
-
static void gap_appearance_cb(guint8 status, const guint8 *pdu, guint16 plen,
gpointer user_data)
{
@@ -176,163 +110,12 @@ done:
att_data_list_free(list);
}
-static void indication_cb(const uint8_t *pdu, uint16_t len, gpointer user_data)
-{
- uint8_t bdaddr_type;
- struct gas *gas = user_data;
- uint16_t start, end, olen;
- size_t plen;
- uint8_t *opdu;
-
- if (len < 7) { /* 1-byte opcode + 2-byte handle + 4 range */
- error("Malformed ATT notification");
- return;
- }
-
- start = get_le16(&pdu[3]);
- end = get_le16(&pdu[5]);
-
- DBG("Service Changed start: 0x%04X end: 0x%04X", start, end);
-
- /* Confirming indication received */
- opdu = g_attrib_get_buffer(gas->attrib, &plen);
- olen = enc_confirmation(opdu, plen);
- g_attrib_send(gas->attrib, 0, opdu, olen, NULL, NULL, NULL);
-
- bdaddr_type = btd_device_get_bdaddr_type(gas->device);
- if (!device_is_bonded(gas->device, bdaddr_type)) {
- DBG("Ignoring Service Changed: device is not bonded");
- return;
- }
-
- btd_device_gatt_set_service_changed(gas->device, start, end);
-}
-
-static void ccc_written_cb(guint8 status, const guint8 *pdu, guint16 plen,
- gpointer user_data)
-{
- struct gas *gas = user_data;
-
- if (status) {
- error("Write Service Changed CCC failed: %s",
- att_ecode2str(status));
- return;
- }
-
- DBG("Service Changed indications enabled");
-
- gas->changed_ind = g_attrib_register(gas->attrib, ATT_OP_HANDLE_IND,
- gas->changed_handle,
- indication_cb, gas, NULL);
-
- write_ctp_handle(gas->device, GATT_CHARAC_SERVICE_CHANGED,
- gas->changed_handle);
-}
-
-static void write_ccc(GAttrib *attrib, uint16_t handle, gpointer user_data)
-{
- uint8_t value[2];
-
- put_le16(GATT_CLIENT_CHARAC_CFG_IND_BIT, value);
- gatt_write_char(attrib, handle, value, sizeof(value), ccc_written_cb,
- user_data);
-}
-
-static void discover_ccc_cb(uint8_t status, GSList *descs, void *user_data)
-{
- struct gas *gas = user_data;
- struct gatt_desc *desc;
-
- if (status != 0) {
- error("Discover Service Changed CCC failed: %s",
- att_ecode2str(status));
- return;
- }
-
- /* There will be only one descriptor on list and it will be CCC */
- desc = descs->data;
-
- DBG("CCC: 0x%04x", desc->handle);
- write_ccc(gas->attrib, desc->handle, user_data);
-}
-
-static void gatt_characteristic_cb(uint8_t status, GSList *characteristics,
- void *user_data)
-{
- struct gas *gas = user_data;
- struct gatt_char *chr;
- uint16_t start, end;
- bt_uuid_t uuid;
-
- if (status) {
- error("Discover Service Changed handle: %s", att_ecode2str(status));
- return;
- }
-
- chr = characteristics->data;
-
- start = chr->value_handle + 1;
- end = gas->gatt.end;
-
- if (start > end) {
- error("Inconsistent database: Service Changed CCC missing");
- return;
- }
-
- gas->changed_handle = chr->value_handle;
-
- bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID);
-
- gatt_discover_desc(gas->attrib, start, end, &uuid, discover_ccc_cb,
- gas);
-}
-
-static void exchange_mtu_cb(guint8 status, const guint8 *pdu, guint16 plen,
- gpointer user_data)
-{
- struct gas *gas = user_data;
- uint16_t rmtu;
-
- if (status) {
- error("MTU exchange: %s", att_ecode2str(status));
- return;
- }
-
- if (!dec_mtu_resp(pdu, plen, &rmtu)) {
- error("MTU exchange: protocol error");
- return;
- }
-
- gas->mtu = MIN(rmtu, gas->mtu);
- if (g_attrib_set_mtu(gas->attrib, gas->mtu))
- DBG("MTU exchange succeeded: %d", gas->mtu);
- else
- DBG("MTU exchange failed");
-}
-
static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
{
struct gas *gas = user_data;
- GIOChannel *io;
- GError *gerr = NULL;
- uint16_t cid, imtu;
uint16_t app;
gas->attrib = g_attrib_ref(attrib);
- io = g_attrib_get_channel(attrib);
-
- if (bt_io_get(io, &gerr, BT_IO_OPT_IMTU, &imtu,
- BT_IO_OPT_CID, &cid, BT_IO_OPT_INVALID) &&
- cid == ATT_CID) {
- gatt_exchange_mtu(gas->attrib, imtu, exchange_mtu_cb, gas);
- gas->mtu = imtu;
- DBG("MTU Exchange: Requesting %d", imtu);
- }
-
- if (gerr) {
- error("Could not acquire att imtu and cid: %s", gerr->message);
- g_error_free(gerr);
- }
if (device_get_appearance(gas->device, &app) < 0) {
bt_uuid_t uuid;
@@ -345,43 +128,23 @@ static void attio_connected_cb(GAttrib *attrib, gpointer user_data)
}
/* TODO: Read other GAP characteristics - See Core spec page 1739 */
-
- /*
- * When re-connecting <<Service Changed>> handle and characteristic
- * value doesn't need to read again: known information from the
- * previous interaction.
- */
- if (gas->changed_handle == 0) {
- bt_uuid_t uuid;
-
- bt_uuid16_create(&uuid, GATT_CHARAC_SERVICE_CHANGED);
-
- gatt_discover_char(gas->attrib, gas->gatt.start, gas->gatt.end,
- &uuid, gatt_characteristic_cb, gas);
- }
}
static void attio_disconnected_cb(gpointer user_data)
{
struct gas *gas = user_data;
- g_attrib_unregister(gas->attrib, gas->changed_ind);
- gas->changed_ind = 0;
-
g_attrib_unref(gas->attrib);
gas->attrib = NULL;
}
-static int gas_register(struct btd_device *device, struct att_range *gap,
- struct att_range *gatt)
+static int gas_register(struct btd_device *device, struct att_range *gap)
{
struct gas *gas;
gas = g_new0(struct gas, 1);
gas->gap.start = gap->start;
gas->gap.end = gap->end;
- gas->gatt.start = gatt->start;
- gas->gatt.end = gatt->end;
gas->device = btd_device_ref(device);
@@ -391,9 +154,6 @@ static int gas_register(struct btd_device *device, struct att_range *gap,
attio_connected_cb,
attio_disconnected_cb, gas);
- read_ctp_handle(gas->device, GATT_CHARAC_SERVICE_CHANGED,
- &gas->changed_handle);
-
return 0;
}
@@ -414,17 +174,16 @@ static void gas_unregister(struct btd_device *device)
static int gatt_driver_probe(struct btd_service *service)
{
struct btd_device *device = btd_service_get_device(service);
- struct gatt_primary *gap, *gatt;
+ struct gatt_primary *gap;
gap = btd_device_get_primary(device, GAP_UUID);
- gatt = btd_device_get_primary(device, GATT_UUID);
- if (gap == NULL || gatt == NULL) {
- error("GAP and GATT are mandatory");
+ if (gap == NULL) {
+ error("GAP service is mandatory");
return -EINVAL;
}
- return gas_register(device, &gap->range, &gatt->range);
+ return gas_register(device, &gap->range);
}
static void gatt_driver_remove(struct btd_service *service)
--
2.2.0.rc0.207.ga3a616c
This patch introduces src/gatt-callbacks.h. This defines API functions
to get notified of bt_gatt_client related events, such as ready,
service removed, and disconnects. These callbacks are parallel to the
existing attio.h functions and will provide a temporary alternative
during the transition from attrib/ to shared/gatt.
---
Makefile.am | 1 +
src/device.c | 162 +++++++++++++++++++++++++++++++++++++++++++++++----
src/gatt-callbacks.h | 36 ++++++++++++
3 files changed, 188 insertions(+), 11 deletions(-)
create mode 100644 src/gatt-callbacks.h
diff --git a/Makefile.am b/Makefile.am
index f2b22ae..a63b055 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -182,6 +182,7 @@ src_bluetoothd_SOURCES = $(builtin_sources) \
src/gatt-dbus.h src/gatt-dbus.c \
src/gatt.h src/gatt.c \
src/device.h src/device.c src/attio.h \
+ src/gatt-callbacks.h \
src/dbus-common.c src/dbus-common.h \
src/eir.h src/eir.c
src_bluetoothd_LDADD = lib/libbluetooth-internal.la \
diff --git a/src/device.c b/src/device.c
index af070cd..f383b80 100644
--- a/src/device.c
+++ b/src/device.c
@@ -59,6 +59,7 @@
#include "attrib/gattrib.h"
#include "attio.h"
#include "device.h"
+#include "gatt-callbacks.h"
#include "profile.h"
#include "service.h"
#include "dbus-common.h"
@@ -145,6 +146,14 @@ struct attio_data {
gpointer user_data;
};
+struct gatt_cb_data {
+ unsigned int id;
+ btd_gatt_client_ready_t ready_func;
+ btd_gatt_service_removed_t svc_removed_func;
+ btd_gatt_disconnect_t disconn_func;
+ void *user_data;
+};
+
struct svc_callback {
unsigned int id;
guint idle_id;
@@ -222,6 +231,8 @@ struct btd_device {
*/
struct gatt_db *client_db; /* GATT client cache */
struct bt_gatt_client *gatt_client; /* GATT client implementation */
+ GSList *gatt_callbacks;
+ unsigned int next_gatt_cb_id;
struct bearer_state bredr_state;
struct bearer_state le_state;
@@ -559,6 +570,7 @@ static void device_free(gpointer user_data)
g_slist_free_full(device->attios, g_free);
g_slist_free_full(device->attios_offline, g_free);
g_slist_free_full(device->svc_callbacks, svc_dev_remove);
+ g_slist_free_full(device->gatt_callbacks, g_free);
attio_cleanup(device);
@@ -2439,6 +2451,21 @@ static gint prim_attr_cmp(gconstpointer a, gconstpointer b)
return !(prim->range.start == start && prim->range.end == end);
}
+struct svc_removed_data {
+ struct gatt_db_attribute *attr;
+ struct bt_gatt_client *client;
+};
+
+static void notify_gatt_service_removed(gpointer data, gpointer user_data)
+{
+ struct gatt_cb_data *cb = data;
+ struct svc_removed_data *rm_data = user_data;
+
+ if (cb->svc_removed_func)
+ cb->svc_removed_func(rm_data->client, rm_data->attr,
+ cb->user_data);
+}
+
static void gatt_service_removed(struct gatt_db_attribute *attr,
void *user_data)
{
@@ -2446,6 +2473,7 @@ static void gatt_service_removed(struct gatt_db_attribute *attr,
GSList *l;
struct gatt_primary *prim;
uint16_t start, end;
+ struct svc_removed_data data;
if (!bt_gatt_client_is_ready(device->gatt_client))
return;
@@ -2469,13 +2497,19 @@ static void gatt_service_removed(struct gatt_db_attribute *attr,
store_services(device);
- /*
- * TODO: Notify the profiles somehow. It may be sufficient for each
- * profile to register a service_removed handler.
- */
-
g_dbus_emit_property_changed(dbus_conn, device->path,
DEVICE_INTERFACE, "UUIDs");
+
+ data.attr = attr;
+ data.client = device->gatt_client;
+
+ g_slist_foreach(device->gatt_callbacks, notify_gatt_service_removed,
+ &data);
+
+ /*
+ * TODO: Remove profiles matching this UUID if no other GATT services
+ * with the same UUID exist.
+ */
}
static struct btd_device *device_new(struct btd_adapter *adapter,
@@ -3560,6 +3594,16 @@ static void attio_disconnected(gpointer data, gpointer user_data)
attio->dcfunc(attio->user_data);
}
+static void gatt_disconnected(gpointer data, gpointer user_data)
+{
+ struct gatt_cb_data *gatt_data = data;
+
+ DBG("");
+
+ if (gatt_data->disconn_func)
+ gatt_data->disconn_func(gatt_data->user_data);
+}
+
static void att_disconnected_cb(int err, void *user_data)
{
struct btd_device *device = user_data;
@@ -3572,6 +3616,7 @@ static void att_disconnected_cb(int err, void *user_data)
DBG("%s (%d)", strerror(err), err);
g_slist_foreach(device->attios, attio_disconnected, NULL);
+ g_slist_foreach(device->gatt_callbacks, gatt_disconnected, NULL);
if (!device_get_auto_connect(device)) {
DBG("Automatic connection disabled");
@@ -3645,7 +3690,15 @@ static void register_gatt_services(struct browse_req *req)
device_probe_profiles(device, req->profiles_added);
- if (device->attios == NULL && device->attios_offline == NULL)
+ /*
+ * TODO: This check seems unnecessary. We may not always want to cleanup
+ * the connection since there will always be built-in plugins who want
+ * to interact with remote GATT services. Even if we didn't have those,
+ * the GATT D-Bus API will need to interact with these, so we should
+ * later remove this check entirely.
+ */
+ if (device->attios == NULL && device->attios_offline == NULL &&
+ device->gatt_callbacks == NULL)
attio_cleanup(device);
device_svc_resolved(device, device->bdaddr_type, 0);
@@ -3655,6 +3708,18 @@ static void register_gatt_services(struct browse_req *req)
browse_request_free(req);
}
+static void notify_gatt_client_ready(gpointer data, gpointer user_data)
+{
+ struct gatt_cb_data *gatt_data = data;
+ struct btd_device *device = user_data;
+
+ DBG("");
+
+ if (gatt_data->ready_func)
+ gatt_data->ready_func(device->gatt_client, device->client_db,
+ gatt_data->user_data);
+}
+
static void gatt_client_ready_cb(bool success, uint8_t att_ecode,
void *user_data)
{
@@ -3683,11 +3748,9 @@ static void gatt_client_ready_cb(bool success, uint8_t att_ecode,
if (device->browse)
register_gatt_services(device->browse);
- /*
- * TODO: Change attio callbacks to accept a gatt-client instead of a
- * GAttrib.
- */
g_slist_foreach(device->attios, attio_connected, device->attrib);
+ g_slist_foreach(device->gatt_callbacks, notify_gatt_client_ready,
+ device);
}
static void gatt_client_service_changed(uint16_t start_handle,
@@ -5077,7 +5140,8 @@ gboolean btd_device_remove_attio_callback(struct btd_device *device, guint id)
g_free(attio);
- if (device->attios != NULL || device->attios_offline != NULL)
+ if (device->attios != NULL || device->attios_offline != NULL ||
+ device->gatt_callbacks != NULL)
return TRUE;
attio_cleanup(device);
@@ -5085,6 +5149,82 @@ gboolean btd_device_remove_attio_callback(struct btd_device *device, guint id)
return TRUE;
}
+unsigned int btd_device_add_gatt_callbacks(struct btd_device *device,
+ btd_gatt_client_ready_t ready_func,
+ btd_gatt_service_removed_t service_removed_func,
+ btd_gatt_disconnect_t disconnect_func,
+ void *user_data)
+{
+ struct gatt_cb_data *gatt_data;
+
+ gatt_data = new0(struct gatt_cb_data, 1);
+ if (!gatt_data)
+ return 0;
+
+ if (device->next_gatt_cb_id < 1)
+ device->next_gatt_cb_id = 1;
+
+ device_set_auto_connect(device, TRUE);
+
+ gatt_data->id = device->next_gatt_cb_id++;
+ gatt_data->ready_func = ready_func;
+ gatt_data->svc_removed_func = service_removed_func;
+ gatt_data->disconn_func = disconnect_func;
+ gatt_data->user_data = user_data;
+
+ /*
+ * TODO: The connection might be incoming from attrib-server (see
+ * btd_device_add_attio_callback). I don't think this is a good place to
+ * attach the GAttrib to the device. We should come up with a more
+ * unified flow for attaching the GAttrib, bt_att, and bt_gatt_client
+ * for incoming and outgoing connections.
+ */
+ device->gatt_callbacks = g_slist_append(device->gatt_callbacks,
+ gatt_data);
+
+ if (ready_func && bt_gatt_client_is_ready(device->gatt_client))
+ ready_func(device->gatt_client, device->client_db, user_data);
+
+ return gatt_data->id;
+}
+
+static int gatt_cb_id_cmp(gconstpointer a, gconstpointer b)
+{
+ const struct gatt_cb_data *gatt_data = a;
+ guint id = GPOINTER_TO_UINT(b);
+
+ return gatt_data->id - id;
+}
+
+bool btd_device_remove_gatt_callbacks(struct btd_device *device,
+ unsigned int id)
+{
+ struct gatt_cb_data *gatt_data;
+ GSList *l;
+
+ l = g_slist_find_custom(device->gatt_callbacks, GUINT_TO_POINTER(id),
+ gatt_cb_id_cmp);
+ if (!l)
+ return false;
+
+ gatt_data = l->data;
+ device->gatt_callbacks = g_slist_remove(device->gatt_callbacks,
+ gatt_data);
+ free(gatt_data);
+
+ /*
+ * TODO: Consider removing this check and avoiding cleanup as it seems
+ * unnecessary.
+ */
+ if (device->attios != NULL || device->attios_offline != NULL ||
+ device->gatt_callbacks != NULL)
+ return true;
+
+ attio_cleanup(device);
+
+ return true;
+}
+
void btd_device_set_pnpid(struct btd_device *device, uint16_t source,
uint16_t vendor, uint16_t product, uint16_t version)
{
diff --git a/src/gatt-callbacks.h b/src/gatt-callbacks.h
new file mode 100644
index 0000000..2ac1800
--- /dev/null
+++ b/src/gatt-callbacks.h
@@ -0,0 +1,36 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2014 Google Inc.
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+
+typedef void (*btd_gatt_client_ready_t) (struct bt_gatt_client *client,
+ struct gatt_db *db,
+ void *user_data);
+typedef void (*btd_gatt_service_removed_t) (struct bt_gatt_client *client,
+ struct gatt_db_attribute *attrib,
+ void *user_data);
+typedef void (*btd_gatt_disconnect_t) (void *user_data);
+
+unsigned int btd_device_add_gatt_callbacks(struct btd_device *device,
+ btd_gatt_client_ready_t ready_func,
+ btd_gatt_service_removed_t service_removed_func,
+ btd_gatt_disconnect_t disconnect_func,
+ void *user_data);
+bool btd_device_remove_gatt_callbacks(struct btd_device *device,
+ unsigned int id);
--
2.2.0.rc0.207.ga3a616c
This patch integrates the gatt_db service callbacks into btd_device.
Based on the event, the device objects UUIDs list is updated and
profiles are probed for new services.
There is currently no mechanism to tell a profile that a service has
been removed, however profiles can use the gatt_db callbacks to make
necessary updates in the future.
---
src/device.c | 143 ++++++++++++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 117 insertions(+), 26 deletions(-)
diff --git a/src/device.c b/src/device.c
index 3bd3939..af070cd 100644
--- a/src/device.c
+++ b/src/device.c
@@ -2365,6 +2365,119 @@ static void load_att_info(struct btd_device *device, const char *local,
free(prim_uuid);
}
+static void device_register_primaries(struct btd_device *device,
+ GSList *prim_list, int psm)
+{
+ device->primaries = g_slist_concat(device->primaries, prim_list);
+}
+
+static void add_primary(struct gatt_db_attribute *attr, void *user_data)
+{
+ GSList **new_services = user_data;
+ struct gatt_primary *prim;
+ bt_uuid_t uuid;
+
+ prim = g_new0(struct gatt_primary, 1);
+ if (!prim) {
+ DBG("Failed to allocate gatt_primary structure");
+ return;
+ }
+
+ gatt_db_attribute_get_service_handles(attr, &prim->range.start,
+ &prim->range.end);
+ gatt_db_attribute_get_service_uuid(attr, &uuid);
+ bt_uuid_to_string(&uuid, prim->uuid, sizeof(prim->uuid));
+
+ *new_services = g_slist_append(*new_services, prim);
+}
+
+static void store_services(struct btd_device *device);
+
+static void gatt_service_added(struct gatt_db_attribute *attr, void *user_data)
+{
+ struct btd_device *device = user_data;
+ struct gatt_primary *prim;
+ GSList *new_service = NULL;
+ GSList *profiles_added = NULL;
+ uint16_t start, end;
+
+ if (!bt_gatt_client_is_ready(device->gatt_client))
+ return;
+
+ gatt_db_attribute_get_service_handles(attr, &start, &end);
+
+ DBG("GATT service added - start: 0x%04x, end: 0x%04x", start, end);
+
+ /*
+ * TODO: Remove the primaries list entirely once all profiles use
+ * shared/gatt.
+ */
+ add_primary(attr, &new_service);
+ if (!new_service)
+ return;
+
+ device_register_primaries(device, new_service, -1);
+
+ prim = new_service->data;
+ profiles_added = g_slist_append(profiles_added, g_strdup(prim->uuid));
+
+ device_probe_profiles(device, profiles_added);
+
+ store_services(device);
+
+ g_slist_free_full(profiles_added, g_free);
+}
+
+static gint prim_attr_cmp(gconstpointer a, gconstpointer b)
+{
+ const struct gatt_primary *prim = a;
+ const struct gatt_db_attribute *attr = b;
+ uint16_t start, end;
+
+ gatt_db_attribute_get_service_handles(attr, &start, &end);
+
+ return !(prim->range.start == start && prim->range.end == end);
+}
+
+static void gatt_service_removed(struct gatt_db_attribute *attr,
+ void *user_data)
+{
+ struct btd_device *device = user_data;
+ GSList *l;
+ struct gatt_primary *prim;
+ uint16_t start, end;
+
+ if (!bt_gatt_client_is_ready(device->gatt_client))
+ return;
+
+ gatt_db_attribute_get_service_handles(attr, &start, &end);
+
+ DBG("GATT service removed - start: 0x%04x, end: 0x%04x", start, end);
+
+ /* Remove the corresponding gatt_primary */
+ l = g_slist_find_custom(device->primaries, attr, prim_attr_cmp);
+ if (!l)
+ return;
+
+ prim = l->data;
+ device->primaries = g_slist_delete_link(device->primaries, l);
+
+ /* Remove the corresponding UUIDs entry */
+ l = g_slist_find_custom(device->uuids, prim->uuid, bt_uuid_strcmp);
+ device->uuids = g_slist_delete_link(device->uuids, l);
+ g_free(prim);
+
+ store_services(device);
+
+ /*
+ * TODO: Notify the profiles somehow. It may be sufficient for each
+ * profile to register a service_removed handler.
+ */
+
+ g_dbus_emit_property_changed(dbus_conn, device->path,
+ DEVICE_INTERFACE, "UUIDs");
+}
+
static struct btd_device *device_new(struct btd_adapter *adapter,
const char *address)
{
@@ -2405,6 +2518,10 @@ static struct btd_device *device_new(struct btd_adapter *adapter,
device->adapter = adapter;
device->temporary = TRUE;
+ gatt_db_register(device->client_db, gatt_service_added,
+ gatt_service_removed,
+ device, NULL);
+
return btd_device_ref(device);
}
@@ -3264,12 +3381,6 @@ static GSList *device_services_from_record(struct btd_device *device,
return prim_list;
}
-static void device_register_primaries(struct btd_device *device,
- GSList *prim_list, int psm)
-{
- device->primaries = g_slist_concat(device->primaries, prim_list);
-}
-
static void search_cb(sdp_list_t *recs, int err, gpointer user_data)
{
struct browse_req *req = user_data;
@@ -3509,26 +3620,6 @@ static void send_le_browse_response(struct browse_req *req)
g_dbus_send_reply(dbus_conn, msg, DBUS_TYPE_INVALID);
}
-static void add_primary(struct gatt_db_attribute *attr, void *user_data)
-{
- struct gatt_primary *prim;
- GSList **services = user_data;
- bt_uuid_t uuid;
-
- prim = g_new0(struct gatt_primary, 1);
- if (!prim) {
- DBG("Failed to allocate gatt_primary structure");
- return;
- }
-
- gatt_db_attribute_get_service_handles(attr, &prim->range.start,
- &prim->range.end);
- gatt_db_attribute_get_service_uuid(attr, &uuid);
- bt_uuid_to_string(&uuid, prim->uuid, sizeof(prim->uuid));
-
- *services = g_slist_append(*services, prim);
-}
-
static void register_gatt_services(struct browse_req *req)
{
struct btd_device *device = req->device;
--
2.2.0.rc0.207.ga3a616c
This patch changes the GATT service discovery code path with the
following:
- As part for device_attach_attrib, a bt_gatt_client structure is
created that performs MTU exchange and service discovery, caches the
services in an internal list, and handles the remote GATT service.
- The device_browse_primary code path obtains service information
by iterating through bt_gatt_client's services, instead of directly
initiating primary and secondary service discovery using attrib/gatt.
If the bt_gatt_client isn't ready at the time, the code defers
registration of services and profile probing until the ready handler
is called.
- The attio connected callbacks are invoked from bt_gatt_client's
ready handler instead of device_attach_attrib.
---
src/device.c | 310 ++++++++++++++++++++++++++-----------------------
src/shared/att-types.h | 3 +-
2 files changed, 169 insertions(+), 144 deletions(-)
diff --git a/src/device.c b/src/device.c
index 84ba88e..bb80409 100644
--- a/src/device.c
+++ b/src/device.c
@@ -45,10 +45,13 @@
#include "log.h"
+#include "lib/uuid.h"
#include "src/shared/util.h"
#include "src/shared/att.h"
+#include "src/shared/queue.h"
+#include "src/shared/gatt-db.h"
+#include "src/shared/gatt-client.h"
#include "btio/btio.h"
-#include "lib/uuid.h"
#include "lib/mgmt.h"
#include "attrib/att.h"
#include "hcid.h"
@@ -73,6 +76,10 @@
#define DISCONNECT_TIMER 2
#define DISCOVERY_TIMER 1
+#ifndef MIN
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
static DBusConnection *dbus_conn = NULL;
unsigned service_state_cb_id;
@@ -204,9 +211,18 @@ struct btd_device {
GSList *attios_offline;
guint attachid; /* Attrib server attach */
- struct bt_att *att; /* The new ATT transport */
+ struct bt_att *att; /* The new ATT transport */
+ uint16_t att_mtu; /* The ATT MTU */
unsigned int att_disconn_id;
+ /*
+ * TODO: For now, device creates and owns the client-role gatt_db, but
+ * this needs to be persisted in a more central place so that proper
+ * attribute cache support can be built.
+ */
+ struct gatt_db *client_db; /* GATT client cache */
+ struct bt_gatt_client *gatt_client; /* GATT client implementation */
+
struct bearer_state bredr_state;
struct bearer_state le_state;
@@ -235,7 +251,7 @@ static const uint16_t uuid_list[] = {
0
};
-static int device_browse_primary(struct btd_device *device, DBusMessage *msg);
+static int device_browse_gatt(struct btd_device *device, DBusMessage *msg);
static int device_browse_sdp(struct btd_device *device, DBusMessage *msg);
static struct bearer_state *get_state(struct btd_device *dev,
@@ -462,6 +478,21 @@ static void browse_request_free(struct browse_req *req)
g_free(req);
}
+static void gatt_client_cleanup(struct btd_device *device)
+{
+ if (!device->gatt_client)
+ return;
+
+ bt_gatt_client_set_service_changed(device->gatt_client, NULL, NULL,
+ NULL);
+ bt_gatt_client_set_ready_handler(device->gatt_client, NULL, NULL, NULL);
+ bt_gatt_client_unref(device->gatt_client);
+ device->gatt_client = NULL;
+
+ if (!device->le_state.bonded)
+ gatt_db_clear(device->client_db);
+}
+
static void attio_cleanup(struct btd_device *device)
{
if (device->attachid) {
@@ -479,6 +510,8 @@ static void attio_cleanup(struct btd_device *device)
device->att_io = NULL;
}
+ gatt_client_cleanup(device);
+
if (device->att) {
bt_att_unref(device->att);
device->att = NULL;
@@ -529,6 +562,8 @@ static void device_free(gpointer user_data)
attio_cleanup(device);
+ gatt_db_unref(device->client_db);
+
if (device->tmp_records)
sdp_list_free(device->tmp_records,
(sdp_free_func_t) sdp_record_free);
@@ -1449,7 +1484,7 @@ resolve_services:
if (bdaddr_type == BDADDR_BREDR)
err = device_browse_sdp(dev, msg);
else
- err = device_browse_primary(dev, msg);
+ err = device_browse_gatt(dev, msg);
if (err < 0)
return btd_error_failed(msg, strerror(-err));
@@ -2343,6 +2378,12 @@ static struct btd_device *device_new(struct btd_adapter *adapter,
if (device == NULL)
return NULL;
+ device->client_db = gatt_db_new();
+ if (!device->client_db) {
+ g_free(device);
+ return NULL;
+ }
+
address_up = g_ascii_strup(address, -1);
device->path = g_strdup_printf("%s/dev_%s", adapter_path, address_up);
g_strdelimit(device->path, ":", '_');
@@ -3161,7 +3202,7 @@ static int primary_cmp(gconstpointer a, gconstpointer b)
return memcmp(a, b, sizeof(struct gatt_primary));
}
-static void update_gatt_services(struct browse_req *req, GSList *current,
+static void update_gatt_uuids(struct browse_req *req, GSList *current,
GSList *found)
{
GSList *l, *lmatch;
@@ -3438,41 +3479,6 @@ done:
attio_cleanup(device);
}
-static void register_all_services(struct browse_req *req, GSList *services)
-{
- struct btd_device *device = req->device;
-
- btd_device_set_temporary(device, FALSE);
-
- update_gatt_services(req, device->primaries, services);
- g_slist_free_full(device->primaries, g_free);
- device->primaries = NULL;
-
- device_register_primaries(device, services, -1);
-
- device_probe_profiles(device, req->profiles_added);
-
- if (device->attios == NULL && device->attios_offline == NULL)
- attio_cleanup(device);
-
- g_dbus_emit_property_changed(dbus_conn, device->path,
- DEVICE_INTERFACE, "UUIDs");
-
- device_svc_resolved(device, device->bdaddr_type, 0);
-
- store_services(device);
-
- browse_request_free(req);
-}
-
-static int service_by_range_cmp(gconstpointer a, gconstpointer b)
-{
- const struct gatt_primary *prim = a;
- const struct att_range *range = b;
-
- return memcmp(&prim->range, range, sizeof(*range));
-}
-
static void send_le_browse_response(struct browse_req *req)
{
struct btd_device *dev = req->device;
@@ -3503,122 +3509,132 @@ static void send_le_browse_response(struct browse_req *req)
g_dbus_send_reply(dbus_conn, msg, DBUS_TYPE_INVALID);
}
-static void find_included_cb(uint8_t status, GSList *includes, void *user_data)
+static void add_primary(struct gatt_db_attribute *attr, void *user_data)
{
- struct included_search *search = user_data;
- struct btd_device *device = search->req->device;
struct gatt_primary *prim;
- GSList *l;
+ GSList **services = user_data;
+ bt_uuid_t uuid;
- DBG("status %u", status);
+ prim = g_new0(struct gatt_primary, 1);
+ if (!prim) {
+ DBG("Failed to allocate gatt_primary structure");
+ return;
+ }
- if (device->attrib == NULL || status) {
- struct browse_req *req = device->browse;
+ gatt_db_attribute_get_service_handles(attr, &prim->range.start,
+ &prim->range.end);
+ gatt_db_attribute_get_service_uuid(attr, &uuid);
+ bt_uuid_to_string(&uuid, prim->uuid, sizeof(prim->uuid));
- if (status)
- error("Find included services failed: %s (%d)",
- att_ecode2str(status), status);
- else
- error("Disconnected while doing included discovery");
+ *services = g_slist_append(*services, prim);
+}
- if (!req)
- goto complete;
+static void register_gatt_services(struct browse_req *req)
+{
+ struct btd_device *device = req->device;
+ GSList *services = NULL;
- send_le_browse_response(req);
- device->browse = NULL;
- browse_request_free(req);
+ if (!bt_gatt_client_is_ready(device->gatt_client))
+ return;
- goto complete;
- }
+ /*
+ * TODO: Remove the primaries list entirely once all profiles use
+ * shared/gatt.
+ */
+ gatt_db_foreach_service(device->client_db, NULL, add_primary,
+ &services);
- if (includes == NULL)
- goto next;
+ btd_device_set_temporary(device, FALSE);
- for (l = includes; l; l = l->next) {
- struct gatt_included *incl = l->data;
+ update_gatt_uuids(req, device->primaries, services);
+ g_slist_free_full(device->primaries, g_free);
+ device->primaries = NULL;
- if (g_slist_find_custom(search->services, &incl->range,
- service_by_range_cmp))
- continue;
+ device_register_primaries(device, services, -1);
- prim = g_new0(struct gatt_primary, 1);
- memcpy(prim->uuid, incl->uuid, sizeof(prim->uuid));
- memcpy(&prim->range, &incl->range, sizeof(prim->range));
+ device_probe_profiles(device, req->profiles_added);
- search->services = g_slist_append(search->services, prim);
- }
+ if (device->attios == NULL && device->attios_offline == NULL)
+ attio_cleanup(device);
-next:
- search->current = search->current->next;
- if (search->current == NULL) {
- register_all_services(search->req, search->services);
- search->services = NULL;
- goto complete;
- }
+ device_svc_resolved(device, device->bdaddr_type, 0);
- prim = search->current->data;
- gatt_find_included(device->attrib, prim->range.start, prim->range.end,
- find_included_cb, search);
- return;
+ store_services(device);
-complete:
- g_slist_free_full(search->services, g_free);
- g_free(search);
+ browse_request_free(req);
}
-static void find_included_services(struct browse_req *req, GSList *services)
+static void gatt_client_ready_cb(bool success, uint8_t att_ecode,
+ void *user_data)
{
- struct btd_device *device = req->device;
- struct included_search *search;
- struct gatt_primary *prim;
- GSList *l;
+ struct btd_device *device = user_data;
+
+ DBG("gatt-client ready -- status: %s, ATT error: %u",
+ success ? "success" : "failed",
+ att_ecode);
- DBG("service count %u", g_slist_length(services));
+ if (!success) {
+ if (device->browse) {
+ struct browse_req *req = device->browse;
+
+ send_le_browse_response(req);
+ device->browse = NULL;
+ browse_request_free(req);
+ }
- if (services == NULL) {
- DBG("No services found");
- register_all_services(req, NULL);
return;
}
- search = g_new0(struct included_search, 1);
- search->req = req;
+ device->att_mtu = bt_att_get_mtu(device->att);
- /* We have to completely duplicate the data in order to have a
- * clearly defined responsibility of freeing regardless of
- * failure or success. Otherwise memory leaks are inevitable.
- */
- for (l = services; l; l = g_slist_next(l)) {
- struct gatt_primary *dup;
+ DBG("gatt-client exchanged MTU: %u", device->att_mtu);
- dup = g_memdup(l->data, sizeof(struct gatt_primary));
-
- search->services = g_slist_append(search->services, dup);
- }
+ if (device->browse)
+ register_gatt_services(device->browse);
- search->current = search->services;
+ /*
+ * TODO: Change attio callbacks to accept a gatt-client instead of a
+ * GAttrib.
+ */
+ g_slist_foreach(device->attios, attio_connected, device->attrib);
+}
- prim = search->current->data;
- gatt_find_included(device->attrib, prim->range.start, prim->range.end,
- find_included_cb, search);
+static void gatt_client_service_changed(uint16_t start_handle,
+ uint16_t end_handle,
+ void *user_data)
+{
+ DBG("gatt-client: Service Changed: start 0x%04x, end: 0x%04x",
+ start_handle, end_handle);
}
-static void primary_cb(uint8_t status, GSList *services, void *user_data)
+static void gatt_client_init(struct btd_device *device)
{
- struct browse_req *req = user_data;
+ gatt_client_cleanup(device);
- DBG("status %u", status);
+ device->gatt_client = bt_gatt_client_new(device->client_db, device->att,
+ device->att_mtu);
+ if (!device->gatt_client) {
+ DBG("Failed to initialize gatt-client");
+ return;
+ }
- if (status) {
- struct btd_device *device = req->device;
+ if (!bt_gatt_client_set_ready_handler(device->gatt_client,
+ gatt_client_ready_cb,
+ device, NULL)) {
+ DBG("Failed to set ready handler for gatt-client");
+ gatt_client_cleanup(device);
+ return;
+ }
- send_le_browse_response(req);
- device->browse = NULL;
- browse_request_free(req);
+ if (!bt_gatt_client_set_service_changed(device->gatt_client,
+ gatt_client_service_changed,
+ device, NULL)) {
+ DBG("Failed to set service changed handler for gatt-client");
+ gatt_client_cleanup(device);
return;
}
- find_included_services(req, services);
+ DBG("gatt-client created");
}
bool device_attach_attrib(struct btd_device *dev, GIOChannel *io)
@@ -3653,10 +3669,8 @@ bool device_attach_attrib(struct btd_device *dev, GIOChannel *io)
}
}
- if (cid == ATT_CID)
- mtu = BT_ATT_DEFAULT_LE_MTU;
-
- attrib = g_attrib_new(io, mtu);
+ dev->att_mtu = MIN(mtu, BT_ATT_MAX_LE_MTU);
+ attrib = g_attrib_new(io, dev->att_mtu);
if (!attrib) {
error("Unable to create new GAttrib instance");
return false;
@@ -3678,6 +3692,8 @@ bool device_attach_attrib(struct btd_device *dev, GIOChannel *io)
att_disconnected_cb, dev, NULL);
bt_att_set_close_on_unref(dev->att, true);
+ gatt_client_init(dev);
+
/*
* Remove the device from the connect_list and give the passive
* scanning another chance to be restarted in case there are
@@ -3685,8 +3701,6 @@ bool device_attach_attrib(struct btd_device *dev, GIOChannel *io)
*/
adapter_connect_list_remove(dev->adapter, dev);
- g_slist_foreach(dev->attios, attio_connected, dev->attrib);
-
return true;
}
@@ -3737,7 +3751,7 @@ done:
if (device->connect) {
if (!device->le_state.svc_resolved)
- device_browse_primary(device, NULL);
+ device_browse_gatt(device, NULL);
if (err < 0)
reply = btd_error_failed(device->connect,
@@ -3840,16 +3854,32 @@ static void att_browse_error_cb(const GError *gerr, gpointer user_data)
browse_request_free(req);
}
+static void browse_gatt_client(struct browse_req *req)
+{
+ struct btd_device *device = req->device;
+
+ if (!device->gatt_client) {
+ DBG("No gatt-client currently attached");
+ return;
+ }
+
+ /*
+ * If gatt-client is ready, then register all services. Otherwise, this
+ * will be deferred until client becomes ready.
+ */
+ if (bt_gatt_client_is_ready(device->gatt_client))
+ register_gatt_services(req);
+}
+
static void att_browse_cb(gpointer user_data)
{
struct att_callbacks *attcb = user_data;
struct btd_device *device = attcb->user_data;
- gatt_discover_primary(device->attrib, NULL, primary_cb,
- device->browse);
+ browse_gatt_client(device->browse);
}
-static int device_browse_primary(struct btd_device *device, DBusMessage *msg)
+static int device_browse_gatt(struct btd_device *device, DBusMessage *msg)
{
struct btd_adapter *adapter = device->adapter;
struct att_callbacks *attcb;
@@ -3864,7 +3894,7 @@ static int device_browse_primary(struct btd_device *device, DBusMessage *msg)
device->browse = req;
if (device->attrib) {
- gatt_discover_primary(device->attrib, NULL, primary_cb, req);
+ browse_gatt_client(device->browse);
goto done;
}
@@ -3981,7 +4011,7 @@ int device_discover_services(struct btd_device *device)
if (device->bredr)
err = device_browse_sdp(device, NULL);
else
- err = device_browse_primary(device, NULL);
+ err = device_browse_gatt(device, NULL);
if (err == 0 && device->discov_timer) {
g_source_remove(device->discov_timer);
@@ -4127,7 +4157,7 @@ static gboolean start_discovery(gpointer user_data)
if (device->bredr)
device_browse_sdp(device, NULL);
else
- device_browse_primary(device, NULL);
+ device_browse_gatt(device, NULL);
device->discov_timer = 0;
@@ -4264,7 +4294,7 @@ void device_bonding_complete(struct btd_device *device, uint8_t bdaddr_type,
if (bdaddr_type == BDADDR_BREDR)
device_browse_sdp(device, bonding->msg);
else
- device_browse_primary(device, bonding->msg);
+ device_browse_gatt(device, bonding->msg);
bonding_request_free(bonding);
} else if (!state->svc_resolved) {
@@ -4737,16 +4767,10 @@ GSList *btd_device_get_primaries(struct btd_device *device)
void btd_device_gatt_set_service_changed(struct btd_device *device,
uint16_t start, uint16_t end)
{
- GSList *l;
-
- for (l = device->primaries; l; l = g_slist_next(l)) {
- struct gatt_primary *prim = l->data;
-
- if (start <= prim->range.end && end >= prim->range.start)
- prim->changed = TRUE;
- }
-
- device_browse_primary(device, NULL);
+ /*
+ * TODO: Remove this function and handle service changed via
+ * gatt-client.
+ */
}
void btd_device_add_uuid(struct btd_device *device, const char *uuid)
diff --git a/src/shared/att-types.h b/src/shared/att-types.h
index 3d8829a..8b6d537 100644
--- a/src/shared/att-types.h
+++ b/src/shared/att-types.h
@@ -27,7 +27,8 @@
#define __packed __attribute__((packed))
#endif
-#define BT_ATT_DEFAULT_LE_MTU 23
+#define BT_ATT_DEFAULT_LE_MTU 23
+#define BT_ATT_MAX_LE_MTU 517
/* ATT protocol opcodes */
#define BT_ATT_OP_ERROR_RSP 0x01
--
2.2.0.rc0.207.ga3a616c
This patch renames the device_attach_attrib function to
device_attach_att_transport to remove ambiguities arise from the word
"attrib".
---
src/attrib-server.c | 2 +-
src/device.c | 4 ++--
src/device.h | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/attrib-server.c b/src/attrib-server.c
index 6571577..e73850d 100644
--- a/src/attrib-server.c
+++ b/src/attrib-server.c
@@ -1300,7 +1300,7 @@ static void connect_event(GIOChannel *io, GError *gerr, void *user_data)
if (!device)
return;
- device_attach_attrib(device, io);
+ device_attach_att_transport(device, io);
}
static gboolean register_core_services(struct gatt_server *server)
diff --git a/src/device.c b/src/device.c
index bb80409..3bd3939 100644
--- a/src/device.c
+++ b/src/device.c
@@ -3637,7 +3637,7 @@ static void gatt_client_init(struct btd_device *device)
DBG("gatt-client created");
}
-bool device_attach_attrib(struct btd_device *dev, GIOChannel *io)
+bool device_attach_att_transport(struct btd_device *dev, GIOChannel *io)
{
GError *gerr = NULL;
GAttrib *attrib;
@@ -3725,7 +3725,7 @@ static void att_connect_cb(GIOChannel *io, GError *gerr, gpointer user_data)
goto done;
}
- if (!device_attach_attrib(device, io))
+ if (!device_attach_att_transport(device, io))
goto done;
if (attcb->success)
diff --git a/src/device.h b/src/device.h
index 2e0473e..10c4951 100644
--- a/src/device.h
+++ b/src/device.h
@@ -69,7 +69,7 @@ struct gatt_primary *btd_device_get_primary(struct btd_device *device,
GSList *btd_device_get_primaries(struct btd_device *device);
void btd_device_gatt_set_service_changed(struct btd_device *device,
uint16_t start, uint16_t end);
-bool device_attach_attrib(struct btd_device *dev, GIOChannel *io);
+bool device_attach_att_transport(struct btd_device *dev, GIOChannel *io);
void btd_device_add_uuid(struct btd_device *device, const char *uuid);
void device_add_eir_uuids(struct btd_device *dev, GSList *uuids);
void device_probe_profile(gpointer a, gpointer b);
--
2.2.0.rc0.207.ga3a616c
btd_device is now notified of ATT channel disconnections by registering
a disconnect handler with bt_att instead of setting up an io watch with
the GIOChannel.
---
src/device.c | 39 +++++++++++++++++++++------------------
1 file changed, 21 insertions(+), 18 deletions(-)
diff --git a/src/device.c b/src/device.c
index e9630ed..84ba88e 100644
--- a/src/device.c
+++ b/src/device.c
@@ -46,6 +46,7 @@
#include "log.h"
#include "src/shared/util.h"
+#include "src/shared/att.h"
#include "btio/btio.h"
#include "lib/uuid.h"
#include "lib/mgmt.h"
@@ -203,6 +204,9 @@ struct btd_device {
GSList *attios_offline;
guint attachid; /* Attrib server attach */
+ struct bt_att *att; /* The new ATT transport */
+ unsigned int att_disconn_id;
+
struct bearer_state bredr_state;
struct bearer_state le_state;
@@ -221,7 +225,6 @@ struct btd_device {
int8_t rssi;
GIOChannel *att_io;
- guint cleanup_id;
guint store_id;
};
@@ -466,10 +469,9 @@ static void attio_cleanup(struct btd_device *device)
device->attachid = 0;
}
- if (device->cleanup_id) {
- g_source_remove(device->cleanup_id);
- device->cleanup_id = 0;
- }
+ if (device->att_disconn_id)
+ bt_att_unregister_disconnect(device->att,
+ device->att_disconn_id);
if (device->att_io) {
g_io_channel_shutdown(device->att_io, FALSE, NULL);
@@ -477,6 +479,11 @@ static void attio_cleanup(struct btd_device *device)
device->att_io = NULL;
}
+ if (device->att) {
+ bt_att_unref(device->att);
+ device->att = NULL;
+ }
+
if (device->attrib) {
GAttrib *attrib = device->attrib;
device->attrib = NULL;
@@ -3401,22 +3408,15 @@ static void attio_disconnected(gpointer data, gpointer user_data)
attio->dcfunc(attio->user_data);
}
-static gboolean attrib_disconnected_cb(GIOChannel *io, GIOCondition cond,
- gpointer user_data)
+static void att_disconnected_cb(int err, void *user_data)
{
struct btd_device *device = user_data;
- int sock, err = 0;
- socklen_t len;
DBG("");
if (device->browse)
goto done;
- sock = g_io_channel_unix_get_fd(io);
- len = sizeof(err);
- getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &len);
-
DBG("%s (%d)", strerror(err), err);
g_slist_foreach(device->attios, attio_disconnected, NULL);
@@ -3436,8 +3436,6 @@ static gboolean attrib_disconnected_cb(GIOChannel *io, GIOCondition cond,
done:
attio_cleanup(device);
-
- return FALSE;
}
static void register_all_services(struct browse_req *req, GSList *services)
@@ -3656,7 +3654,7 @@ bool device_attach_attrib(struct btd_device *dev, GIOChannel *io)
}
if (cid == ATT_CID)
- mtu = ATT_DEFAULT_LE_MTU;
+ mtu = BT_ATT_DEFAULT_LE_MTU;
attrib = g_attrib_new(io, mtu);
if (!attrib) {
@@ -3672,8 +3670,13 @@ bool device_attach_attrib(struct btd_device *dev, GIOChannel *io)
}
dev->attrib = attrib;
- dev->cleanup_id = g_io_add_watch(io, G_IO_HUP,
- attrib_disconnected_cb, dev);
+ dev->att = g_attrib_get_att(attrib);
+
+ bt_att_ref(dev->att);
+
+ dev->att_disconn_id = bt_att_register_disconnect(dev->att,
+ att_disconnected_cb, dev, NULL);
+ bt_att_set_close_on_unref(dev->att, true);
/*
* Remove the device from the connect_list and give the passive
--
2.2.0.rc0.207.ga3a616c
This patch fixes a bug where not correctly setting the success field of
the discovery_op instance when gatt-client is initialized with a
non-empty db caused the db to get cleared by the init_failure handler.
---
src/shared/gatt-client.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index 41df669..6ca027f 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
@@ -893,6 +893,8 @@ static void exchange_mtu_cb(bool success, uint8_t att_ecode, void *user_data)
struct discovery_op *op = user_data;
struct bt_gatt_client *client = op->client;
+ op->success = success;
+
if (!success) {
util_debug(client->debug_callback, client->debug_data,
"MTU Exchange failed. ATT ECODE: 0x%02x",
@@ -1188,6 +1190,8 @@ fail:
util_debug(client->debug_callback, client->debug_data,
"Failed to initialize gatt-client");
+ op->success = false;
+
done:
if (client->ready_callback)
client->ready_callback(success, att_ecode, client->ready_data);
--
2.2.0.rc0.207.ga3a616c
This patch adds an "int err" argument to the disconnect callback.
shared/att obtains the value of SO_ERROR option for the underlying
fd and passes it to the callbacks.
---
src/shared/att.c | 25 ++++++++++++++++++++-----
src/shared/att.h | 2 +-
src/shared/gatt-client.c | 2 +-
tools/btgatt-client.c | 4 ++--
tools/btgatt-server.c | 4 ++--
5 files changed, 26 insertions(+), 11 deletions(-)
diff --git a/src/shared/att.c b/src/shared/att.c
index 447863d..6a733fa 100644
--- a/src/shared/att.c
+++ b/src/shared/att.c
@@ -502,28 +502,43 @@ static void wakeup_writer(struct bt_att *att)
static void disconn_handler(void *data, void *user_data)
{
struct att_disconn *disconn = data;
+ int err = PTR_TO_INT(user_data);
if (disconn->removed)
return;
if (disconn->callback)
- disconn->callback(disconn->user_data);
+ disconn->callback(err, disconn->user_data);
}
static bool disconnect_cb(struct io *io, void *user_data)
{
struct bt_att *att = user_data;
+ int err;
+ socklen_t len;
- io_destroy(att->io);
- att->io = NULL;
+ len = sizeof(err);
+
+ if (getsockopt(att->fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
+ util_debug(att->debug_callback, att->debug_data,
+ "Failed to obtain disconnect error: %s",
+ strerror(errno));
+ err = 0;
+ }
util_debug(att->debug_callback, att->debug_data,
- "Physical link disconnected");
+ "Physical link disconnected: %s",
+ strerror(err));
+
+ io_destroy(att->io);
+ att->io = NULL;
bt_att_cancel_all(att);
bt_att_ref(att);
- queue_foreach(att->disconn_list, disconn_handler, NULL);
+
+ queue_foreach(att->disconn_list, disconn_handler, INT_TO_PTR(err));
+
bt_att_unregister_all(att);
bt_att_unref(att);
diff --git a/src/shared/att.h b/src/shared/att.h
index 99b5a5b..cd00a1e 100644
--- a/src/shared/att.h
+++ b/src/shared/att.h
@@ -43,7 +43,7 @@ typedef void (*bt_att_destroy_func_t)(void *user_data);
typedef void (*bt_att_debug_func_t)(const char *str, void *user_data);
typedef void (*bt_att_timeout_func_t)(unsigned int id, uint8_t opcode,
void *user_data);
-typedef void (*bt_att_disconnect_func_t)(void *user_data);
+typedef void (*bt_att_disconnect_func_t)(int err, void *user_data);
bool bt_att_set_debug(struct bt_att *att, bt_att_debug_func_t callback,
void *user_data, bt_att_destroy_func_t destroy);
diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index a15f3b8..41df669 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
@@ -1436,7 +1436,7 @@ static void bt_gatt_client_free(struct bt_gatt_client *client)
free(client);
}
-static void att_disconnect_cb(void *user_data)
+static void att_disconnect_cb(int err, void *user_data)
{
struct bt_gatt_client *client = user_data;
bool in_init = client->in_init;
diff --git a/tools/btgatt-client.c b/tools/btgatt-client.c
index 015142d..e2e0537 100644
--- a/tools/btgatt-client.c
+++ b/tools/btgatt-client.c
@@ -74,9 +74,9 @@ static void print_prompt(void)
fflush(stdout);
}
-static void att_disconnect_cb(void *user_data)
+static void att_disconnect_cb(int err, void *user_data)
{
- printf("Device disconnected\n");
+ printf("Device disconnected: %s\n", strerror(err));
mainloop_quit();
}
diff --git a/tools/btgatt-server.c b/tools/btgatt-server.c
index 1a9b9fb..f6ad8c3 100644
--- a/tools/btgatt-server.c
+++ b/tools/btgatt-server.c
@@ -101,9 +101,9 @@ static void print_prompt(void)
fflush(stdout);
}
-static void att_disconnect_cb(void *user_data)
+static void att_disconnect_cb(int err, void *user_data)
{
- printf("Device disconnected\n");
+ printf("Device disconnected: %s\n", strerror(err));
mainloop_quit();
}
--
2.2.0.rc0.207.ga3a616c
Added the g_attrib_get_att function which returns the underlying bt_att
structure associated with a GAttrib.
---
attrib/gattrib.c | 8 ++++++++
attrib/gattrib.h | 3 +++
2 files changed, 11 insertions(+)
diff --git a/attrib/gattrib.c b/attrib/gattrib.c
index 3ce6748..ab43f84 100644
--- a/attrib/gattrib.c
+++ b/attrib/gattrib.c
@@ -169,6 +169,14 @@ GIOChannel *g_attrib_get_channel(GAttrib *attrib)
return attrib->io;
}
+struct bt_att *g_attrib_get_att(GAttrib *attrib)
+{
+ if (!attrib)
+ return NULL;
+
+ return attrib->att;
+}
+
gboolean g_attrib_set_destroy_function(GAttrib *attrib, GDestroyNotify destroy,
gpointer user_data)
{
diff --git a/attrib/gattrib.h b/attrib/gattrib.h
index 2ed57c1..374bac2 100644
--- a/attrib/gattrib.h
+++ b/attrib/gattrib.h
@@ -31,6 +31,7 @@ extern "C" {
#define GATTRIB_ALL_REQS 0xFE
#define GATTRIB_ALL_HANDLES 0x0000
+struct bt_att; /* Forward declaration for compatibility */
struct _GAttrib;
typedef struct _GAttrib GAttrib;
@@ -47,6 +48,8 @@ void g_attrib_unref(GAttrib *attrib);
GIOChannel *g_attrib_get_channel(GAttrib *attrib);
+struct bt_att *g_attrib_get_att(GAttrib *attrib);
+
gboolean g_attrib_set_destroy_function(GAttrib *attrib,
GDestroyNotify destroy, gpointer user_data);
--
2.2.0.rc0.207.ga3a616c