blob: 03d722fde6dfb639b642f134fb5f8bd326aab0ff [file] [log] [blame]
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2014 Google Inc.
*
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "src/shared/att.h"
#include "src/shared/gatt-client.h"
#include "lib/uuid.h"
#include "src/shared/gatt-helpers.h"
#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
#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
#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;
/* 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;
struct chrc_data *chrcs;
size_t num_chrcs;
struct service_list *next;
};
struct bt_gatt_client {
struct bt_att *att;
int ref_count;
bt_gatt_client_callback_t ready_callback;
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;
struct service_list *svc_head, *svc_tail;
bool in_init;
bool ready;
/* Queue of long write requests. An error during "prepare write"
* requests can result in a cancel through "execute write". To prevent
* cancelation of prepared writes to the wrong attribute and multiple
* requests to the same attribute that may result in a corrupted final
* value, we avoid interleaving prepared writes.
*/
struct queue *long_write_queue;
bool in_long_write;
/* List of registered disconnect/notification/indication callbacks */
struct queue *notify_list;
int next_reg_id;
unsigned int disc_id, 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;
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(&notify_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(&notify_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 = &notify_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 = &notify_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,
uint8_t uuid[BT_GATT_UUID_SIZE])
{
struct service_list *list;
list = new0(struct service_list, 1);
if (!list)
return false;
list->service.start_handle = start;
list->service.end_handle = end;
memcpy(list->service.uuid, uuid, UUID_BYTES);
if (!(*head))
*head = *tail = list;
else {
(*tail)->next = list;
*tail = list;
}
return true;
}
static void service_destroy_characteristics(struct service_list *service)
{
unsigned int i;
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);
}
static void service_list_clear(struct service_list **head,
struct service_list **tail)
{
struct service_list *l, *tmp;
if (!(*head) || !(*tail))
return;
l = *head;
while (l) {
service_destroy_characteristics(l);
tmp = l;
l = tmp->next;
free(tmp);
}
*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 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)
{
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);
}
struct discovery_op {
struct bt_gatt_client *client;
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)
{
__sync_fetch_and_add(&op->ref_count, 1);
return op;
}
static void discovery_op_unref(void *data)
{
struct discovery_op *op = data;
if (__sync_sub_and_fetch(&op->ref_count, 1))
return;
free(data);
}
static void uuid_to_string(const uint8_t uuid[BT_GATT_UUID_SIZE],
char str[MAX_LEN_UUID_STR])
{
bt_uuid_t tmp;
tmp.type = BT_UUID128;
memcpy(tmp.value.u128.data, uuid, UUID_BYTES);
bt_uuid_to_string(&tmp, str, MAX_LEN_UUID_STR * sizeof(char));
}
static void discover_chrcs_cb(bool success, uint8_t att_ecode,
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)
{
struct discovery_op *op = user_data;
struct bt_gatt_client *client = op->client;
struct bt_gatt_iter iter;
char uuid_str[MAX_LEN_UUID_STR];
unsigned int desc_count;
uint16_t desc_start;
unsigned int i;
bt_gatt_descriptor_t *descs;
if (!success) {
if (att_ecode == BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND) {
success = true;
goto next;
}
goto done;
}
if (!result || !bt_gatt_iter_init(&iter, result)) {
success = false;
goto done;
}
desc_count = bt_gatt_result_descriptor_count(result);
if (desc_count == 0) {
success = false;
goto done;
}
util_debug(client->debug_callback, client->debug_data,
"Descriptors found: %u", desc_count);
descs = new0(bt_gatt_descriptor_t, desc_count);
if (!descs) {
success = false;
goto done;
}
i = 0;
while (bt_gatt_iter_next_descriptor(&iter, &descs[i].handle,
descs[i].uuid)) {
uuid_to_string(descs[i].uuid, uuid_str);
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->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->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->chrc_external.end_handle,
discover_descs_cb, discovery_op_ref(op),
discovery_op_unref))
return;
util_debug(client->debug_callback, client->debug_data,
"Failed to start descriptor discovery");
discovery_op_unref(op);
success = false;
goto done;
}
next:
if (!op->cur_service->next)
goto done;
/* Move on to the next service */
op->cur_service = op->cur_service->next;
if (bt_gatt_discover_characteristics(client->att,
op->cur_service->service.start_handle,
op->cur_service->service.end_handle,
discover_chrcs_cb,
discovery_op_ref(op),
discovery_op_unref))
return;
util_debug(client->debug_callback, client->debug_data,
"Failed to start characteristic discovery");
discovery_op_unref(op);
success = false;
done:
op->complete_func(op, success, att_ecode);
}
static void discover_chrcs_cb(bool success, uint8_t att_ecode,
struct bt_gatt_result *result,
void *user_data)
{
struct discovery_op *op = user_data;
struct bt_gatt_client *client = op->client;
struct bt_gatt_iter iter;
char uuid_str[MAX_LEN_UUID_STR];
unsigned int chrc_count;
unsigned int i;
uint16_t desc_start;
struct chrc_data *chrcs;
if (!success) {
if (att_ecode == BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND) {
success = true;
goto next;
}
goto done;
}
if (!result || !bt_gatt_iter_init(&iter, result)) {
success = false;
goto done;
}
chrc_count = bt_gatt_result_characteristic_count(result);
util_debug(client->debug_callback, client->debug_data,
"Characteristics found: %u", chrc_count);
if (chrc_count == 0)
goto next;
chrcs = new0(struct chrc_data, chrc_count);
if (!chrcs) {
success = false;
goto done;
}
i = 0;
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].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;
}
if (uuid_cmp(chrcs[i].chrc_external.uuid, SVC_CHNGD_UUID) == 0)
client->svc_chngd_val_handle =
chrcs[i].chrc_external.value_handle;
i++;
}
op->cur_service->chrcs = chrcs;
op->cur_service->num_chrcs = chrc_count;
for (i = 0; i < chrc_count; i++) {
op->cur_chrc_index = i;
op->cur_chrc = chrcs + i;
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].chrc_external.end_handle,
discover_descs_cb, discovery_op_ref(op),
discovery_op_unref))
return;
util_debug(client->debug_callback, client->debug_data,
"Failed to start descriptor discovery");
discovery_op_unref(op);
success = false;
goto done;
}
next:
if (!op->cur_service->next)
goto done;
/* Move on to the next service */
op->cur_service = op->cur_service->next;
if (bt_gatt_discover_characteristics(client->att,
op->cur_service->service.start_handle,
op->cur_service->service.end_handle,
discover_chrcs_cb,
discovery_op_ref(op),
discovery_op_unref))
return;
util_debug(client->debug_callback, client->debug_data,
"Failed to start characteristic discovery");
discovery_op_unref(op);
success = false;
done:
op->complete_func(op, success, att_ecode);
}
static void discover_primary_cb(bool success, uint8_t att_ecode,
struct bt_gatt_result *result,
void *user_data)
{
struct discovery_op *op = user_data;
struct bt_gatt_client *client = op->client;
struct bt_gatt_iter iter;
uint16_t start, end;
uint8_t uuid[BT_GATT_UUID_SIZE];
char uuid_str[MAX_LEN_UUID_STR];
if (!success) {
util_debug(client->debug_callback, client->debug_data,
"Primary service discovery failed."
" ATT ECODE: 0x%02x", att_ecode);
goto done;
}
if (!result || !bt_gatt_iter_init(&iter, result)) {
success = false;
goto done;
}
util_debug(client->debug_callback, client->debug_data,
"Primary services found: %u",
bt_gatt_result_service_count(result));
while (bt_gatt_iter_next_service(&iter, &start, &end, uuid)) {
/* Log debug message. */
uuid_to_string(uuid, uuid_str);
util_debug(client->debug_callback, client->debug_data,
"start: 0x%04x, end: 0x%04x, uuid: %s",
start, end, uuid_str);
/* Store the service */
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;
goto done;
}
if (uuid_cmp(uuid, GATT_SVC_UUID) == 0)
client->gatt_svc_handle = start;
}
/* Complete the process if the service list is empty */
if (!op->result_head)
goto done;
/* Sequentially discover the characteristics of all services */
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,
discover_chrcs_cb,
discovery_op_ref(op),
discovery_op_unref))
return;
util_debug(client->debug_callback, client->debug_data,
"Failed to start characteristic discovery");
discovery_op_unref(op);
success = false;
done:
op->complete_func(op, success, att_ecode);
}
static void exchange_mtu_cb(bool success, uint8_t att_ecode, void *user_data)
{
struct discovery_op *op = user_data;
struct bt_gatt_client *client = op->client;
if (!success) {
util_debug(client->debug_callback, client->debug_data,
"MTU Exchange failed. ATT ECODE: 0x%02x",
att_ecode);
client->in_init = false;
if (client->ready_callback)
client->ready_callback(success, att_ecode,
client->ready_data);
return;
}
util_debug(client->debug_callback, client->debug_data,
"MTU exchange complete, with MTU: %u",
bt_att_get_mtu(client->att));
if (bt_gatt_discover_all_primary_services(client->att, NULL,
discover_primary_cb,
discovery_op_ref(op),
discovery_op_unref))
return;
util_debug(client->debug_callback, client->debug_data,
"Failed to initiate primary service discovery");
client->in_init = false;
if (client->ready_callback)
client->ready_callback(success, att_ecode, client->ready_data);
discovery_op_unref(op);
}
struct service_changed_op {
struct bt_gatt_client *client;
uint16_t start_handle;
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;
uint16_t start_handle, end_handle;
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;
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) {
process_service_changed(client, next_sc_op->start_handle,
next_sc_op->end_handle);
free(next_sc_op);
return;
}
/* Check if the GATT service is not present or has remained unchanged */
if (!client->svc_chngd_val_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,
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,
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);
/* 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);
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;
}
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;
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,
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)
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);
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,
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)
goto fail;
client->svc_head = op->result_head;
client->svc_tail = op->result_tail;
if (!client->svc_chngd_val_handle) {
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;
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);
}
static bool gatt_client_init(struct bt_gatt_client *client, uint16_t mtu)
{
struct discovery_op *op;
if (client->in_init || client->ready)
return false;
op = new0(struct discovery_op, 1);
if (!op)
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),
exchange_mtu_cb,
discovery_op_ref(op),
discovery_op_unref)) {
free(op);
return false;
}
client->in_init = true;
return true;
}
struct pdu_data {
const void *pdu;
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(&notify_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];
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->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)
{
struct notify_data *notify_data = user_data;
struct notify_data *next_data;
assert(!notify_data->chrc->notify_count);
assert(notify_data->chrc->ccc_write_id);
notify_data->chrc->ccc_write_id = 0;
/* This is a best effort procedure, so ignore errors and process any
* queued requests.
*/
while (1) {
next_data = queue_pop_head(notify_data->chrc->reg_notify_queue);
if (!next_data || notify_data_write_ccc(notify_data, true,
enable_ccc_callback))
return;
}
}
static void complete_unregister_notify(void *data)
{
struct notify_data *notify_data = data;
if (__sync_sub_and_fetch(&notify_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_invalid,
NULL, notify_data_unref);
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);
}
static void long_write_op_unref(void *data);
static void bt_gatt_client_free(struct bt_gatt_client *client)
{
if (client->ready_destroy)
client->ready_destroy(client->ready_data);
if (client->debug_destroy)
client->debug_destroy(client->debug_data);
if (client->att) {
bt_att_unregister_disconnect(client->att, client->disc_id);
bt_att_unregister(client->att, client->notify_id);
bt_att_unregister(client->att, client->ind_id);
bt_att_unref(client->att);
}
gatt_client_clear_services(client);
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);
free(client);
}
static void att_disconnect_cb(void *user_data)
{
struct bt_gatt_client *client = user_data;
client->disc_id = 0;
bt_att_unref(client->att);
client->att = NULL;
client->in_init = false;
client->ready = false;
if (client->ready_callback)
client->ready_callback(false, 0, client->ready_data);
}
struct bt_gatt_client *bt_gatt_client_new(struct bt_att *att, uint16_t mtu)
{
struct bt_gatt_client *client;
if (!att)
return NULL;
client = new0(struct bt_gatt_client, 1);
if (!client)
return NULL;
client->disc_id = bt_att_register_disconnect(att, att_disconnect_cb,
client, NULL);
if (!client->disc_id)
goto fail;
client->long_write_queue = queue_new();
if (!client->long_write_queue)
goto fail;
client->svc_chngd_queue = queue_new();
if (!client->svc_chngd_queue)
goto fail;
client->notify_list = queue_new();
if (!client->notify_list)
goto fail;
client->notify_id = bt_att_register(att, BT_ATT_OP_HANDLE_VAL_NOT,
notify_cb, client, NULL);
if (!client->notify_id)
goto fail;
client->ind_id = bt_att_register(att, BT_ATT_OP_HANDLE_VAL_IND,
notify_cb, client, NULL);
if (!client->ind_id)
goto fail;
client->att = bt_att_ref(att);
if (!gatt_client_init(client, mtu))
goto fail;
return bt_gatt_client_ref(client);
fail:
bt_gatt_client_free(client);
return NULL;
}
struct bt_gatt_client *bt_gatt_client_ref(struct bt_gatt_client *client)
{
if (!client)
return NULL;
__sync_fetch_and_add(&client->ref_count, 1);
return client;
}
void bt_gatt_client_unref(struct bt_gatt_client *client)
{
if (!client)
return;
if (__sync_sub_and_fetch(&client->ref_count, 1))
return;
bt_gatt_client_free(client);
}
bool bt_gatt_client_is_ready(struct bt_gatt_client *client)
{
return (client && client->ready);
}
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)
{
if (!client)
return false;
if (client->ready_destroy)
client->ready_destroy(client->ready_data);
client->ready_callback = callback;
client->ready_destroy = destroy;
client->ready_data = user_data;
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,
bt_gatt_client_destroy_func_t destroy) {
if (!client)
return false;
if (client->debug_destroy)
client->debug_destroy(client->debug_data);
client->debug_callback = callback;
client->debug_destroy = destroy;
client->debug_data = user_data;
return true;
}
bool bt_gatt_service_iter_init(struct bt_gatt_service_iter *iter,
struct bt_gatt_client *client)
{
if (!iter || !client)
return false;
if (client->in_init || client->in_svc_chngd)
return false;
memset(iter, 0, sizeof(*iter));
iter->client = client;
iter->ptr = NULL;
return true;
}
bool bt_gatt_service_iter_next(struct bt_gatt_service_iter *iter,
const bt_gatt_service_t **service)
{
struct service_list *l;
if (!iter || !service)
return false;
l = iter->ptr;
if (!l)
l = iter->client->svc_head;
else
l = l->next;
if (!l)
return false;
*service = &l->service;
iter->ptr = l;
return true;
}
bool bt_gatt_service_iter_next_by_handle(struct bt_gatt_service_iter *iter,
uint16_t start_handle,
const bt_gatt_service_t **service)
{
while (bt_gatt_service_iter_next(iter, service)) {
if ((*service)->start_handle == start_handle)
return true;
}
return false;
}
bool bt_gatt_service_iter_next_by_uuid(struct bt_gatt_service_iter *iter,
const uint8_t uuid[BT_GATT_UUID_SIZE],
const bt_gatt_service_t **service)
{
while (bt_gatt_service_iter_next(iter, service)) {
if (memcmp((*service)->uuid, uuid, UUID_BYTES) == 0)
return true;
}
return false;
}
bool bt_gatt_characteristic_iter_init(struct bt_gatt_characteristic_iter *iter,
const bt_gatt_service_t *service)
{
if (!iter || !service)
return false;
memset(iter, 0, sizeof(*iter));
iter->service = (struct service_list *) service;
return true;
}
bool bt_gatt_characteristic_iter_next(struct bt_gatt_characteristic_iter *iter,
const bt_gatt_characteristic_t **chrc)
{
struct service_list *service;
if (!iter || !chrc)
return false;
service = iter->service;
if (iter->pos >= service->num_chrcs)
return false;
*chrc = &service->chrcs[iter->pos++].chrc_external;
return true;
}
struct read_op {
bt_gatt_client_read_callback_t callback;
void *user_data;
bt_gatt_client_destroy_func_t destroy;
};
static void destroy_read_op(void *data)
{
struct read_op *op = data;
if (op->destroy)
op->destroy(op->user_data);
free(op);
}
static void read_cb(uint8_t opcode, const void *pdu, uint16_t length,
void *user_data)
{
struct read_op *op = user_data;
bool success;
uint8_t att_ecode = 0;
const uint8_t *value = NULL;
uint16_t value_len = 0;
if (opcode == BT_ATT_OP_ERROR_RSP) {
success = false;
att_ecode = process_error(pdu, length);
goto done;
}
if (opcode != BT_ATT_OP_READ_RSP || (!pdu && length)) {
success = false;
goto done;
}
success = true;
value_len = length;
if (value_len)
value = pdu;
done:
if (op->callback)
op->callback(success, att_ecode, value, length, op->user_data);
}
bool bt_gatt_client_read_value(struct bt_gatt_client *client,
uint16_t value_handle,
bt_gatt_client_read_callback_t callback,
void *user_data,
bt_gatt_client_destroy_func_t destroy)
{
struct read_op *op;
uint8_t pdu[2];
if (!client)
return false;
op = new0(struct read_op, 1);
if (!op)
return false;
op->callback = callback;
op->user_data = user_data;
op->destroy = destroy;
put_le16(value_handle, pdu);
if (!bt_att_send(client->att, BT_ATT_OP_READ_REQ, pdu, sizeof(pdu),
read_cb, op,
destroy_read_op)) {
free(op);
return false;
}
return true;
}
struct read_long_op {
struct bt_gatt_client *client;
int ref_count;
uint16_t value_handle;
size_t orig_offset;
size_t offset;
struct queue *blobs;
bt_gatt_client_read_callback_t callback;
void *user_data;
bt_gatt_client_destroy_func_t destroy;
};
struct blob {
uint8_t *data;
uint16_t offset;
uint16_t length;
};
static struct blob *create_blob(const uint8_t *data, uint16_t len,
uint16_t offset)
{
struct blob *blob;
blob = new0(struct blob, 1);
if (!blob)
return NULL;
blob->data = malloc(len);
if (!blob->data) {
free(blob);
return NULL;
}
memcpy(blob->data, data, len);
blob->length = len;
blob->offset = offset;
return blob;
}
static void destroy_blob(void *data)
{
struct blob *blob = data;
free(blob->data);
free(blob);
}
static struct read_long_op *read_long_op_ref(struct read_long_op *op)
{
__sync_fetch_and_add(&op->ref_count, 1);
return op;
}
static void read_long_op_unref(void *data)
{
struct read_long_op *op = data;
if (__sync_sub_and_fetch(&op->ref_count, 1))
return;
if (op->destroy)
op->destroy(op->user_data);
queue_destroy(op->blobs, destroy_blob);
free(op);
}
static void append_blob(void *data, void *user_data)
{
struct blob *blob = data;
uint8_t *value = user_data;
memcpy(value + blob->offset, blob->data, blob->length);
}
static void complete_read_long_op(struct read_long_op *op, bool success,
uint8_t att_ecode)
{
uint8_t *value = NULL;
uint16_t length = 0;
if (!success)
goto done;
length = op->offset - op->orig_offset;
if (!length)
goto done;
value = malloc(length);
if (!value) {
success = false;
goto done;
}
queue_foreach(op->blobs, append_blob, value - op->orig_offset);
done:
if (op->callback)
op->callback(success, att_ecode, value, length, op->user_data);
free(value);
}
static void read_long_cb(uint8_t opcode, const void *pdu,
uint16_t length, void *user_data)
{
struct read_long_op *op = user_data;
struct blob *blob;
bool success;
uint8_t att_ecode = 0;
if (opcode == BT_ATT_OP_ERROR_RSP) {
success = false;
att_ecode = process_error(pdu, length);
goto done;
}
if (opcode != BT_ATT_OP_READ_BLOB_RSP || (!pdu && length)) {
success = false;
goto done;
}
if (!length)
goto success;
blob = create_blob(pdu, length, op->offset);
if (!blob) {
success = false;
goto done;
}
queue_push_tail(op->blobs, blob);
op->offset += length;
if (op->offset > UINT16_MAX)
goto success;
if (length >= bt_att_get_mtu(op->client->att) - 1) {
uint8_t pdu[4];
put_le16(op->value_handle, pdu);
put_le16(op->offset, pdu + 2);
if (bt_att_send(op->client->att, BT_ATT_OP_READ_BLOB_REQ,
pdu, sizeof(pdu),
read_long_cb,
read_long_op_ref(op),
read_long_op_unref))
return;
read_long_op_unref(op);
success = false;
goto done;
}
success:
success = true;
done:
complete_read_long_op(op, success, att_ecode);
}
bool bt_gatt_client_read_long_value(struct bt_gatt_client *client,
uint16_t value_handle, uint16_t offset,
bt_gatt_client_read_callback_t callback,
void *user_data,
bt_gatt_client_destroy_func_t destroy)
{
struct read_long_op *op;
uint8_t pdu[4];
if (!client)
return false;
op = new0(struct read_long_op, 1);
if (!op)
return false;
op->blobs = queue_new();
if (!op->blobs) {
free(op);
return false;
}
op->client = client;
op->value_handle = value_handle;
op->orig_offset = offset;
op->offset = offset;
op->callback = callback;
op->user_data = user_data;
op->destroy = destroy;
put_le16(value_handle, pdu);
put_le16(offset, pdu + 2);
if (!bt_att_send(client->att, BT_ATT_OP_READ_BLOB_REQ, pdu, sizeof(pdu),
read_long_cb,
read_long_op_ref(op),
read_long_op_unref)) {
queue_destroy(op->blobs, free);
free(op);
return false;
}
return true;
}
bool bt_gatt_client_write_without_response(struct bt_gatt_client *client,
uint16_t value_handle,
bool signed_write,
uint8_t *value, uint16_t length) {
uint8_t pdu[2 + length];
if (!client)
return 0;
/* TODO: Support this once bt_att_send supports signed writes. */
if (signed_write)
return 0;
put_le16(value_handle, pdu);
memcpy(pdu + 2, value, length);
return bt_att_send(client->att, BT_ATT_OP_WRITE_CMD, pdu, sizeof(pdu),
NULL, NULL, NULL);
}
struct write_op {
bt_gatt_result_callback_t callback;
void *user_data;
bt_gatt_destroy_func_t destroy;
};
static void destroy_write_op(void *data)
{
struct write_op *op = data;
if (op->destroy)
op->destroy(op->user_data);
free(op);
}
static void write_cb(uint8_t opcode, const void *pdu, uint16_t length,
void *user_data)
{
struct write_op *op = user_data;
bool success = true;
uint8_t att_ecode = 0;
if (opcode == BT_ATT_OP_ERROR_RSP) {
success = false;
att_ecode = process_error(pdu, length);
goto done;
}
if (opcode != BT_ATT_OP_WRITE_RSP || pdu || length)
success = false;
done:
if (op->callback)
op->callback(success, att_ecode, op->user_data);
}
bool bt_gatt_client_write_value(struct bt_gatt_client *client,
uint16_t value_handle,
uint8_t *value, uint16_t length,
bt_gatt_client_callback_t callback,
void *user_data,
bt_gatt_client_destroy_func_t destroy)
{
struct write_op *op;
uint8_t pdu[2 + length];
if (!client)
return false;
op = new0(struct write_op, 1);
if (!op)
return false;
op->callback = callback;
op->user_data = user_data;
op->destroy = destroy;
put_le16(value_handle, pdu);
memcpy(pdu + 2, value, length);
if (!bt_att_send(client->att, BT_ATT_OP_WRITE_REQ, pdu, sizeof(pdu),
write_cb, op,
destroy_write_op)) {
free(op);
return false;
}
return true;
}
struct long_write_op {
struct bt_gatt_client *client;
int ref_count;
bool reliable;
bool success;
uint8_t att_ecode;
bool reliable_error;
uint16_t value_handle;
uint8_t *value;
uint16_t length;
uint16_t offset;
uint16_t index;
uint16_t cur_length;
bt_gatt_client_write_long_callback_t callback;
void *user_data;
bt_gatt_client_destroy_func_t destroy;
};
static struct long_write_op *long_write_op_ref(struct long_write_op *op)
{
__sync_fetch_and_add(&op->ref_count, 1);
return op;
}
static void long_write_op_unref(void *data)
{
struct long_write_op *op = data;
if (__sync_sub_and_fetch(&op->ref_count, 1))
return;
if (op->destroy)
op->destroy(op->user_data);
free(op->value);
free(op);
}
static void prepare_write_cb(uint8_t opcode, const void *pdu, uint16_t length,
void *user_data);
static void complete_write_long_op(struct long_write_op *op, bool success,
uint8_t att_ecode, bool reliable_error);
static void handle_next_prep_write(struct long_write_op *op)
{
bool success = true;
uint8_t *pdu;
pdu = malloc(op->cur_length + 4);
if (!pdu) {
success = false;
goto done;
}
put_le16(op->value_handle, pdu);
put_le16(op->offset + op->index, pdu + 2);
memcpy(pdu + 4, op->value + op->index, op->cur_length);
if (!bt_att_send(op->client->att, BT_ATT_OP_PREP_WRITE_REQ,
pdu, op->cur_length + 4,
prepare_write_cb,
long_write_op_ref(op),
long_write_op_unref)) {
long_write_op_unref(op);
success = false;
}
free(pdu);
/* If so far successful, then the operation should continue.
* Otherwise, there was an error and the procedure should be
* completed.
*/
if (success)
return;
done:
complete_write_long_op(op, success, 0, false);
}
static void start_next_long_write(struct bt_gatt_client *client)
{
struct long_write_op *op;
if (queue_isempty(client->long_write_queue)) {
client->in_long_write = false;
return;
}
op = queue_pop_head(client->long_write_queue);
if (!op)
return;
handle_next_prep_write(op);
/* send_next_prep_write adds an extra ref. Unref here to clean up if
* necessary, since we also added a ref before pushing to the queue.
*/
long_write_op_unref(op);
}
static void execute_write_cb(uint8_t opcode, const void *pdu, uint16_t length,
void *user_data)
{
struct long_write_op *op = user_data;
bool success = op->success;
uint8_t att_ecode = op->att_ecode;
if (opcode == BT_ATT_OP_ERROR_RSP) {
success = false;
att_ecode = process_error(pdu, length);
} else if (opcode != BT_ATT_OP_EXEC_WRITE_RSP || pdu || length)
success = false;
if (op->callback)
op->callback(success, op->reliable_error, att_ecode,
op->user_data);
start_next_long_write(op->client);
}
static void complete_write_long_op(struct long_write_op *op, bool success,
uint8_t att_ecode, bool reliable_error)
{
uint8_t pdu;
op->success = success;
op->att_ecode = att_ecode;
op->reliable_error = reliable_error;
if (success)
pdu = 0x01; /* Write */
else
pdu = 0x00; /* Cancel */
if (bt_att_send(op->client->att, BT_ATT_OP_EXEC_WRITE_REQ,
&pdu, sizeof(pdu),
execute_write_cb,
long_write_op_ref(op),
long_write_op_unref))
return;
long_write_op_unref(op);
success = false;
if (op->callback)
op->callback(success, reliable_error, att_ecode, op->user_data);
start_next_long_write(op->client);
}
static void prepare_write_cb(uint8_t opcode, const void *pdu, uint16_t length,
void *user_data)
{
struct long_write_op *op = user_data;
bool success = true;
bool reliable_error = false;
uint8_t att_ecode = 0;
uint16_t next_index;
if (opcode == BT_ATT_OP_ERROR_RSP) {
success = false;
att_ecode = process_error(pdu, length);
goto done;
}
if (opcode != BT_ATT_OP_PREP_WRITE_RSP) {
success = false;
goto done;
}
if (op->reliable) {
if (!pdu || length != (op->cur_length + 4)) {
success = false;
reliable_error = true;
goto done;
}
if (get_le16(pdu) != op->value_handle ||
get_le16(pdu + 2) != (op->offset + op->index)) {
success = false;
reliable_error = true;
goto done;
}
if (memcmp(pdu + 4, op->value + op->index, op->cur_length)) {
success = false;
reliable_error = true;
goto done;
}
}
next_index = op->index + op->cur_length;
if (next_index == op->length) {
/* All bytes written */
goto done;
}
/* If the last written length was greater than or equal to what can fit
* inside a PDU, then there is more data to send.
*/
if (op->cur_length >= bt_att_get_mtu(op->client->att) - 5) {
op->index = next_index;
op->cur_length = MIN(op->length - op->index,
bt_att_get_mtu(op->client->att) - 5);
handle_next_prep_write(op);
return;
}
done:
complete_write_long_op(op, success, att_ecode, reliable_error);
}
bool bt_gatt_client_write_long_value(struct bt_gatt_client *client,
bool reliable,
uint16_t value_handle, uint16_t offset,
uint8_t *value, uint16_t length,
bt_gatt_client_write_long_callback_t callback,
void *user_data,
bt_gatt_client_destroy_func_t destroy)
{
struct long_write_op *op;
uint8_t *pdu;
bool status;
if (!client)
return false;
if ((size_t)(length + offset) > UINT16_MAX)
return false;
/* Don't allow writing a 0-length value using this procedure. The
* upper-layer should use bt_gatt_write_value for that instead.
*/
if (!length || !value)
return false;
op = new0(struct long_write_op, 1);
if (!op)
return false;
op->value = malloc(length);
if (!op->value) {
free(op);
return false;
}
memcpy(op->value, value, length);
op->client = client;
op->reliable = reliable;
op->value_handle = value_handle;
op->length = length;
op->offset = offset;
op->cur_length = MIN(length, bt_att_get_mtu(client->att) - 5);
op->callback = callback;
op->user_data = user_data;
op->destroy = destroy;
if (client->in_long_write) {
queue_push_tail(client->long_write_queue,
long_write_op_ref(op));
return true;
}
pdu = malloc(op->cur_length + 4);
if (!pdu) {
free(op->value);
free(op);
return false;
}
put_le16(value_handle, pdu);
put_le16(offset, pdu + 2);
memcpy(pdu + 4, op->value, op->cur_length);
status = bt_att_send(client->att, BT_ATT_OP_PREP_WRITE_REQ,
pdu, op->cur_length + 4,
prepare_write_cb,
long_write_op_ref(op),
long_write_op_unref);
free(pdu);
if (!status) {
free(op->value);
free(op);
return false;
}
client->in_long_write = true;
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) || client->in_svc_chngd)
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)
{
struct notify_data *notify_data;
if (!client || !id)
return false;
notify_data = queue_find(client->notify_list, match_notify_data_id,
UINT_TO_PTR(id));
if (!notify_data || notify_data->removed)
return false;
assert(notify_data->chrc->notify_count > 0);
assert(!notify_data->chrc->ccc_write_id);
if (!client->in_notify) {
queue_remove(client->notify_list, notify_data);
complete_unregister_notify(notify_data);
return true;
}
notify_data->removed = true;
client->need_notify_cleanup = true;
return true;
}