diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index 0de4308..ab8da6a 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
/* List of registered notification/indication callbacks */
struct queue *notify_list;
int next_reg_id;
+ unsigned int notify_id, ind_id;
+ bool in_notify;
+ bool need_notify_cleanup;
};
static bool gatt_client_add_service(struct bt_gatt_client *client,
struct notify_data {
struct bt_gatt_client *client;
+ bool removed;
unsigned int id;
int ref_count;
struct chrc_data *chrc;
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;
+};
+
static void complete_notify_request(void *data)
{
struct notify_data *notify_data = data;
{
uint8_t pdu[4];
+ assert(notify_data->chrc->ccc_handle);
memset(pdu, 0, sizeof(pdu));
put_le16(notify_data->chrc->ccc_handle, pdu);
}
}
+static void complete_unregister_notify(void *data)
+{
+ struct notify_data *notify_data = data;
+
+ if (__sync_sub_and_fetch(¬ify_data->chrc->notify_count, 1)) {
+ notify_data_unref(notify_data);
+ return;
+ }
+
+ notify_data_write_ccc(notify_data, false, disable_ccc_callback);
+}
+
+static void notify_handler(void *data, void *user_data)
+{
+ struct notify_data *notify_data = data;
+ struct pdu_data *pdu_data = user_data;
+ uint16_t value_handle;
+ const uint8_t *value = NULL;
+
+ if (notify_data->removed)
+ return;
+
+ value_handle = get_le16(pdu_data->pdu);
+
+ if (notify_data->chrc->chrc_external.value_handle != value_handle)
+ return;
+
+ if (pdu_data->length > 2)
+ value = pdu_data->pdu + 2;
+
+ if (notify_data->notify)
+ notify_data->notify(value_handle, value, pdu_data->length - 2,
+ notify_data->user_data);
+}
+
+static void notify_cb(uint8_t opcode, const void *pdu, uint16_t length,
+ void *user_data)
+{
+ struct bt_gatt_client *client = user_data;
+ struct pdu_data pdu_data;
+
+ bt_gatt_client_ref(client);
+
+ client->in_notify = true;
+
+ memset(&pdu_data, 0, sizeof(pdu_data));
+ pdu_data.pdu = pdu;
+ pdu_data.length = length;
+
+ queue_foreach(client->notify_list, notify_handler, &pdu_data);
+
+ client->in_notify = false;
+
+ if (client->need_notify_cleanup) {
+ queue_remove_all(client->notify_list, match_notify_data_removed,
+ NULL, complete_unregister_notify);
+ client->need_notify_cleanup = false;
+ }
+
+ if (opcode == BT_ATT_OP_HANDLE_VAL_IND)
+ bt_att_send(client->att, BT_ATT_OP_HANDLE_VAL_CONF, NULL, 0,
+ NULL, NULL, NULL);
+
+ bt_gatt_client_unref(client);
+}
+
struct bt_gatt_client *bt_gatt_client_new(struct bt_att *att, uint16_t mtu)
{
struct bt_gatt_client *client;
return NULL;
}
+ client->notify_id = bt_att_register(att, BT_ATT_OP_HANDLE_VAL_NOT,
+ notify_cb, client, NULL);
+ if (!client->notify_id) {
+ queue_destroy(client->long_write_queue, NULL);
+ free(client);
+ return NULL;
+ }
+
+ client->ind_id = bt_att_register(att, BT_ATT_OP_HANDLE_VAL_IND,
+ notify_cb, client, NULL);
+ if (!client->ind_id) {
+ bt_att_unregister(att, client->notify_id);
+ queue_destroy(client->long_write_queue, NULL);
+ free(client);
+ return NULL;
+ }
+
client->att = bt_att_ref(att);
gatt_client_init(client, mtu);
if (client->debug_destroy)
client->debug_destroy(client->debug_data);
+ bt_att_unregister(client->att, client->notify_id);
+ bt_att_unregister(client->att, client->ind_id);
+
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);
}
notify_data = queue_find(client->notify_list, match_notify_data_id,
UINT_TO_PTR(id));
- if (!notify_data)
+ if (!notify_data || notify_data->removed)
return false;
assert(notify_data->chrc->notify_count > 0);
assert(!notify_data->chrc->ccc_write_id);
- /* TODO: Delay destruction/removal if we're in the middle of processing
- * a notification.
- */
- queue_remove(client->notify_list, notify_data);
-
- if (__sync_sub_and_fetch(¬ify_data->chrc->notify_count, 1)) {
- notify_data_unref(notify_data);
+ if (!client->in_notify) {
+ queue_remove(client->notify_list, notify_data);
+ complete_unregister_notify(notify_data);
return true;
}
- notify_data_write_ccc(notify_data, false, disable_ccc_callback);
-
+ notify_data->removed = true;
+ client->need_notify_cleanup = true;
return true;
}
diff --git a/src/shared/gatt-helpers.c b/src/shared/gatt-helpers.c
index ede6a67..54876bc 100644
--- a/src/shared/gatt-helpers.c
+++ b/src/shared/gatt-helpers.c
return true;
}
-
-struct notify_data {
- struct bt_att *att;
- bt_gatt_notify_callback_t callback;
- void *user_data;
- bt_gatt_destroy_func_t destroy;
-};
-
-static void notify_data_destroy(void *data)
-{
- struct notify_data *notd = data;
-
- if (notd->destroy)
- notd->destroy(notd->user_data);
-
- free(notd);
-}
-
-static void notify_cb(uint8_t opcode, const void *pdu, uint16_t length,
- void *user_data)
-{
- struct notify_data *data = user_data;
- uint16_t value_handle;
- const uint8_t *value = NULL;
-
- value_handle = get_le16(pdu);
-
- if (length > 2)
- value = pdu + 2;
-
- if (data->callback)
- data->callback(value_handle, value, length - 2, data->user_data);
-
- if (opcode == BT_ATT_OP_HANDLE_VAL_IND)
- bt_att_send(data->att, BT_ATT_OP_HANDLE_VAL_CONF, NULL, 0,
- NULL, NULL, NULL);
-}
-
-unsigned int bt_gatt_register(struct bt_att *att, bool indications,
- bt_gatt_notify_callback_t callback,
- void *user_data,
- bt_gatt_destroy_func_t destroy)
-{
- struct notify_data *data;
- uint8_t opcode;
- unsigned int id;
-
- if (!att)
- return 0;
-
- data = new0(struct notify_data, 1);
- if (!data)
- return 0;
-
- data->att = att;
- data->callback = callback;
- data->user_data = user_data;
- data->destroy = destroy;
-
- opcode = indications ? BT_ATT_OP_HANDLE_VAL_IND : BT_ATT_OP_HANDLE_VAL_NOT;
-
- id = bt_att_register(att, opcode, notify_cb, data, notify_data_destroy);
- if (!id)
- free(data);
-
- return id;
-}
diff --git a/src/shared/gatt-helpers.h b/src/shared/gatt-helpers.h
index 75ad4b0..c4a6578 100644
--- a/src/shared/gatt-helpers.h
+++ b/src/shared/gatt-helpers.h
struct bt_gatt_result *result,
void *user_data);
-typedef void (*bt_gatt_notify_callback_t)(uint16_t value_handle,
- const uint8_t *value, uint16_t length,
- void *user_data);
-
bool bt_gatt_exchange_mtu(struct bt_att *att, uint16_t client_rx_mtu,
bt_gatt_result_callback_t callback,
void *user_data,
bt_gatt_discovery_callback_t callback,
void *user_data,
bt_gatt_destroy_func_t destroy);
-
-unsigned int bt_gatt_register(struct bt_att *att, bool indications,
- bt_gatt_notify_callback_t callback,
- void *user_data,
- bt_gatt_destroy_func_t destroy);