diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c
index feb30f6..ee3f984 100644
--- a/src/shared/gatt-client.c
+++ b/src/shared/gatt-client.c
uint16_t end_handle;
};
-static void service_changed_reregister_cb(uint16_t att_ecode, void *user_data)
+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 complete_notify_request(void *data)
{
- struct bt_gatt_client *client = user_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);
+
+ notify_data->att_id = 0;
+ notify_data->callback(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];
+ unsigned int att_id;
+
+ assert(notify_data->chrc->ccc_handle);
+ 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->properties & BT_GATT_CHRC_PROP_NOTIFY)
+ pdu[2] = 0x01;
+
+ if (notify_data->chrc->properties & BT_GATT_CHRC_PROP_INDICATE)
+ pdu[2] |= 0x02;
+
+ if (!pdu[2])
+ return false;
+ }
+
+ att_id = bt_att_send(notify_data->client->att, BT_ATT_OP_WRITE_REQ,
+ pdu, sizeof(pdu), callback,
+ notify_data_ref(notify_data),
+ notify_data_unref);
+ notify_data->chrc->ccc_write_id = notify_data->att_id = att_id;
+
+ return !!att_id;
+}
+
+static uint8_t process_error(const void *pdu, uint16_t length)
+{
+ const struct bt_att_pdu_error_rsp *error_pdu;
+
+ if (!pdu || length != sizeof(struct bt_att_pdu_error_rsp))
+ return 0;
+
+ error_pdu = pdu;
+
+ return error_pdu->ecode;
+}
+
+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.
+ */
+ queue_remove(notify_data->client->notify_list, notify_data);
+ notify_data->callback(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;
+ }
- if (!att_ecode) {
- util_debug(client->debug_callback, client->debug_data,
- "Re-registered handler for \"Service Changed\" after "
- "change in GATT service");
- client->svc_chngd_registered = true;
return;
}
- util_debug(client->debug_callback, client->debug_data,
- "Failed to register handler for \"Service Changed\"");
- client->svc_chngd_ind_id = 0;
+ /* Success! Report success for all remaining requests. */
+ bt_gatt_client_ref(notify_data->client);
+
+ complete_notify_request(notify_data);
+ queue_remove_all(notify_data->chrc->reg_notify_queue, NULL, NULL,
+ complete_notify_request);
+
+ bt_gatt_client_unref(notify_data->client);
}
-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 bool match_notify_chrc_value_handle(const void *a, const void *b)
+{
+ const struct notify_chrc *chrc = a;
+ uint16_t value_handle = PTR_TO_UINT(b);
+
+ return chrc->value_handle == value_handle;
+}
+
+static unsigned int register_notify(struct bt_gatt_client *client,
+ uint16_t handle,
+ bt_gatt_client_register_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 notify_chrc *chrc = NULL;
+
+ /* Check if a characteristic ref count has been started already */
+ chrc = queue_find(client->notify_chrcs, match_notify_chrc_value_handle,
+ UINT_TO_PTR(handle));
+
+ if (!chrc) {
+ /*
+ * Create an entry if the characteristic is known and has a CCC
+ * descriptor.
+ */
+ chrc = notify_chrc_create(client, handle);
+ if (!chrc)
+ return 0;
+ }
+
+ /* Fail if we've hit the maximum allowed notify sessions */
+ if (chrc->notify_count == INT_MAX)
+ return 0;
+
+ notify_data = new0(struct notify_data, 1);
+ if (!notify_data)
+ return 0;
+
+ 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;
+
+ /* Add the handler to the bt_gatt_client's general list */
+ queue_push_tail(client->notify_list, notify_data);
+
+ /* Assign an ID to the handler. */
+ if (client->next_reg_id < 1)
+ client->next_reg_id = 1;
+
+ notify_data->id = client->next_reg_id++;
+
+ /*
+ * 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 notify_data->id;
+ }
+
+ /*
+ * If the ref count is not zero, then notifications are already enabled.
+ */
+ if (chrc->notify_count > 0 || !chrc->ccc_handle) {
+ complete_notify_request(notify_data);
+ return notify_data->id;
+ }
+
+ /* Write to the CCC descriptor */
+ if (!notify_data_write_ccc(notify_data, true, enable_ccc_callback)) {
+ queue_remove(client->notify_list, notify_data);
+ free(notify_data);
+ return 0;
+ }
+
+ return notify_data->id;
+}
static void get_first_attribute(struct gatt_db_attribute *attrib,
void *user_data)
*stored = attrib;
}
+static void service_changed_register_cb(uint16_t att_ecode, void *user_data)
+{
+ bool success;
+ struct bt_gatt_client *client = user_data;
+
+ if (att_ecode) {
+ util_debug(client->debug_callback, client->debug_data,
+ "Failed to register handler for \"Service Changed\"");
+ success = false;
+ client->svc_chngd_ind_id = 0;
+ goto done;
+ }
+
+ client->svc_chngd_registered = true;
+ success = true;
+ util_debug(client->debug_callback, client->debug_data,
+ "Registered handler for \"Service Changed\": %u",
+ client->svc_chngd_ind_id);
+
+done:
+ if (!client->ready) {
+ client->ready = success;
+ notify_client_ready(client, success, att_ecode);
+ }
+}
+
+static bool register_service_changed(struct bt_gatt_client *client)
+{
+ bt_uuid_t uuid;
+ struct gatt_db_attribute *attr = NULL;
+
+ bt_uuid16_create(&uuid, SVC_CHNGD_UUID);
+
+ if (client->svc_chngd_ind_id)
+ return true;
+
+ gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid,
+ get_first_attribute, &attr);
+ if (!attr)
+ return true;
+
+ /*
+ * Register an indication handler for the "Service Changed"
+ * characteristic and report ready only if the handler is registered
+ * successfully.
+ */
+ client->svc_chngd_ind_id = register_notify(client,
+ gatt_db_attribute_get_handle(attr),
+ service_changed_register_cb,
+ service_changed_cb,
+ client, NULL);
+
+ return client->svc_chngd_ind_id ? true : false;
+}
+
static void service_changed_complete(struct discovery_op *op, bool success,
uint8_t att_ecode)
{
struct service_changed_op *next_sc_op;
uint16_t start_handle = op->start;
uint16_t end_handle = op->end;
- struct gatt_db_attribute *attr = NULL;
- bt_uuid_t uuid;
client->in_svc_chngd = false;
return;
}
- bt_uuid16_create(&uuid, SVC_CHNGD_UUID);
-
- gatt_db_find_by_type(client->db, start_handle, end_handle, &uuid,
- get_first_attribute, &attr);
- if (!attr)
- return;
-
- /* The GATT service was modified. Re-register the handler for
- * indications from the "Service Changed" characteristic.
- */
- client->svc_chngd_registered = false;
- client->svc_chngd_ind_id = bt_gatt_client_register_notify(client,
- gatt_db_attribute_get_handle(attr),
- service_changed_reregister_cb,
- service_changed_cb,
- client, NULL);
- if (client->svc_chngd_ind_id)
+ if (register_service_changed(client))
return;
util_debug(client->debug_callback, client->debug_data,
queue_push_tail(client->svc_chngd_queue, op);
}
-static void service_changed_register_cb(uint16_t att_ecode, void *user_data)
-{
- bool success;
- struct bt_gatt_client *client = user_data;
-
- if (att_ecode) {
- util_debug(client->debug_callback, client->debug_data,
- "Failed to register handler for \"Service Changed\"");
- success = false;
- client->svc_chngd_ind_id = 0;
- goto done;
- }
-
- client->svc_chngd_registered = true;
- client->ready = true;
- success = true;
- util_debug(client->debug_callback, client->debug_data,
- "Registered handler for \"Service Changed\": %u",
- client->svc_chngd_ind_id);
-
-done:
- notify_client_ready(client, success, att_ecode);
-}
-
static void init_complete(struct discovery_op *op, bool success,
uint8_t att_ecode)
{
struct bt_gatt_client *client = op->client;
- struct gatt_db_attribute *attr = NULL;
- bt_uuid_t uuid;
client->in_init = false;
if (!success)
goto fail;
- bt_uuid16_create(&uuid, SVC_CHNGD_UUID);
-
- gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid,
- get_first_attribute, &attr);
- if (!attr) {
+ if (register_service_changed(client)) {
client->ready = true;
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;
- client->svc_chngd_ind_id = bt_gatt_client_register_notify(client,
- gatt_db_attribute_get_handle(attr),
- service_changed_register_cb,
- service_changed_cb,
- client, NULL);
-
- if (!client->svc_chngd_registered)
- client->ready = false;
-
- if (client->svc_chngd_ind_id)
- return;
-
util_debug(client->debug_callback, client->debug_data,
"Failed to register handler for \"Service Changed\"");
success = false;
uint16_t length;
};
-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);
-
- notify_data->att_id = 0;
- notify_data->callback(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];
- unsigned int att_id;
-
- assert(notify_data->chrc->ccc_handle);
- 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->properties & BT_GATT_CHRC_PROP_NOTIFY)
- pdu[2] = 0x01;
-
- if (notify_data->chrc->properties & BT_GATT_CHRC_PROP_INDICATE)
- pdu[2] |= 0x02;
-
- if (!pdu[2])
- return false;
- }
-
- att_id = bt_att_send(notify_data->client->att, BT_ATT_OP_WRITE_REQ,
- pdu, sizeof(pdu), callback,
- notify_data_ref(notify_data),
- notify_data_unref);
- notify_data->chrc->ccc_write_id = notify_data->att_id = att_id;
-
- return !!att_id;
-}
-
-static uint8_t process_error(const void *pdu, uint16_t length)
-{
- const struct bt_att_pdu_error_rsp *error_pdu;
-
- if (!pdu || length != sizeof(struct bt_att_pdu_error_rsp))
- return 0;
-
- error_pdu = pdu;
-
- return error_pdu->ecode;
-}
-
-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.
- */
- queue_remove(notify_data->client->notify_list, notify_data);
- notify_data->callback(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. */
- bt_gatt_client_ref(notify_data->client);
-
- complete_notify_request(notify_data);
- queue_remove_all(notify_data->chrc->reg_notify_queue, NULL, NULL,
- complete_notify_request);
-
- bt_gatt_client_unref(notify_data->client);
-}
-
static void disable_ccc_callback(uint8_t opcode, const void *pdu,
uint16_t length, void *user_data)
{
return id;
}
-static bool match_notify_chrc_value_handle(const void *a, const void *b)
-{
- const struct notify_chrc *chrc = a;
- uint16_t value_handle = PTR_TO_UINT(b);
-
- return chrc->value_handle == value_handle;
-}
-
unsigned int bt_gatt_client_register_notify(struct bt_gatt_client *client,
uint16_t chrc_value_handle,
bt_gatt_client_register_callback_t callback,
void *user_data,
bt_gatt_client_destroy_func_t destroy)
{
- struct notify_data *notify_data;
- struct notify_chrc *chrc = NULL;
-
if (!client || !client->db || !chrc_value_handle || !callback)
return 0;
if (!bt_gatt_client_is_ready(client) || client->in_svc_chngd)
return 0;
- /* Check if a characteristic ref count has been started already */
- chrc = queue_find(client->notify_chrcs, match_notify_chrc_value_handle,
- UINT_TO_PTR(chrc_value_handle));
-
- if (!chrc) {
- /*
- * Create an entry if the characteristic is known and has a CCC
- * descriptor.
- */
- chrc = notify_chrc_create(client, chrc_value_handle);
- if (!chrc)
- return 0;
- }
-
- /* Fail if we've hit the maximum allowed notify sessions */
- if (chrc->notify_count == INT_MAX)
- return 0;
-
- notify_data = new0(struct notify_data, 1);
- if (!notify_data)
- return 0;
-
- 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;
-
- /* Add the handler to the bt_gatt_client's general list */
- queue_push_tail(client->notify_list, notify_data);
-
- /* Assign an ID to the handler. */
- if (client->next_reg_id < 1)
- client->next_reg_id = 1;
-
- notify_data->id = client->next_reg_id++;
-
- /*
- * 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 notify_data->id;
- }
-
- /*
- * If the ref count is not zero, then notifications are already enabled.
- */
- if (chrc->notify_count > 0 || !chrc->ccc_handle) {
- complete_notify_request(notify_data);
- return notify_data->id;
- }
-
- /* Write to the CCC descriptor */
- if (!notify_data_write_ccc(notify_data, true, enable_ccc_callback)) {
- queue_remove(client->notify_list, notify_data);
- free(notify_data);
- return 0;
- }
-
- return notify_data->id;
+ return register_notify(client, chrc_value_handle, callback, notify,
+ user_data, destroy);
}
bool bt_gatt_client_unregister_notify(struct bt_gatt_client *client,