This patch set adds handling for indications from the "Service Changed"
characteristic. gatt-client automatically caches the value handle of the Service
Changed characteristic and listens to indications from it, if the characteristic
exists. When an indication is received, services within the modified range are
automatically re-discovered and gatt-client's internal service cache gets
updated. Upper layers can register a callback, which then gets invoked to notify
them of modifications to the service cache.
Arman Uguray (9):
shared/gatt-helpers: Allow service discovery by handle range.
shared/gatt-client: Make service discovery more modular.
shared/gatt-client: Register "Service Changed" handler as part of
init.
shared/gatt-client: Remove effected services from cache on Service
Changed.
shared/gatt-client: Rediscover services within changed range.
shared/gatt-client: Add handler for "Service Changed" if GATT service
changes.
shared/gatt-client: Add bt_gatt_client_set_service_changed.
tools/btgatt-client: Set service changed handler.
TODO: shared/gatt-client now handles Service Changed
TODO | 7 -
src/shared/gatt-client.c | 590 +++++++++++++++++++++++++++++++++++++++-------
src/shared/gatt-client.h | 7 +
src/shared/gatt-helpers.c | 40 +++-
src/shared/gatt-helpers.h | 5 +
tools/btgatt-client.c | 34 +++
6 files changed, 585 insertions(+), 98 deletions(-)
--
2.1.0.rc2.206.gedb03e5
Hi Arman,
On Tue, Sep 23, 2014, Arman Uguray wrote:
> This patch set adds handling for indications from the "Service Changed"
> characteristic. gatt-client automatically caches the value handle of the Service
> Changed characteristic and listens to indications from it, if the characteristic
> exists. When an indication is received, services within the modified range are
> automatically re-discovered and gatt-client's internal service cache gets
> updated. Upper layers can register a callback, which then gets invoked to notify
> them of modifications to the service cache.
>
> Arman Uguray (9):
> shared/gatt-helpers: Allow service discovery by handle range.
> shared/gatt-client: Make service discovery more modular.
> shared/gatt-client: Register "Service Changed" handler as part of
> init.
> shared/gatt-client: Remove effected services from cache on Service
> Changed.
> shared/gatt-client: Rediscover services within changed range.
> shared/gatt-client: Add handler for "Service Changed" if GATT service
> changes.
> shared/gatt-client: Add bt_gatt_client_set_service_changed.
> tools/btgatt-client: Set service changed handler.
> TODO: shared/gatt-client now handles Service Changed
>
> TODO | 7 -
> src/shared/gatt-client.c | 590 +++++++++++++++++++++++++++++++++++++++-------
> src/shared/gatt-client.h | 7 +
> src/shared/gatt-helpers.c | 40 +++-
> src/shared/gatt-helpers.h | 5 +
> tools/btgatt-client.c | 34 +++
> 6 files changed, 585 insertions(+), 98 deletions(-)
All patches in this set have been applied. Thanks.
Johan
---
TODO | 7 -------
1 file changed, 7 deletions(-)
diff --git a/TODO b/TODO
index 25faeb9..fe5ebe5 100644
--- a/TODO
+++ b/TODO
@@ -105,13 +105,6 @@ ATT/GATT (new shared stack)
Priority: Medium
Complexity: C1
-- Properly handle indications from the "Service Changed" characteristic.
- shared/gatt-client should automatically rediscover all changed GATT services
- and notify the upper layer using a specially assigned handler.
-
- Priority: Medium
- Complexity: C1
-
- Introduce a handler interface to shared/gatt-client which can be used by the
upper layer to determine when the link has been disconnected or an ATT
protocol request times out.
--
2.1.0.rc2.206.gedb03e5
If the GATT service on a peripheral changes gatt-client unregisters its handler
for "Service Changed" indications. This patch adds code that re-registers the
handler at the end of re-discovery if there was a change in the Service Changed
characteristic.
---
src/shared/gatt-client.c | 48 +++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 45 insertions(+), 3 deletions(-)
diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index 3f092ac..e98a164 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
@@ -744,15 +744,36 @@ struct service_changed_op {
uint16_t end_handle;
};
+static void service_changed_reregister_cb(unsigned int id, uint16_t att_ecode,
+ void *user_data)
+{
+ struct bt_gatt_client *client = user_data;
+
+ if (!id || att_ecode) {
+ util_debug(client->debug_callback, client->debug_data,
+ "Failed to register handler for \"Service Changed\"");
+ return;
+ }
+
+ client->svc_chngd_ind_id = id;
+
+ util_debug(client->debug_callback, client->debug_data,
+ "Re-registered handler for \"Service Changed\" after change in "
+ "GATT service");
+}
+
static void process_service_changed(struct bt_gatt_client *client,
uint16_t start_handle,
uint16_t end_handle);
+static void service_changed_cb(uint16_t value_handle, const uint8_t *value,
+ uint16_t length, void *user_data);
static void service_changed_complete(struct discovery_op *op, bool success,
uint8_t att_ecode)
{
struct bt_gatt_client *client = op->client;
struct service_changed_op *next_sc_op;
+ bt_gatt_service_t *head, *tail;
client->in_svc_chngd = false;
@@ -781,9 +802,23 @@ static void service_changed_complete(struct discovery_op *op, bool success,
return;
}
- /* TODO: if the GATT service has changed then register a handler
- * for "Service Changed".
- */
+ /* Check if the GATT service is not present or has remained unchanged */
+ head = &op->result_head->service;
+ tail = &op->result_tail->service;
+ if (!client->svc_chngd_val_handle ||
+ client->svc_chngd_val_handle < head->start_handle ||
+ client->svc_chngd_val_handle > tail->end_handle)
+ return;
+
+ if (bt_gatt_client_register_notify(client,
+ client->svc_chngd_val_handle,
+ service_changed_reregister_cb,
+ service_changed_cb,
+ client, NULL))
+ return;
+
+ util_debug(client->debug_callback, client->debug_data,
+ "Failed to re-register handler for \"Service Changed\"");
}
static void process_service_changed(struct bt_gatt_client *client,
@@ -810,6 +845,13 @@ static void process_service_changed(struct bt_gatt_client *client,
return;
}
+ if (client->gatt_svc_handle >= start_handle &&
+ client->gatt_svc_handle <= end_handle) {
+ client->gatt_svc_handle = 0;
+ client->svc_chngd_val_handle = 0;
+ client->svc_chngd_ind_id = 0;
+ }
+
op->client = client;
op->complete_func = service_changed_complete;
--
2.1.0.rc2.206.gedb03e5
This patch introduces the bt_gatt_client_set_service_changed function which
allows upper layers to register a callback with gatt-client for
"Service Changed" events. The callback gets invoked for each indication from the
Service Changed characteristic after service discovery within the changed range
has completed and gatt-client's service cache has been updated.
---
src/shared/gatt-client.c | 41 ++++++++++++++++++++++++++++++++++++-----
src/shared/gatt-client.h | 7 +++++++
2 files changed, 43 insertions(+), 5 deletions(-)
diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index e98a164..6dc8e95 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
@@ -80,6 +80,10 @@ struct bt_gatt_client {
bt_gatt_client_destroy_func_t ready_destroy;
void *ready_data;
+ bt_gatt_client_service_changed_callback_t svc_chngd_callback;
+ bt_gatt_client_destroy_func_t svc_chngd_destroy;
+ void *svc_chngd_data;
+
bt_gatt_client_debug_func_t debug_callback;
bt_gatt_client_destroy_func_t debug_destroy;
void *debug_data;
@@ -773,7 +777,7 @@ static void service_changed_complete(struct discovery_op *op, bool success,
{
struct bt_gatt_client *client = op->client;
struct service_changed_op *next_sc_op;
- bt_gatt_service_t *head, *tail;
+ uint16_t start_handle, end_handle;
client->in_svc_chngd = false;
@@ -788,11 +792,19 @@ static void service_changed_complete(struct discovery_op *op, bool success,
if (!op->result_head || !op->result_tail)
return;
+ start_handle = op->result_head->service.start_handle;
+ end_handle = op->result_tail->service.end_handle;
+
/* Insert all newly discovered services in their correct place as a
* contiguous chunk */
service_list_insert_services(&client->svc_head, &client->svc_tail,
op->result_head, op->result_tail);
+ /* Notify the upper layer of changed services */
+ if (client->svc_chngd_callback)
+ client->svc_chngd_callback(start_handle, end_handle,
+ client->svc_chngd_data);
+
/* Process any queued events */
next_sc_op = queue_pop_head(client->svc_chngd_queue);
if (next_sc_op) {
@@ -803,13 +815,14 @@ static void service_changed_complete(struct discovery_op *op, bool success,
}
/* Check if the GATT service is not present or has remained unchanged */
- head = &op->result_head->service;
- tail = &op->result_tail->service;
if (!client->svc_chngd_val_handle ||
- client->svc_chngd_val_handle < head->start_handle ||
- client->svc_chngd_val_handle > tail->end_handle)
+ client->svc_chngd_val_handle < start_handle ||
+ client->svc_chngd_val_handle > end_handle)
return;
+ /* The GATT service was modified. Re-register the handler for
+ * indications from the "Service Changed" characteristic.
+ */
if (bt_gatt_client_register_notify(client,
client->svc_chngd_val_handle,
service_changed_reregister_cb,
@@ -1320,6 +1333,24 @@ bool bt_gatt_client_set_ready_handler(struct bt_gatt_client *client,
return true;
}
+bool bt_gatt_client_set_service_changed(struct bt_gatt_client *client,
+ bt_gatt_client_service_changed_callback_t callback,
+ void *user_data,
+ bt_gatt_client_destroy_func_t destroy)
+{
+ if (!client)
+ return false;
+
+ if (client->svc_chngd_destroy)
+ client->svc_chngd_destroy(client->svc_chngd_data);
+
+ client->svc_chngd_callback = callback;
+ client->svc_chngd_destroy = destroy;
+ client->svc_chngd_data = user_data;
+
+ return true;
+}
+
bool bt_gatt_client_set_debug(struct bt_gatt_client *client,
bt_gatt_client_debug_func_t callback,
void *user_data,
diff --git a/src/shared/gatt-client.h b/src/shared/gatt-client.h
index 7612a6e..1c28a82 100644
--- a/src/shared/gatt-client.h
+++ b/src/shared/gatt-client.h
@@ -57,12 +57,19 @@ typedef void (*bt_gatt_client_notify_callback_t)(uint16_t value_handle,
typedef void (*bt_gatt_client_notify_id_callback_t)(unsigned int id,
uint16_t att_ecode,
void *user_data);
+typedef void (*bt_gatt_client_service_changed_callback_t)(uint16_t start_handle,
+ uint16_t end_handle,
+ void *user_data);
bool bt_gatt_client_is_ready(struct bt_gatt_client *client);
bool bt_gatt_client_set_ready_handler(struct bt_gatt_client *client,
bt_gatt_client_callback_t callback,
void *user_data,
bt_gatt_client_destroy_func_t destroy);
+bool bt_gatt_client_set_service_changed(struct bt_gatt_client *client,
+ bt_gatt_client_service_changed_callback_t callback,
+ void *user_data,
+ bt_gatt_client_destroy_func_t destroy);
bool bt_gatt_client_set_debug(struct bt_gatt_client *client,
bt_gatt_client_debug_func_t callback,
void *user_data,
--
2.1.0.rc2.206.gedb03e5
This patch adds the discovery step of handling "Service Changed". A primary
service discovery is performed within the affected handle range and results are
inserted into the service list as a contiguous block of services, sorted by
handle.
---
src/shared/gatt-client.c | 111 ++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 106 insertions(+), 5 deletions(-)
diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index cc0a894..3f092ac 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
@@ -289,6 +289,43 @@ static void service_list_clear_range(struct service_list **head,
}
}
+static void service_list_insert_services(struct service_list **head,
+ struct service_list **tail,
+ struct service_list *svc_head,
+ struct service_list *svc_tail)
+{
+ struct service_list *cur, *prev;
+
+ if (!(*head) || !(*tail)) {
+ *head = svc_head;
+ *tail = svc_tail;
+ return;
+ }
+
+ prev = NULL;
+ cur = *head;
+ while (cur) {
+ if (svc_tail->service.end_handle < cur->service.start_handle) {
+ if (!prev)
+ *head = svc_head;
+ else
+ prev->next = svc_head;
+
+ svc_tail->next = cur;
+ return;
+ }
+
+ prev = cur;
+ cur = cur->next;
+ }
+
+ if (prev != *tail)
+ return;
+
+ prev->next = svc_head;
+ *tail = svc_tail;
+}
+
static void gatt_client_remove_all_notify_in_range(
struct bt_gatt_client *client,
uint16_t start_handle, uint16_t end_handle)
@@ -709,8 +746,52 @@ struct service_changed_op {
static void process_service_changed(struct bt_gatt_client *client,
uint16_t start_handle,
+ uint16_t end_handle);
+
+static void service_changed_complete(struct discovery_op *op, bool success,
+ uint8_t att_ecode)
+{
+ struct bt_gatt_client *client = op->client;
+ struct service_changed_op *next_sc_op;
+
+ client->in_svc_chngd = false;
+
+ if (!success) {
+ util_debug(client->debug_callback, client->debug_data,
+ "Failed to discover services within changed range - "
+ "error: 0x%02x", att_ecode);
+ return;
+ }
+
+ /* No new services in the modified range */
+ if (!op->result_head || !op->result_tail)
+ return;
+
+ /* Insert all newly discovered services in their correct place as a
+ * contiguous chunk */
+ service_list_insert_services(&client->svc_head, &client->svc_tail,
+ op->result_head, op->result_tail);
+
+ /* Process any queued events */
+ next_sc_op = queue_pop_head(client->svc_chngd_queue);
+ if (next_sc_op) {
+ process_service_changed(client, next_sc_op->start_handle,
+ next_sc_op->end_handle);
+ free(next_sc_op);
+ return;
+ }
+
+ /* TODO: if the GATT service has changed then register a handler
+ * for "Service Changed".
+ */
+}
+
+static void process_service_changed(struct bt_gatt_client *client,
+ uint16_t start_handle,
uint16_t end_handle)
{
+ struct discovery_op *op;
+
/* Invalidate and remove all effected notify callbacks */
gatt_client_remove_all_notify_in_range(client, start_handle,
end_handle);
@@ -721,10 +802,30 @@ static void process_service_changed(struct bt_gatt_client *client,
service_list_clear_range(&client->svc_head, &client->svc_tail,
start_handle, end_handle);
- /* TODO Rediscover all services within the modified service range. If
- * the GATT service was effected, then register a new handler for
- * "Service Changed"
- */
+ op = new0(struct discovery_op, 1);
+ if (!op) {
+ util_debug(client->debug_callback, client->debug_data,
+ "Failed to initiate primary service discovery"
+ " after Service Changed");
+ return;
+ }
+
+ op->client = client;
+ op->complete_func = service_changed_complete;
+
+ if (!bt_gatt_discover_primary_services(client->att, NULL,
+ start_handle, end_handle,
+ discover_primary_cb,
+ discovery_op_ref(op),
+ discovery_op_unref)) {
+ util_debug(client->debug_callback, client->debug_data,
+ "Failed to initiate primary service discovery"
+ " after Service Changed");
+ free(op);
+ return;
+ }
+
+ client->in_svc_chngd = true;
}
static void service_changed_cb(uint16_t value_handle, const uint8_t *value,
@@ -1980,7 +2081,7 @@ bool bt_gatt_client_register_notify(struct bt_gatt_client *client,
if (!client || !chrc_value_handle || !callback)
return false;
- if (!bt_gatt_client_is_ready(client))
+ if (!bt_gatt_client_is_ready(client) || client->in_svc_chngd)
return false;
/* Check that chrc_value_handle belongs to a known characteristic */
--
2.1.0.rc2.206.gedb03e5
This patch changes shared/gatt-client so that a struct bt_gatt_client
registers a handler for indications from the "Service Changed" characteristic
at the end of its init sequence if the characteristic was discovered. The
handles for the Service Changed characteristic value and the GATT service are
stored during discovery.
---
src/shared/gatt-client.c | 103 ++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 98 insertions(+), 5 deletions(-)
diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index d0c12df..1a6fd86 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
@@ -41,6 +41,9 @@
#define UUID_BYTES (BT_GATT_UUID_SIZE * sizeof(uint8_t))
+#define GATT_SVC_UUID 0x1801
+#define SVC_CHNGD_UUID 0x2a05
+
struct chrc_data {
/* The public characteristic entry. */
bt_gatt_characteristic_t chrc_external;
@@ -100,6 +103,14 @@ struct bt_gatt_client {
unsigned int notify_id, ind_id;
bool in_notify;
bool need_notify_cleanup;
+
+ /* Handles of the GATT Service and the Service Changed characteristic
+ * value handle. These will have the value 0 if they are not present on
+ * the remote peripheral.
+ */
+ uint16_t gatt_svc_handle;
+ uint16_t svc_chngd_val_handle;
+ unsigned int svc_chngd_ind_id;
};
static bool service_list_add_service(struct service_list **head,
@@ -387,6 +398,10 @@ static void discover_chrcs_cb(bool success, uint8_t att_ecode,
goto done;
}
+ if (uuid_cmp(chrcs[i].chrc_external.uuid, SVC_CHNGD_UUID) == 0)
+ client->svc_chngd_val_handle =
+ chrcs[i].chrc_external.value_handle;
+
i++;
}
@@ -479,6 +494,9 @@ static void discover_primary_cb(bool success, uint8_t att_ecode,
success = false;
goto done;
}
+
+ if (uuid_cmp(uuid, GATT_SVC_UUID) == 0)
+ client->gatt_svc_handle = start;
}
/* Complete the process if the service list is empty */
@@ -544,20 +562,95 @@ static void exchange_mtu_cb(bool success, uint8_t att_ecode, void *user_data)
discovery_op_unref(op);
}
+static void service_changed_cb(uint16_t value_handle, const uint8_t *value,
+ uint16_t length, void *user_data)
+{
+ struct bt_gatt_client *client = user_data;
+ uint16_t start, end;
+
+ if (value_handle != client->svc_chngd_val_handle || length != 4)
+ return;
+
+ start = get_le16(value);
+ end = get_le16(value + 2);
+
+ util_debug(client->debug_callback, client->debug_data,
+ "Service Changed received - start: 0x%04x end: 0x%04x",
+ start, end);
+
+ /* TODO: Process changed services */
+}
+
+static void service_changed_register_cb(unsigned int id, uint16_t att_ecode,
+ void *user_data)
+{
+ bool success;
+ struct bt_gatt_client *client = user_data;
+
+ if (!id || att_ecode) {
+ util_debug(client->debug_callback, client->debug_data,
+ "Failed to register handler for \"Service Changed\"");
+ success = false;
+ goto done;
+ }
+
+ client->svc_chngd_ind_id = id;
+ client->ready = true;
+ success = true;
+ util_debug(client->debug_callback, client->debug_data,
+ "Registered handler for \"Service Changed\": %u", id);
+
+done:
+ if (client->ready_callback)
+ client->ready_callback(success, att_ecode, client->ready_data);
+}
+
static void init_complete(struct discovery_op *op, bool success,
uint8_t att_ecode)
{
struct bt_gatt_client *client = op->client;
+ bool registered;
client->in_init = false;
- if (success) {
- client->svc_head = op->result_head;
- client->svc_tail = op->result_tail;
+ if (!success)
+ goto fail;
+
+ client->svc_head = op->result_head;
+ client->svc_tail = op->result_tail;
+
+ if (!client->svc_chngd_val_handle) {
client->ready = true;
- } else
- service_list_clear(&op->result_head, &op->result_tail);
+ goto done;
+ }
+
+ /* Register an indication handler for the "Service Changed"
+ * characteristic and report ready only if the handler is registered
+ * successfully. Temporarily set "ready" to true so that we can register
+ * the handler using the existing framework.
+ */
+ client->ready = true;
+ registered = bt_gatt_client_register_notify(client,
+ client->svc_chngd_val_handle,
+ service_changed_register_cb,
+ service_changed_cb,
+ client, NULL);
+ client->ready = false;
+
+ if (registered)
+ return;
+ util_debug(client->debug_callback, client->debug_data,
+ "Failed to register handler for \"Service Changed\"");
+
+ client->svc_head = client->svc_tail = NULL;
+
+fail:
+ util_debug(client->debug_callback, client->debug_data,
+ "Failed to initialize gatt-client");
+ service_list_clear(&op->result_head, &op->result_tail);
+
+done:
if (client->ready_callback)
client->ready_callback(success, att_ecode, client->ready_data);
}
--
2.1.0.rc2.206.gedb03e5
btgatt-client now sets a service changed handler by calling
bt_gatt_client_set_service_changed.
---
tools/btgatt-client.c | 34 ++++++++++++++++++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/tools/btgatt-client.c b/tools/btgatt-client.c
index 41b85ad..d900e08 100644
--- a/tools/btgatt-client.c
+++ b/tools/btgatt-client.c
@@ -93,6 +93,8 @@ static void gatt_debug_cb(const char *str, void *user_data)
}
static void ready_cb(bool success, uint8_t att_ecode, void *user_data);
+static void service_changed_cb(uint16_t start_handle, uint16_t end_handle,
+ void *user_data);
static struct client *client_create(int fd, uint16_t mtu)
{
@@ -143,6 +145,8 @@ static struct client *client_create(int fd, uint16_t mtu)
}
bt_gatt_client_set_ready_handler(cli->gatt, ready_cb, cli, NULL);
+ bt_gatt_client_set_service_changed(cli->gatt, service_changed_cb, cli,
+ NULL);
/* bt_gatt_client already holds a reference */
bt_att_unref(att);
@@ -269,6 +273,36 @@ static void ready_cb(bool success, uint8_t att_ecode, void *user_data)
print_prompt();
}
+static void service_changed_cb(uint16_t start_handle, uint16_t end_handle,
+ void *user_data)
+{
+ struct client *cli = user_data;
+ struct bt_gatt_service_iter iter;
+ const bt_gatt_service_t *service;
+
+ if (!bt_gatt_service_iter_init(&iter, cli->gatt)) {
+ PRLOG("Failed to initialize service iterator\n");
+ return;
+ }
+
+ printf("\nService Changed handled - start: 0x%04x end: 0x%04x\n",
+ start_handle, end_handle);
+
+ if (!bt_gatt_service_iter_next_by_handle(&iter, start_handle, &service))
+ return;
+
+ print_service(service);
+
+ while (bt_gatt_service_iter_next(&iter, &service)) {
+ if (service->start_handle >= end_handle)
+ break;
+
+ print_service(service);
+ }
+
+ print_prompt();
+}
+
static void services_usage(void)
{
printf("Usage: services [options]\nOptions:\n"
--
2.1.0.rc2.206.gedb03e5
This patch makes the existing service discovery procedures more modular by
making the following changes:
- Don't accumulate the discovery results in struct bt_gatt_client's internal
service list. Instead, accumulate them inside struct discovery_op and assign
them to bt_gatt_client during completion.
- Add a completion callback to struct discovery_op which gets called at the
end of a discovery operation. This allows discovery ops triggered for
different reasons (e.g. init vs "Service Changed") to process discovery
results separately.
---
src/shared/gatt-client.c | 80 +++++++++++++++++++++++++++++-------------------
1 file changed, 49 insertions(+), 31 deletions(-)
diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index 997a280..d0c12df 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
@@ -102,7 +102,8 @@ struct bt_gatt_client {
bool need_notify_cleanup;
};
-static bool gatt_client_add_service(struct bt_gatt_client *client,
+static bool service_list_add_service(struct service_list **head,
+ struct service_list **tail,
uint16_t start, uint16_t end,
uint8_t uuid[BT_GATT_UUID_SIZE])
{
@@ -116,11 +117,11 @@ static bool gatt_client_add_service(struct bt_gatt_client *client,
list->service.end_handle = end;
memcpy(list->service.uuid, uuid, UUID_BYTES);
- if (!client->svc_head)
- client->svc_head = client->svc_tail = list;
+ if (!(*head))
+ *head = *tail = list;
else {
- client->svc_tail->next = list;
- client->svc_tail = list;
+ (*tail)->next = list;
+ *tail = list;
}
return true;
@@ -141,11 +142,15 @@ static void service_destroy_characteristics(struct service_list *service)
free(service->chrcs);
}
-static void gatt_client_clear_services(struct bt_gatt_client *client)
+static void service_list_clear(struct service_list **head,
+ struct service_list **tail)
{
struct service_list *l, *tmp;
- l = client->svc_head;
+ if (!(*head) || !(*tail))
+ return;
+
+ l = *head;
while (l) {
service_destroy_characteristics(l);
@@ -154,15 +159,22 @@ static void gatt_client_clear_services(struct bt_gatt_client *client)
free(tmp);
}
- client->svc_head = client->svc_tail = NULL;
+ *head = *tail = NULL;
+}
+
+static void gatt_client_clear_services(struct bt_gatt_client *client)
+{
+ service_list_clear(&client->svc_head, &client->svc_tail);
}
struct discovery_op {
struct bt_gatt_client *client;
- struct service_list *cur_service;
+ struct service_list *result_head, *result_tail, *cur_service;
struct chrc_data *cur_chrc;
int cur_chrc_index;
int ref_count;
+ void (*complete_func)(struct discovery_op *op, bool success,
+ uint8_t att_ecode);
};
static struct discovery_op *discovery_op_ref(struct discovery_op *op)
@@ -182,22 +194,6 @@ static void discovery_op_unref(void *data)
free(data);
}
-static void discovery_op_complete(struct discovery_op *op, bool success,
- uint8_t att_ecode)
-{
- struct bt_gatt_client *client = op->client;
-
- client->in_init = false;
-
- if (success)
- client->ready = true;
- else
- gatt_client_clear_services(client);
-
- if (client->ready_callback)
- client->ready_callback(success, att_ecode, client->ready_data);
-}
-
static void uuid_to_string(const uint8_t uuid[BT_GATT_UUID_SIZE],
char str[MAX_LEN_UUID_STR])
{
@@ -325,7 +321,7 @@ next:
success = false;
done:
- discovery_op_complete(op, success, att_ecode);
+ op->complete_func(op, success, att_ecode);
}
static void discover_chrcs_cb(bool success, uint8_t att_ecode,
@@ -438,7 +434,7 @@ next:
success = false;
done:
- discovery_op_complete(op, success, att_ecode);
+ op->complete_func(op, success, att_ecode);
}
static void discover_primary_cb(bool success, uint8_t att_ecode,
@@ -476,7 +472,8 @@ static void discover_primary_cb(bool success, uint8_t att_ecode,
start, end, uuid_str);
/* Store the service */
- if (!gatt_client_add_service(client, start, end, uuid)) {
+ if (!service_list_add_service(&op->result_head,
+ &op->result_tail, start, end, uuid)) {
util_debug(client->debug_callback, client->debug_data,
"Failed to store service");
success = false;
@@ -485,11 +482,11 @@ static void discover_primary_cb(bool success, uint8_t att_ecode,
}
/* Complete the process if the service list is empty */
- if (!client->svc_head)
+ if (!op->result_head)
goto done;
/* Sequentially discover the characteristics of all services */
- op->cur_service = client->svc_head;
+ op->cur_service = op->result_head;
if (bt_gatt_discover_characteristics(client->att,
op->cur_service->service.start_handle,
op->cur_service->service.end_handle,
@@ -504,7 +501,7 @@ static void discover_primary_cb(bool success, uint8_t att_ecode,
success = false;
done:
- discovery_op_complete(op, success, att_ecode);
+ op->complete_func(op, success, att_ecode);
}
static void exchange_mtu_cb(bool success, uint8_t att_ecode, void *user_data)
@@ -547,6 +544,24 @@ static void exchange_mtu_cb(bool success, uint8_t att_ecode, void *user_data)
discovery_op_unref(op);
}
+static void init_complete(struct discovery_op *op, bool success,
+ uint8_t att_ecode)
+{
+ struct bt_gatt_client *client = op->client;
+
+ client->in_init = false;
+
+ if (success) {
+ client->svc_head = op->result_head;
+ client->svc_tail = op->result_tail;
+ client->ready = true;
+ } else
+ service_list_clear(&op->result_head, &op->result_tail);
+
+ if (client->ready_callback)
+ client->ready_callback(success, att_ecode, client->ready_data);
+}
+
static bool gatt_client_init(struct bt_gatt_client *client, uint16_t mtu)
{
struct discovery_op *op;
@@ -559,6 +574,7 @@ static bool gatt_client_init(struct bt_gatt_client *client, uint16_t mtu)
return false;
op->client = client;
+ op->complete_func = init_complete;
/* Configure the MTU */
if (!bt_gatt_exchange_mtu(client->att, MAX(BT_ATT_DEFAULT_LE_MTU, mtu),
@@ -896,6 +912,8 @@ void bt_gatt_client_unref(struct bt_gatt_client *client)
queue_destroy(client->long_write_queue, long_write_op_unref);
queue_destroy(client->notify_list, notify_data_unref);
+ gatt_client_clear_services(client);
+
bt_att_unref(client->att);
free(client);
}
--
2.1.0.rc2.206.gedb03e5
This patch implements the first step of handling "Service Changed", which
involves removing all effected services from the internal list of services and
removing all registered notify handlers that have a handle on a characteristic
within the effected handle range.
---
src/shared/gatt-client.c | 245 +++++++++++++++++++++++++++++++++++++----------
1 file changed, 194 insertions(+), 51 deletions(-)
diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index 1a6fd86..cc0a894 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
@@ -111,8 +111,91 @@ struct bt_gatt_client {
uint16_t gatt_svc_handle;
uint16_t svc_chngd_val_handle;
unsigned int svc_chngd_ind_id;
+ struct queue *svc_chngd_queue; /* Queued service changed events */
+ bool in_svc_chngd;
};
+struct notify_data {
+ struct bt_gatt_client *client;
+ bool removed;
+ bool invalid;
+ unsigned int id;
+ int ref_count;
+ struct chrc_data *chrc;
+ bt_gatt_client_notify_id_callback_t callback;
+ bt_gatt_client_notify_callback_t notify;
+ void *user_data;
+ bt_gatt_client_destroy_func_t destroy;
+};
+
+static struct notify_data *notify_data_ref(struct notify_data *notify_data)
+{
+ __sync_fetch_and_add(¬ify_data->ref_count, 1);
+
+ return notify_data;
+}
+
+static void notify_data_unref(void *data)
+{
+ struct notify_data *notify_data = data;
+
+ if (__sync_sub_and_fetch(¬ify_data->ref_count, 1))
+ return;
+
+ if (notify_data->destroy)
+ notify_data->destroy(notify_data->user_data);
+
+ free(notify_data);
+}
+
+static bool match_notify_data_id(const void *a, const void *b)
+{
+ const struct notify_data *notify_data = a;
+ unsigned int id = PTR_TO_UINT(b);
+
+ return notify_data->id == id;
+}
+
+static bool match_notify_data_removed(const void *a, const void *b)
+{
+ const struct notify_data *notify_data = a;
+
+ return notify_data->removed;
+}
+
+static bool match_notify_data_invalid(const void *a, const void *b)
+{
+ const struct notify_data *notify_data = a;
+
+ return notify_data->invalid;
+}
+
+struct handle_range {
+ uint16_t start;
+ uint16_t end;
+};
+
+static bool match_notify_data_handle_range(const void *a, const void *b)
+{
+ const struct notify_data *notify_data = a;
+ bt_gatt_characteristic_t *chrc = ¬ify_data->chrc->chrc_external;
+ const struct handle_range *range = b;
+
+ return chrc->value_handle >= range->start &&
+ chrc->value_handle <= range->end;
+}
+
+static void mark_notify_data_invalid_if_in_range(void *data, void *user_data)
+{
+ struct notify_data *notify_data = data;
+ bt_gatt_characteristic_t *chrc = ¬ify_data->chrc->chrc_external;
+ struct handle_range *range = user_data;
+
+ if (chrc->value_handle >= range->start &&
+ chrc->value_handle <= range->end)
+ notify_data->invalid = true;
+}
+
static bool service_list_add_service(struct service_list **head,
struct service_list **tail,
uint16_t start, uint16_t end,
@@ -138,8 +221,6 @@ static bool service_list_add_service(struct service_list **head,
return true;
}
-static void notify_data_unref(void *data);
-
static void service_destroy_characteristics(struct service_list *service)
{
unsigned int i;
@@ -173,8 +254,66 @@ static void service_list_clear(struct service_list **head,
*head = *tail = NULL;
}
+static void service_list_clear_range(struct service_list **head,
+ struct service_list **tail,
+ uint16_t start, uint16_t end)
+{
+ struct service_list *cur, *prev, *tmp;
+
+ if (!(*head) || !(*tail))
+ return;
+
+ prev = NULL;
+ cur = *head;
+ while (cur) {
+ if (cur->service.end_handle < start ||
+ cur->service.start_handle > end) {
+ prev = cur;
+ cur = cur->next;
+ continue;
+ }
+
+ service_destroy_characteristics(cur);
+
+ if (!prev)
+ *head = cur->next;
+ else
+ prev->next = cur->next;
+
+ if (*tail == cur)
+ *tail = prev;
+
+ tmp = cur;
+ cur = cur->next;
+ free(tmp);
+ }
+}
+
+static void gatt_client_remove_all_notify_in_range(
+ struct bt_gatt_client *client,
+ uint16_t start_handle, uint16_t end_handle)
+{
+ struct handle_range range;
+
+ range.start = start_handle;
+ range.end = end_handle;
+
+ if (client->in_notify) {
+ queue_foreach(client->notify_list,
+ mark_notify_data_invalid_if_in_range,
+ &range);
+ client->need_notify_cleanup = true;
+ return;
+ }
+
+ queue_remove_all(client->notify_list, match_notify_data_handle_range,
+ &range, notify_data_unref);
+}
+
static void gatt_client_clear_services(struct bt_gatt_client *client)
{
+
+ gatt_client_remove_all_notify_in_range(client, 0x0001, 0xffff);
service_list_clear(&client->svc_head, &client->svc_tail);
}
@@ -562,10 +701,37 @@ static void exchange_mtu_cb(bool success, uint8_t att_ecode, void *user_data)
discovery_op_unref(op);
}
+struct service_changed_op {
+ struct bt_gatt_client *client;
+ uint16_t start_handle;
+ uint16_t end_handle;
+};
+
+static void process_service_changed(struct bt_gatt_client *client,
+ uint16_t start_handle,
+ uint16_t end_handle)
+{
+ /* Invalidate and remove all effected notify callbacks */
+ gatt_client_remove_all_notify_in_range(client, start_handle,
+ end_handle);
+
+ /* Remove all services that overlap the modified range since we'll
+ * rediscover them
+ */
+ service_list_clear_range(&client->svc_head, &client->svc_tail,
+ start_handle, end_handle);
+
+ /* TODO Rediscover all services within the modified service range. If
+ * the GATT service was effected, then register a new handler for
+ * "Service Changed"
+ */
+}
+
static void service_changed_cb(uint16_t value_handle, const uint8_t *value,
uint16_t length, void *user_data)
{
struct bt_gatt_client *client = user_data;
+ struct service_changed_op *op;
uint16_t start, end;
if (value_handle != client->svc_chngd_val_handle || length != 4)
@@ -578,7 +744,16 @@ static void service_changed_cb(uint16_t value_handle, const uint8_t *value,
"Service Changed received - start: 0x%04x end: 0x%04x",
start, end);
- /* TODO: Process changed services */
+ if (!client->in_svc_chngd) {
+ process_service_changed(client, start, end);
+ return;
+ }
+
+ op = new0(struct service_changed_op, 1);
+ if (!op)
+ return;
+
+ queue_push_tail(client->svc_chngd_queue, op);
}
static void service_changed_register_cb(unsigned int id, uint16_t att_ecode,
@@ -685,53 +860,6 @@ static bool gatt_client_init(struct bt_gatt_client *client, uint16_t mtu)
return true;
}
-struct notify_data {
- struct bt_gatt_client *client;
- bool removed;
- unsigned int id;
- int ref_count;
- struct chrc_data *chrc;
- bt_gatt_client_notify_id_callback_t callback;
- bt_gatt_client_notify_callback_t notify;
- void *user_data;
- bt_gatt_client_destroy_func_t destroy;
-};
-
-static struct notify_data *notify_data_ref(struct notify_data *notify_data)
-{
- __sync_fetch_and_add(¬ify_data->ref_count, 1);
-
- return notify_data;
-}
-
-static void notify_data_unref(void *data)
-{
- struct notify_data *notify_data = data;
-
- if (__sync_sub_and_fetch(¬ify_data->ref_count, 1))
- return;
-
- if (notify_data->destroy)
- notify_data->destroy(notify_data->user_data);
-
- free(notify_data);
-}
-
-static bool match_notify_data_id(const void *a, const void *b)
-{
- const struct notify_data *notify_data = a;
- unsigned int id = PTR_TO_UINT(b);
-
- return notify_data->id == id;
-}
-
-static bool match_notify_data_removed(const void *a, const void *b)
-{
- const struct notify_data *notify_data = a;
-
- return notify_data->removed;
-}
-
struct pdu_data {
const void *pdu;
uint16_t length;
@@ -913,6 +1041,8 @@ static void notify_cb(uint8_t opcode, const void *pdu, uint16_t length,
client->in_notify = false;
if (client->need_notify_cleanup) {
+ queue_remove_all(client->notify_list, match_notify_data_invalid,
+ NULL, notify_data_unref);
queue_remove_all(client->notify_list, match_notify_data_removed,
NULL, complete_unregister_notify);
client->need_notify_cleanup = false;
@@ -942,8 +1072,16 @@ struct bt_gatt_client *bt_gatt_client_new(struct bt_att *att, uint16_t mtu)
return NULL;
}
+ client->svc_chngd_queue = queue_new();
+ if (!client->svc_chngd_queue) {
+ queue_destroy(client->long_write_queue, NULL);
+ free(client);
+ return NULL;
+ }
+
client->notify_list = queue_new();
if (!client->notify_list) {
+ queue_destroy(client->svc_chngd_queue, NULL);
queue_destroy(client->long_write_queue, NULL);
free(client);
return NULL;
@@ -952,6 +1090,8 @@ struct bt_gatt_client *bt_gatt_client_new(struct bt_att *att, uint16_t mtu)
client->notify_id = bt_att_register(att, BT_ATT_OP_HANDLE_VAL_NOT,
notify_cb, client, NULL);
if (!client->notify_id) {
+ queue_destroy(client->notify_list, NULL);
+ queue_destroy(client->svc_chngd_queue, NULL);
queue_destroy(client->long_write_queue, NULL);
free(client);
return NULL;
@@ -961,6 +1101,8 @@ struct bt_gatt_client *bt_gatt_client_new(struct bt_att *att, uint16_t mtu)
notify_cb, client, NULL);
if (!client->ind_id) {
bt_att_unregister(att, client->notify_id);
+ queue_destroy(client->notify_list, NULL);
+ queue_destroy(client->svc_chngd_queue, NULL);
queue_destroy(client->long_write_queue, NULL);
free(client);
return NULL;
@@ -1002,6 +1144,7 @@ void bt_gatt_client_unref(struct bt_gatt_client *client)
bt_att_unregister(client->att, client->notify_id);
bt_att_unregister(client->att, client->ind_id);
+ queue_destroy(client->svc_chngd_queue, free);
queue_destroy(client->long_write_queue, long_write_op_unref);
queue_destroy(client->notify_list, notify_data_unref);
@@ -1057,7 +1200,7 @@ bool bt_gatt_service_iter_init(struct bt_gatt_service_iter *iter,
if (!iter || !client)
return false;
- if (client->in_init)
+ if (client->in_init || client->in_svc_chngd)
return false;
memset(iter, 0, sizeof(*iter));
--
2.1.0.rc2.206.gedb03e5
This patch adds the "start" and "end" parameters to
bt_gatt_discover_primary_services to allow restricting the service discovery
procedure to a handle range. Also added the
bt_gatt_discover_all_primary_services function which internally calls
bt_gatt_discover_primary_services within the handles 0x0001 and 0xffff.
---
src/shared/gatt-client.c | 2 +-
src/shared/gatt-helpers.c | 40 ++++++++++++++++++++++++++++++----------
src/shared/gatt-helpers.h | 5 +++++
3 files changed, 36 insertions(+), 11 deletions(-)
diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index ab8da6a..997a280 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
@@ -530,7 +530,7 @@ static void exchange_mtu_cb(bool success, uint8_t att_ecode, void *user_data)
"MTU exchange complete, with MTU: %u",
bt_att_get_mtu(client->att));
- if (bt_gatt_discover_primary_services(client->att, NULL,
+ if (bt_gatt_discover_all_primary_services(client->att, NULL,
discover_primary_cb,
discovery_op_ref(op),
discovery_op_unref))
diff --git a/src/shared/gatt-helpers.c b/src/shared/gatt-helpers.c
index 54876bc..55e6992 100644
--- a/src/shared/gatt-helpers.c
+++ b/src/shared/gatt-helpers.c
@@ -482,11 +482,11 @@ static void read_by_grp_type_cb(uint8_t opcode, const void *pdu,
}
last_end = get_le16(pdu + length - data_length + 2);
- if (last_end != 0xffff) {
+ if (last_end < op->end_handle) {
uint8_t pdu[6];
put_le16(last_end + 1, pdu);
- put_le16(0xffff, pdu + 2);
+ put_le16(op->end_handle, pdu + 2);
put_le16(GATT_PRIM_SVC_UUID, pdu + 4);
if (bt_att_send(op->att, BT_ATT_OP_READ_BY_GRP_TYPE_REQ,
@@ -501,6 +501,15 @@ static void read_by_grp_type_cb(uint8_t opcode, const void *pdu,
goto done;
}
+ /* Some devices incorrectly return 0xffff as the end group handle when
+ * the read-by-group-type request is performed within a smaller range.
+ * Manually set the end group handle that we report in the result to the
+ * end handle in the original request.
+ */
+ if (last_end == 0xffff && last_end != op->end_handle)
+ put_le16(op->end_handle,
+ cur_result->pdu + length - data_length + 1);
+
success:
/* End of procedure */
final_result = op->result_head;
@@ -555,11 +564,11 @@ static void find_by_type_val_cb(uint8_t opcode, const void *pdu,
}
last_end = get_le16(pdu + length - 6);
- if (last_end != 0xffff) {
+ if (last_end < op->end_handle) {
uint8_t pdu[6 + get_uuid_len(&op->uuid)];
put_le16(last_end + 1, pdu);
- put_le16(0xffff, pdu + 2);
+ put_le16(op->end_handle, pdu + 2);
put_le16(GATT_PRIM_SVC_UUID, pdu + 4);
put_uuid_le(&op->uuid, pdu + 6);
@@ -585,8 +594,18 @@ done:
op->callback(success, att_ecode, final_result, op->user_data);
}
-bool bt_gatt_discover_primary_services(struct bt_att *att,
- bt_uuid_t *uuid,
+bool bt_gatt_discover_all_primary_services(struct bt_att *att, bt_uuid_t *uuid,
+ bt_gatt_discovery_callback_t callback,
+ void *user_data,
+ bt_gatt_destroy_func_t destroy)
+{
+ return bt_gatt_discover_primary_services(att, uuid, 0x0001, 0xffff,
+ callback, user_data,
+ destroy);
+}
+
+bool bt_gatt_discover_primary_services(struct bt_att *att, bt_uuid_t *uuid,
+ uint16_t start, uint16_t end,
bt_gatt_discovery_callback_t callback,
void *user_data,
bt_gatt_destroy_func_t destroy)
@@ -602,6 +621,7 @@ bool bt_gatt_discover_primary_services(struct bt_att *att,
return false;
op->att = att;
+ op->end_handle = end;
op->callback = callback;
op->user_data = user_data;
op->destroy = destroy;
@@ -610,8 +630,8 @@ bool bt_gatt_discover_primary_services(struct bt_att *att,
if (!uuid) {
uint8_t pdu[6];
- put_le16(0x0001, pdu);
- put_le16(0xffff, pdu + 2);
+ put_le16(start, pdu);
+ put_le16(end, pdu + 2);
put_le16(GATT_PRIM_SVC_UUID, pdu + 4);
result = bt_att_send(att, BT_ATT_OP_READ_BY_GRP_TYPE_REQ,
@@ -630,8 +650,8 @@ bool bt_gatt_discover_primary_services(struct bt_att *att,
/* Discover by UUID */
op->uuid = *uuid;
- put_le16(0x0001, pdu);
- put_le16(0xffff, pdu + 2);
+ put_le16(start, pdu);
+ put_le16(end, pdu + 2);
put_le16(GATT_PRIM_SVC_UUID, pdu + 4);
put_uuid_le(&op->uuid, pdu + 6);
diff --git a/src/shared/gatt-helpers.h b/src/shared/gatt-helpers.h
index c4a6578..f6f4b62 100644
--- a/src/shared/gatt-helpers.h
+++ b/src/shared/gatt-helpers.h
@@ -63,7 +63,12 @@ bool bt_gatt_exchange_mtu(struct bt_att *att, uint16_t client_rx_mtu,
void *user_data,
bt_gatt_destroy_func_t destroy);
+bool bt_gatt_discover_all_primary_services(struct bt_att *att, bt_uuid_t *uuid,
+ bt_gatt_discovery_callback_t callback,
+ void *user_data,
+ bt_gatt_destroy_func_t destroy);
bool bt_gatt_discover_primary_services(struct bt_att *att, bt_uuid_t *uuid,
+ uint16_t start, uint16_t end,
bt_gatt_discovery_callback_t callback,
void *user_data,
bt_gatt_destroy_func_t destroy);
--
2.1.0.rc2.206.gedb03e5