diff --git a/src/device.c b/src/device.c
index 84ba88e..51fe402 100644
--- a/src/device.c
+++ b/src/device.c
#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"
#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;
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 *db; /* GATT db cache */
+ struct bt_gatt_client *client; /* GATT client instance */
+
struct bearer_state bredr_state;
struct bearer_state le_state;
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,
g_free(req);
}
+static void gatt_client_cleanup(struct btd_device *device)
+{
+ if (!device->client)
+ return;
+
+ bt_gatt_client_set_service_changed(device->client, NULL, NULL, NULL);
+ bt_gatt_client_set_ready_handler(device->client, NULL, NULL, NULL);
+ bt_gatt_client_unref(device->client);
+ device->client = NULL;
+
+ if (!device->le_state.bonded)
+ gatt_db_clear(device->db);
+}
+
static void attio_cleanup(struct btd_device *device)
{
if (device->attachid) {
device->att_io = NULL;
}
+ gatt_client_cleanup(device);
+
if (device->att) {
bt_att_unref(device->att);
device->att = NULL;
attio_cleanup(device);
+ gatt_db_unref(device->db);
+
if (device->tmp_records)
sdp_list_free(device->tmp_records,
(sdp_free_func_t) sdp_record_free);
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));
if (device == NULL)
return NULL;
+ device->db = gatt_db_new();
+ if (!device->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, ":", '_');
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;
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;
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->client))
+ return;
- goto complete;
- }
+ /*
+ * TODO: Remove the primaries list entirely once all profiles use
+ * shared/gatt.
+ */
+ gatt_db_foreach_service(device->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("service count %u", g_slist_length(services));
+ DBG("status: %s, error: %u", success ? "success" : "failed", att_ecode);
- if (services == NULL) {
- DBG("No services found");
- register_all_services(req, NULL);
- return;
- }
+ if (!success) {
+ if (device->browse) {
+ struct browse_req *req = device->browse;
- search = g_new0(struct included_search, 1);
- search->req = req;
+ send_le_browse_response(req);
+ device->browse = NULL;
+ browse_request_free(req);
+ }
- /* 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;
+ return;
+ }
- dup = g_memdup(l->data, sizeof(struct gatt_primary));
+ device->att_mtu = bt_att_get_mtu(device->att);
- search->services = g_slist_append(search->services, dup);
- }
+ DBG("MTU: %u", device->att_mtu);
- search->current = search->services;
+ if (device->browse)
+ register_gatt_services(device->browse);
- prim = search->current->data;
- gatt_find_included(device->attrib, prim->range.start, prim->range.end,
- find_included_cb, search);
+ /*
+ * TODO: Change attio callbacks to accept a gatt-client instead of a
+ * GAttrib.
+ */
+ g_slist_foreach(device->attios, attio_connected, device->attrib);
}
-static void primary_cb(uint8_t status, GSList *services, void *user_data)
+static void gatt_client_service_changed(uint16_t start_handle,
+ uint16_t end_handle,
+ void *user_data)
{
- struct browse_req *req = user_data;
+ DBG("start 0x%04x, end: 0x%04x", start_handle, end_handle);
+}
- DBG("status %u", status);
+static void gatt_client_init(struct btd_device *device)
+{
+ gatt_client_cleanup(device);
- if (status) {
- struct btd_device *device = req->device;
+ device->client = bt_gatt_client_new(device->db, device->att,
+ device->att_mtu);
+ if (!device->client) {
+ DBG("Failed to initialize");
+ return;
+ }
- send_le_browse_response(req);
- device->browse = NULL;
- browse_request_free(req);
+ if (!bt_gatt_client_set_ready_handler(device->client,
+ gatt_client_ready_cb,
+ device, NULL)) {
+ DBG("Failed to set ready handler");
+ gatt_client_cleanup(device);
return;
}
- find_included_services(req, services);
+ if (!bt_gatt_client_set_service_changed(device->client,
+ gatt_client_service_changed,
+ device, NULL)) {
+ DBG("Failed to set service changed handler");
+ gatt_client_cleanup(device);
+ return;
+ }
}
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;
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
*/
adapter_connect_list_remove(dev->adapter, dev);
- g_slist_foreach(dev->attios, attio_connected, dev->attrib);
-
return true;
}
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,
browse_request_free(req);
}
+static void browse_gatt_client(struct browse_req *req)
+{
+ struct btd_device *device = req->device;
+
+ if (!device->client) {
+ DBG("No instance 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->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;
device->browse = req;
if (device->attrib) {
- gatt_discover_primary(device->attrib, NULL, primary_cb, req);
+ browse_gatt_client(device->browse);
goto done;
}
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);
if (device->bredr)
device_browse_sdp(device, NULL);
else
- device_browse_primary(device, NULL);
+ device_browse_gatt(device, NULL);
device->discov_timer = 0;
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) {
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
#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