diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index fd866b2..76fa91c 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
#include "src/shared/util.h"
#include "src/shared/queue.h"
+#include <assert.h>
+#include <limits.h>
+
#ifndef MAX
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#endif
#define UUID_BYTES (BT_GATT_UUID_SIZE * sizeof(uint8_t))
+struct chrc_data {
+ /* The public characteristic entry. */
+ bt_gatt_characteristic_t chrc_external;
+
+ /* The private entries. */
+ uint16_t ccc_handle;
+ int notify_count; /* Reference count of registered notify callbacks */
+
+ /* Internal non-const pointer to the descriptor array. We use this
+ * internally to modify/free the array, while we expose it externally
+ * using the const pointer "descs" field in bt_gatt_characteristic_t.
+ */
+ bt_gatt_descriptor_t *descs;
+
+ /* Pending calls to register_notify are queued here so that they can be
+ * processed after a write that modifies the CCC descriptor.
+ */
+ struct queue *reg_notify_queue;
+ unsigned int ccc_write_id;
+};
+
struct service_list {
bt_gatt_service_t service;
- bt_gatt_characteristic_t *chrcs;
+ struct chrc_data *chrcs;
size_t num_chrcs;
struct service_list *next;
};
*/
struct queue *long_write_queue;
bool in_long_write;
+
+ /* List of registered notification/indication callbacks */
+ struct queue *notify_list;
+ int next_reg_id;
};
static bool gatt_client_add_service(struct bt_gatt_client *client,
return true;
}
+static void notify_data_unref(void *data);
+
static void service_destroy_characteristics(struct service_list *service)
{
unsigned int i;
- for (i = 0; i < service->num_chrcs; i++)
- free((bt_gatt_descriptor_t *) service->chrcs[i].descs);
+ for (i = 0; i < service->num_chrcs; i++) {
+ free(service->chrcs[i].descs);
+ queue_destroy(service->chrcs[i].reg_notify_queue,
+ notify_data_unref);
+ }
free(service->chrcs);
}
struct discovery_op {
struct bt_gatt_client *client;
struct service_list *cur_service;
- bt_gatt_characteristic_t *cur_chrc;
+ struct chrc_data *cur_chrc;
int cur_chrc_index;
int ref_count;
};
struct bt_gatt_result *result,
void *user_data);
+static int uuid_cmp(const uint8_t uuid128[16], uint16_t uuid16)
+{
+ uint8_t rhs_uuid[16] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB
+ };
+
+ put_be16(uuid16, rhs_uuid + 2);
+
+ return memcmp(uuid128, rhs_uuid, sizeof(rhs_uuid));
+}
+
static void discover_descs_cb(bool success, uint8_t att_ecode,
struct bt_gatt_result *result,
void *user_data)
util_debug(client->debug_callback, client->debug_data,
"handle: 0x%04x, uuid: %s",
descs[i].handle, uuid_str);
+
+ if (uuid_cmp(descs[i].uuid, GATT_CLIENT_CHARAC_CFG_UUID) == 0)
+ op->cur_chrc->ccc_handle = descs[i].handle;
+
i++;
}
- op->cur_chrc->num_descs = desc_count;
+ op->cur_chrc->chrc_external.num_descs = desc_count;
op->cur_chrc->descs = descs;
+ op->cur_chrc->chrc_external.descs = descs;
for (i = op->cur_chrc_index + 1; i < op->cur_service->num_chrcs; i++) {
op->cur_chrc_index = i;
op->cur_chrc++;
- desc_start = op->cur_chrc->value_handle + 1;
- if (desc_start > op->cur_chrc->end_handle)
+ desc_start = op->cur_chrc->chrc_external.value_handle + 1;
+ if (desc_start > op->cur_chrc->chrc_external.end_handle)
continue;
- if (bt_gatt_discover_descriptors(client->att,
- desc_start,
- op->cur_chrc->end_handle,
- discover_descs_cb,
- discovery_op_ref(op),
- discovery_op_unref))
+ if (bt_gatt_discover_descriptors(client->att, desc_start,
+ op->cur_chrc->chrc_external.end_handle,
+ discover_descs_cb, discovery_op_ref(op),
+ discovery_op_unref))
return;
util_debug(client->debug_callback, client->debug_data,
unsigned int chrc_count;
unsigned int i;
uint16_t desc_start;
- bt_gatt_characteristic_t *chrcs;
+ struct chrc_data *chrcs;
if (!success) {
if (att_ecode == BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND) {
if (chrc_count == 0)
goto next;
- chrcs = new0(bt_gatt_characteristic_t, chrc_count);
+ chrcs = new0(struct chrc_data, chrc_count);
if (!chrcs) {
success = false;
goto done;
}
i = 0;
- while (bt_gatt_iter_next_characteristic(&iter, &chrcs[i].start_handle,
- &chrcs[i].end_handle,
- &chrcs[i].value_handle,
- &chrcs[i].properties,
- chrcs[i].uuid)) {
- uuid_to_string(chrcs[i].uuid, uuid_str);
+ while (bt_gatt_iter_next_characteristic(&iter,
+ &chrcs[i].chrc_external.start_handle,
+ &chrcs[i].chrc_external.end_handle,
+ &chrcs[i].chrc_external.value_handle,
+ &chrcs[i].chrc_external.properties,
+ chrcs[i].chrc_external.uuid)) {
+ uuid_to_string(chrcs[i].chrc_external.uuid, uuid_str);
util_debug(client->debug_callback, client->debug_data,
"start: 0x%04x, end: 0x%04x, value: 0x%04x, "
"props: 0x%02x, uuid: %s",
- chrcs[i].start_handle, chrcs[i].end_handle,
- chrcs[i].value_handle, chrcs[i].properties,
+ chrcs[i].chrc_external.start_handle,
+ chrcs[i].chrc_external.end_handle,
+ chrcs[i].chrc_external.value_handle,
+ chrcs[i].chrc_external.properties,
uuid_str);
+
+ chrcs[i].reg_notify_queue = queue_new();
+ if (!chrcs[i].reg_notify_queue) {
+ success = false;
+ goto done;
+ }
+
i++;
}
for (i = 0; i < chrc_count; i++) {
op->cur_chrc_index = i;
op->cur_chrc = chrcs + i;
- desc_start = chrcs[i].value_handle + 1;
- if (desc_start > chrcs[i].end_handle)
+ desc_start = chrcs[i].chrc_external.value_handle + 1;
+ if (desc_start > chrcs[i].chrc_external.end_handle)
continue;
- if (bt_gatt_discover_descriptors(client->att,
- desc_start, chrcs[i].end_handle,
- discover_descs_cb,
- discovery_op_ref(op),
- discovery_op_unref))
+ if (bt_gatt_discover_descriptors(client->att, desc_start,
+ chrcs[i].chrc_external.end_handle,
+ discover_descs_cb, discovery_op_ref(op),
+ discovery_op_unref))
return;
util_debug(client->debug_callback, client->debug_data,
return true;
}
+struct notify_data {
+ struct bt_gatt_client *client;
+ 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 void complete_notify_request(void *data)
+{
+ struct notify_data *notify_data = data;
+
+ /* Increment the per-characteristic ref count of notify handlers */
+ __sync_fetch_and_add(¬ify_data->chrc->notify_count, 1);
+
+ /* Add the handler to the bt_gatt_client's general list */
+ queue_push_tail(notify_data->client->notify_list,
+ notify_data_ref(notify_data));
+
+ /* Assign an ID to the handler and notify the caller that it was
+ * successfully registered.
+ */
+ if (notify_data->client->next_reg_id < 1)
+ notify_data->client->next_reg_id = 1;
+
+ notify_data->id = notify_data->client->next_reg_id++;
+ notify_data->callback(notify_data->id, 0, notify_data->user_data);
+}
+
+static bool notify_data_write_ccc(struct notify_data *notify_data, bool enable,
+ bt_att_response_func_t callback)
+{
+ uint8_t pdu[4];
+
+ memset(pdu, 0, sizeof(pdu));
+ put_le16(notify_data->chrc->ccc_handle, pdu);
+
+ if (enable) {
+ /* Try to enable notifications and/or indications based on
+ * whatever the characteristic supports.
+ */
+ if (notify_data->chrc->chrc_external.properties &
+ BT_GATT_CHRC_PROP_NOTIFY)
+ pdu[2] = 0x01;
+
+ if (notify_data->chrc->chrc_external.properties &
+ BT_GATT_CHRC_PROP_INDICATE)
+ pdu[2] |= 0x02;
+
+ if (!pdu[2])
+ return false;
+ }
+
+ notify_data->chrc->ccc_write_id = bt_att_send(notify_data->client->att,
+ BT_ATT_OP_WRITE_REQ,
+ pdu, sizeof(pdu),
+ callback,
+ notify_data, notify_data_unref);
+
+ return !!notify_data->chrc->ccc_write_id;
+}
+
+static uint8_t process_error(const void *pdu, uint16_t length)
+{
+ if (!pdu || length != 4)
+ return 0;
+
+ return ((uint8_t *) pdu)[3];
+}
+
+static void enable_ccc_callback(uint8_t opcode, const void *pdu,
+ uint16_t length, void *user_data)
+{
+ struct notify_data *notify_data = user_data;
+ uint16_t att_ecode;
+
+ assert(!notify_data->chrc->notify_count);
+ assert(notify_data->chrc->ccc_write_id);
+
+ notify_data->chrc->ccc_write_id = 0;
+
+ if (opcode == BT_ATT_OP_ERROR_RSP) {
+ att_ecode = process_error(pdu, length);
+
+ /* Failed to enable. Complete the current request and move on to
+ * the next one in the queue. If there was an error sending the
+ * write request, then just move on to the next queued entry.
+ */
+ notify_data->callback(0, att_ecode, notify_data->user_data);
+
+ while ((notify_data = queue_pop_head(
+ notify_data->chrc->reg_notify_queue))) {
+
+ if (notify_data_write_ccc(notify_data, true,
+ enable_ccc_callback))
+ return;
+ }
+
+ return;
+ }
+
+ /* Success! Report success for all remaining requests. */
+ complete_notify_request(notify_data);
+ queue_remove_all(notify_data->chrc->reg_notify_queue, NULL, NULL,
+ complete_notify_request);
+}
+
+static void disable_ccc_callback(uint8_t opcode, const void *pdu,
+ uint16_t length, void *user_data)
+{
+ /* TODO */
+}
+
struct bt_gatt_client *bt_gatt_client_new(struct bt_att *att, uint16_t mtu)
{
struct bt_gatt_client *client;
return NULL;
}
+ client->notify_list = queue_new();
+ if (!client->notify_list) {
+ queue_destroy(client->long_write_queue, NULL);
+ free(client);
+ return NULL;
+ }
+
client->att = bt_att_ref(att);
gatt_client_init(client, mtu);
client->debug_destroy(client->debug_data);
queue_destroy(client->long_write_queue, long_write_op_unref);
+ queue_destroy(client->notify_list, notify_data_unref);
bt_att_unref(client->att);
free(client);
}
if (iter->pos >= service->num_chrcs)
return false;
- *chrc = service->chrcs + iter->pos++;
+ *chrc = &service->chrcs[iter->pos++].chrc_external;
return true;
}
free(op);
}
-static uint8_t process_error(const void *pdu, uint16_t length)
-{
- if (!pdu || length != 4)
- return 0;
-
- return ((uint8_t *) pdu)[3];
-}
-
static void read_cb(uint8_t opcode, const void *pdu, uint16_t length,
void *user_data)
{
return true;
}
+
+bool bt_gatt_client_register_notify(struct bt_gatt_client *client,
+ uint16_t chrc_value_handle,
+ 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)
+{
+ struct notify_data *notify_data;
+ struct service_list *svc_data = NULL;
+ struct chrc_data *chrc = NULL;
+ struct bt_gatt_service_iter iter;
+ const bt_gatt_service_t *service;
+ size_t i;
+
+ if (!client || !chrc_value_handle || !callback)
+ return false;
+
+ if (!bt_gatt_client_is_ready(client))
+ return false;
+
+ /* Check that chrc_value_handle belongs to a known characteristic */
+ if (!bt_gatt_service_iter_init(&iter, client))
+ return false;
+
+ while (bt_gatt_service_iter_next(&iter, &service)) {
+ if (chrc_value_handle >= service->start_handle &&
+ chrc_value_handle <= service->end_handle) {
+ svc_data = (struct service_list *) service;
+ break;
+ }
+ }
+
+ if (!svc_data)
+ return false;
+
+ for (i = 0; i < svc_data->num_chrcs; i++) {
+ if (svc_data->chrcs[i].chrc_external.value_handle ==
+ chrc_value_handle) {
+ chrc = svc_data->chrcs + i;
+ break;
+ }
+ }
+
+ /* Check that the characteristic supports notifications/indications */
+ if (!chrc || !chrc->ccc_handle || chrc->notify_count == INT_MAX)
+ return false;
+
+ notify_data = new0(struct notify_data, 1);
+ if (!notify_data)
+ return false;
+
+ notify_data->client = client;
+ notify_data->ref_count = 1;
+ notify_data->chrc = chrc;
+ notify_data->callback = callback;
+ notify_data->notify = notify;
+ notify_data->user_data = user_data;
+ notify_data->destroy = destroy;
+
+ /* If a write to the CCC descriptor is in progress, then queue this
+ * request.
+ */
+ if (chrc->ccc_write_id) {
+ queue_push_tail(chrc->reg_notify_queue, notify_data);
+ return true;
+ }
+
+ /* If the ref count is not zero, then notifications are already enabled.
+ */
+ if (chrc->notify_count > 0) {
+ complete_notify_request(notify_data);
+ return true;
+ }
+
+ /* Write to the CCC descriptor */
+ if (!notify_data_write_ccc(notify_data, true, enable_ccc_callback)) {
+ free(notify_data);
+ return false;
+ }
+
+ return true;
+}
+
+bool bt_gatt_client_unregister_notify(struct bt_gatt_client *client,
+ unsigned int id)
+{
+ /* TODO */
+ return false;
+}
diff --git a/src/shared/gatt-client.h b/src/shared/gatt-client.h
index 417d964..7612a6e 100644
--- a/src/shared/gatt-client.h
+++ b/src/shared/gatt-client.h
#define BT_GATT_UUID_SIZE 16
+#define BT_GATT_CHRC_PROP_BROADCAST 0x01
+#define BT_GATT_CHRC_PROP_READ 0x02
+#define BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP 0x04
+#define BT_GATT_CHRC_PROP_WRITE 0x08
+#define BT_GATT_CHRC_PROP_NOTIFY 0x10
+#define BT_GATT_CHRC_PROP_INDICATE 0x20
+#define BT_GATT_CHRC_PROP_AUTH 0x40
+#define BT_GATT_CHRC_PROP_EXT_PROP 0x80
+
+/* Client Characteristic Configuration bit field */
struct bt_gatt_client;
struct bt_gatt_client *bt_gatt_client_new(struct bt_att *att, uint16_t mtu);
typedef void (*bt_gatt_client_write_long_callback_t)(bool success,
bool reliable_error, uint8_t att_ecode,
void *user_data);
+typedef void (*bt_gatt_client_notify_callback_t)(uint16_t value_handle,
+ const uint8_t *value, uint16_t length,
+ void *user_data);
+typedef void (*bt_gatt_client_notify_id_callback_t)(unsigned int id,
+ uint16_t att_ecode,
+ 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_write_long_callback_t callback,
void *user_data,
bt_gatt_client_destroy_func_t destroy);
+
+bool bt_gatt_client_register_notify(struct bt_gatt_client *client,
+ uint16_t chrc_value_handle,
+ 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);
+bool bt_gatt_client_unregister_notify(struct bt_gatt_client *client,
+ unsigned int id);