blob: 99c8ea231bff879c6ffe2d21983c24d02da01370 [file] [log] [blame]
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2015 Google Inc.
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdint.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include "lib/bluetooth.h"
#include "lib/sdp.h"
#include "lib/sdp_lib.h"
#include "lib/uuid.h"
#include "btio/btio.h"
#include "gdbus/gdbus.h"
#include "src/shared/util.h"
#include "src/shared/queue.h"
#include "src/shared/io.h"
#include "src/shared/att.h"
#include "src/shared/gatt-db.h"
#include "src/shared/gatt-server.h"
#include "log.h"
#include "error.h"
#include "adapter.h"
#include "device.h"
#include "gatt-database.h"
#include "dbus-common.h"
#include "profile.h"
#include "service.h"
#ifndef ATT_CID
#define ATT_CID 4
#endif
#ifndef ATT_PSM
#define ATT_PSM 31
#endif
#define GATT_MANAGER_IFACE "org.bluez.GattManager1"
#define GATT_PROFILE_IFACE "org.bluez.GattProfile1"
#define GATT_SERVICE_IFACE "org.bluez.GattService1"
#define GATT_CHRC_IFACE "org.bluez.GattCharacteristic1"
#define GATT_DESC_IFACE "org.bluez.GattDescriptor1"
#define UUID_GAP 0x1800
#define UUID_GATT 0x1801
#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
struct gatt_record {
struct btd_gatt_database *database;
uint32_t handle;
struct gatt_db_attribute *attr;
};
struct btd_gatt_database {
struct btd_adapter *adapter;
struct gatt_db *db;
unsigned int db_id;
GIOChannel *le_io;
GIOChannel *l2cap_io;
struct queue *records;
struct queue *device_states;
struct queue *ccc_callbacks;
struct gatt_db_attribute *svc_chngd;
struct gatt_db_attribute *svc_chngd_ccc;
struct queue *apps;
struct queue *profiles;
};
struct gatt_app {
struct btd_gatt_database *database;
char *owner;
char *path;
DBusMessage *reg;
GDBusClient *client;
bool failed;
struct queue *profiles;
struct queue *services;
struct queue *proxies;
};
struct external_service {
struct gatt_app *app;
char *path; /* Path to GattService1 */
GDBusProxy *proxy;
struct gatt_db_attribute *attrib;
uint16_t attr_cnt;
struct queue *chrcs;
struct queue *descs;
struct queue *includes;
};
struct external_profile {
struct gatt_app *app;
GDBusProxy *proxy;
struct queue *profiles; /* btd_profile list */
};
struct external_chrc {
struct external_service *service;
char *path;
GDBusProxy *proxy;
uint8_t props;
uint8_t ext_props;
uint32_t perm;
uint16_t mtu;
struct io *write_io;
struct io *notify_io;
struct gatt_db_attribute *attrib;
struct gatt_db_attribute *ccc;
struct queue *pending_reads;
struct queue *pending_writes;
unsigned int ntfy_cnt;
bool prep_authorized;
bool req_prep_authorization;
};
struct external_desc {
struct external_service *service;
char *chrc_path;
GDBusProxy *proxy;
uint32_t perm;
struct gatt_db_attribute *attrib;
bool handled;
struct queue *pending_reads;
struct queue *pending_writes;
bool prep_authorized;
bool req_prep_authorization;
};
struct pending_op {
struct btd_device *device;
unsigned int id;
uint16_t offset;
uint8_t link_type;
struct gatt_db_attribute *attrib;
struct queue *owner_queue;
struct iovec data;
bool is_characteristic;
bool prep_authorize;
};
struct notify {
struct btd_gatt_database *database;
uint16_t handle, ccc_handle;
uint8_t *value;
uint16_t len;
bt_gatt_server_conf_func_t conf;
void *user_data;
};
struct device_state {
struct btd_gatt_database *db;
bdaddr_t bdaddr;
uint8_t bdaddr_type;
unsigned int disc_id;
struct queue *ccc_states;
struct notify *pending;
};
typedef uint8_t (*btd_gatt_database_ccc_write_t) (struct pending_op *op,
void *user_data);
typedef void (*btd_gatt_database_destroy_t) (void *data);
struct ccc_state {
uint16_t handle;
uint8_t value[2];
};
struct ccc_cb_data {
uint16_t handle;
btd_gatt_database_ccc_write_t callback;
btd_gatt_database_destroy_t destroy;
void *user_data;
};
struct device_info {
bdaddr_t bdaddr;
uint8_t bdaddr_type;
};
static void ccc_cb_free(void *data)
{
struct ccc_cb_data *ccc_cb = data;
if (ccc_cb->destroy)
ccc_cb->destroy(ccc_cb->user_data);
free(ccc_cb);
}
static bool ccc_cb_match_service(const void *data, const void *match_data)
{
const struct ccc_cb_data *ccc_cb = data;
const struct gatt_db_attribute *attrib = match_data;
uint16_t start, end;
if (!gatt_db_attribute_get_service_handles(attrib, &start, &end))
return false;
return ccc_cb->handle >= start && ccc_cb->handle <= end;
}
static bool ccc_cb_match_handle(const void *data, const void *match_data)
{
const struct ccc_cb_data *ccc_cb = data;
uint16_t handle = PTR_TO_UINT(match_data);
return ccc_cb->handle == handle;
}
static bool dev_state_match(const void *a, const void *b)
{
const struct device_state *dev_state = a;
const struct device_info *dev_info = b;
return bacmp(&dev_state->bdaddr, &dev_info->bdaddr) == 0 &&
dev_state->bdaddr_type == dev_info->bdaddr_type;
}
static struct device_state *
find_device_state(struct btd_gatt_database *database, bdaddr_t *bdaddr,
uint8_t bdaddr_type)
{
struct device_info dev_info;
memset(&dev_info, 0, sizeof(dev_info));
bacpy(&dev_info.bdaddr, bdaddr);
dev_info.bdaddr_type = bdaddr_type;
return queue_find(database->device_states, dev_state_match, &dev_info);
}
static bool ccc_state_match(const void *a, const void *b)
{
const struct ccc_state *ccc = a;
uint16_t handle = PTR_TO_UINT(b);
return ccc->handle == handle;
}
static struct ccc_state *find_ccc_state(struct device_state *dev_state,
uint16_t handle)
{
return queue_find(dev_state->ccc_states, ccc_state_match,
UINT_TO_PTR(handle));
}
static struct device_state *device_state_create(struct btd_gatt_database *db,
const bdaddr_t *bdaddr,
uint8_t bdaddr_type)
{
struct device_state *dev_state;
dev_state = new0(struct device_state, 1);
dev_state->db = db;
dev_state->ccc_states = queue_new();
bacpy(&dev_state->bdaddr, bdaddr);
dev_state->bdaddr_type = bdaddr_type;
return dev_state;
}
static void device_state_free(void *data)
{
struct device_state *state = data;
queue_destroy(state->ccc_states, free);
if (state->pending) {
free(state->pending->value);
free(state->pending);
}
free(state);
}
static void clear_ccc_state(void *data, void *user_data)
{
struct ccc_state *ccc = data;
struct btd_gatt_database *db = user_data;
struct ccc_cb_data *ccc_cb;
if (!ccc->value[0])
return;
ccc_cb = queue_find(db->ccc_callbacks, ccc_cb_match_handle,
UINT_TO_PTR(ccc->handle));
if (!ccc_cb)
return;
if (ccc_cb->callback)
ccc_cb->callback(NULL, ccc_cb->user_data);
}
static void att_disconnected(int err, void *user_data)
{
struct device_state *state = user_data;
struct btd_device *device;
DBG("");
state->disc_id = 0;
device = btd_adapter_get_device(state->db->adapter, &state->bdaddr,
state->bdaddr_type);
if (!device)
goto remove;
if (device_is_bonded(device, state->bdaddr_type)) {
struct ccc_state *ccc;
uint16_t handle;
handle = gatt_db_attribute_get_handle(state->db->svc_chngd_ccc);
ccc = find_ccc_state(state, handle);
if (ccc)
device_store_svc_chng_ccc(device, state->bdaddr_type,
ccc->value[0]);
return;
}
remove:
/* Remove device state if device no longer exists or is not paired */
if (queue_remove(state->db->device_states, state)) {
queue_foreach(state->ccc_states, clear_ccc_state, state->db);
device_state_free(state);
}
}
static bool get_dst_info(struct bt_att *att, bdaddr_t *dst, uint8_t *dst_type)
{
GIOChannel *io = NULL;
GError *gerr = NULL;
io = g_io_channel_unix_new(bt_att_get_fd(att));
if (!io)
return false;
bt_io_get(io, &gerr, BT_IO_OPT_DEST_BDADDR, dst,
BT_IO_OPT_DEST_TYPE, dst_type,
BT_IO_OPT_INVALID);
if (gerr) {
error("gatt: bt_io_get: %s", gerr->message);
g_error_free(gerr);
g_io_channel_unref(io);
return false;
}
g_io_channel_unref(io);
return true;
}
static struct device_state *get_device_state(struct btd_gatt_database *database,
struct bt_att *att)
{
struct device_state *dev_state;
bdaddr_t bdaddr;
uint8_t bdaddr_type;
if (!get_dst_info(att, &bdaddr, &bdaddr_type))
return NULL;
/*
* Find and return a device state. If a matching state doesn't exist,
* then create a new one.
*/
dev_state = find_device_state(database, &bdaddr, bdaddr_type);
if (dev_state)
goto done;
dev_state = device_state_create(database, &bdaddr, bdaddr_type);
queue_push_tail(database->device_states, dev_state);
done:
if (!dev_state->disc_id)
dev_state->disc_id = bt_att_register_disconnect(att,
att_disconnected,
dev_state, NULL);
return dev_state;
}
static struct ccc_state *get_ccc_state(struct btd_gatt_database *database,
struct bt_att *att, uint16_t handle)
{
struct device_state *dev_state;
struct ccc_state *ccc;
dev_state = get_device_state(database, att);
if (!dev_state)
return NULL;
ccc = find_ccc_state(dev_state, handle);
if (ccc)
return ccc;
ccc = new0(struct ccc_state, 1);
ccc->handle = handle;
queue_push_tail(dev_state->ccc_states, ccc);
return ccc;
}
static void cancel_pending_read(void *data)
{
struct pending_op *op = data;
gatt_db_attribute_read_result(op->attrib, op->id,
BT_ATT_ERROR_REQUEST_NOT_SUPPORTED,
NULL, 0);
op->owner_queue = NULL;
}
static void cancel_pending_write(void *data)
{
struct pending_op *op = data;
gatt_db_attribute_write_result(op->attrib, op->id,
BT_ATT_ERROR_REQUEST_NOT_SUPPORTED);
op->owner_queue = NULL;
}
static void chrc_free(void *data)
{
struct external_chrc *chrc = data;
io_destroy(chrc->write_io);
io_destroy(chrc->notify_io);
queue_destroy(chrc->pending_reads, cancel_pending_read);
queue_destroy(chrc->pending_writes, cancel_pending_write);
g_free(chrc->path);
g_dbus_proxy_set_property_watch(chrc->proxy, NULL, NULL);
g_dbus_proxy_unref(chrc->proxy);
free(chrc);
}
static void desc_free(void *data)
{
struct external_desc *desc = data;
queue_destroy(desc->pending_reads, cancel_pending_read);
queue_destroy(desc->pending_writes, cancel_pending_write);
g_dbus_proxy_unref(desc->proxy);
g_free(desc->chrc_path);
free(desc);
}
static void inc_free(void *data)
{
struct external_desc *inc = data;
free(inc);
}
static void service_free(void *data)
{
struct external_service *service = data;
queue_destroy(service->chrcs, chrc_free);
queue_destroy(service->descs, desc_free);
queue_destroy(service->includes, inc_free);
if (service->attrib)
gatt_db_remove_service(service->app->database->db,
service->attrib);
if (service->app->client)
g_dbus_proxy_unref(service->proxy);
g_free(service->path);
free(service);
}
static void profile_remove(void *data)
{
struct btd_profile *p = data;
DBG("Removed \"%s\"", p->name);
adapter_foreach(adapter_remove_profile, p);
btd_profile_unregister(p);
g_free((void *) p->name);
g_free((void *) p->remote_uuid);
free(p);
}
static void profile_release(struct external_profile *profile)
{
DBG("Releasing \"%s\"", profile->app->owner);
g_dbus_proxy_method_call(profile->proxy, "Release", NULL, NULL, NULL,
NULL);
}
static void profile_free(void *data)
{
struct external_profile *profile = data;
queue_destroy(profile->profiles, profile_remove);
profile_release(profile);
g_dbus_proxy_unref(profile->proxy);
free(profile);
}
static void app_free(void *data)
{
struct gatt_app *app = data;
queue_destroy(app->profiles, profile_free);
queue_destroy(app->services, service_free);
queue_destroy(app->proxies, NULL);
if (app->client) {
g_dbus_client_set_disconnect_watch(app->client, NULL, NULL);
g_dbus_client_set_proxy_handlers(app->client, NULL, NULL,
NULL, NULL);
g_dbus_client_set_ready_watch(app->client, NULL, NULL);
g_dbus_client_unref(app->client);
}
if (app->reg)
dbus_message_unref(app->reg);
g_free(app->owner);
g_free(app->path);
free(app);
}
static void gatt_record_free(void *data)
{
struct gatt_record *rec = data;
adapter_service_remove(rec->database->adapter, rec->handle);
free(rec);
}
static void gatt_database_free(void *data)
{
struct btd_gatt_database *database = data;
if (database->le_io) {
g_io_channel_shutdown(database->le_io, FALSE, NULL);
g_io_channel_unref(database->le_io);
}
if (database->l2cap_io) {
g_io_channel_shutdown(database->l2cap_io, FALSE, NULL);
g_io_channel_unref(database->l2cap_io);
}
/* TODO: Persistently store CCC states before freeing them */
gatt_db_unregister(database->db, database->db_id);
queue_destroy(database->records, gatt_record_free);
queue_destroy(database->device_states, device_state_free);
queue_destroy(database->apps, app_free);
queue_destroy(database->profiles, profile_free);
queue_destroy(database->ccc_callbacks, ccc_cb_free);
database->device_states = NULL;
database->ccc_callbacks = NULL;
gatt_db_unref(database->db);
btd_adapter_unref(database->adapter);
free(database);
}
static void connect_cb(GIOChannel *io, GError *gerr, gpointer user_data)
{
struct btd_adapter *adapter;
struct btd_device *device;
uint8_t dst_type;
bdaddr_t src, dst;
if (gerr) {
error("%s", gerr->message);
return;
}
bt_io_get(io, &gerr, BT_IO_OPT_SOURCE_BDADDR, &src,
BT_IO_OPT_DEST_BDADDR, &dst,
BT_IO_OPT_DEST_TYPE, &dst_type,
BT_IO_OPT_INVALID);
if (gerr) {
error("bt_io_get: %s", gerr->message);
g_error_free(gerr);
return;
}
DBG("New incoming %s ATT connection", dst_type == BDADDR_BREDR ?
"BR/EDR" : "LE");
adapter = adapter_find(&src);
if (!adapter)
return;
device = btd_adapter_get_device(adapter, &dst, dst_type);
if (!device)
return;
device_attach_att(device, io);
}
static void gap_device_name_read_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct btd_gatt_database *database = user_data;
uint8_t error = 0;
size_t len = 0;
const uint8_t *value = NULL;
const char *device_name;
DBG("GAP Device Name read request\n");
device_name = btd_adapter_get_name(database->adapter);
len = strlen(device_name);
if (offset > len) {
error = BT_ATT_ERROR_INVALID_OFFSET;
goto done;
}
len -= offset;
value = len ? (const uint8_t *) &device_name[offset] : NULL;
done:
gatt_db_attribute_read_result(attrib, id, error, value, len);
}
static void gap_appearance_read_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct btd_gatt_database *database = user_data;
uint8_t error = 0;
size_t len = 2;
const uint8_t *value = NULL;
uint8_t appearance[2];
uint32_t dev_class;
DBG("GAP Appearance read request\n");
dev_class = btd_adapter_get_class(database->adapter);
if (offset > 2) {
error = BT_ATT_ERROR_INVALID_OFFSET;
goto done;
}
appearance[0] = dev_class & 0x00ff;
appearance[1] = (dev_class >> 8) & 0x001f;
len -= offset;
value = len ? &appearance[offset] : NULL;
done:
gatt_db_attribute_read_result(attrib, id, error, value, len);
}
static sdp_record_t *record_new(uuid_t *uuid, uint16_t start, uint16_t end)
{
sdp_list_t *svclass_id, *apseq, *proto[2], *root, *aproto;
uuid_t root_uuid, proto_uuid, l2cap;
sdp_record_t *record;
sdp_data_t *psm, *sh, *eh;
uint16_t lp = ATT_PSM;
if (uuid == NULL)
return NULL;
if (start > end)
return NULL;
record = sdp_record_alloc();
if (record == NULL)
return NULL;
sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
root = sdp_list_append(NULL, &root_uuid);
sdp_set_browse_groups(record, root);
sdp_list_free(root, NULL);
svclass_id = sdp_list_append(NULL, uuid);
sdp_set_service_classes(record, svclass_id);
sdp_list_free(svclass_id, NULL);
sdp_uuid16_create(&l2cap, L2CAP_UUID);
proto[0] = sdp_list_append(NULL, &l2cap);
psm = sdp_data_alloc(SDP_UINT16, &lp);
proto[0] = sdp_list_append(proto[0], psm);
apseq = sdp_list_append(NULL, proto[0]);
sdp_uuid16_create(&proto_uuid, ATT_UUID);
proto[1] = sdp_list_append(NULL, &proto_uuid);
sh = sdp_data_alloc(SDP_UINT16, &start);
proto[1] = sdp_list_append(proto[1], sh);
eh = sdp_data_alloc(SDP_UINT16, &end);
proto[1] = sdp_list_append(proto[1], eh);
apseq = sdp_list_append(apseq, proto[1]);
aproto = sdp_list_append(NULL, apseq);
sdp_set_access_protos(record, aproto);
sdp_data_free(psm);
sdp_data_free(sh);
sdp_data_free(eh);
sdp_list_free(proto[0], NULL);
sdp_list_free(proto[1], NULL);
sdp_list_free(apseq, NULL);
sdp_list_free(aproto, NULL);
return record;
}
static void database_add_record(struct btd_gatt_database *database,
struct gatt_db_attribute *attr)
{
struct gatt_record *rec;
sdp_record_t *record;
uint16_t start, end;
uuid_t svc, gap_uuid;
bt_uuid_t uuid;
const char *name = NULL;
char uuidstr[MAX_LEN_UUID_STR];
gatt_db_attribute_get_service_uuid(attr, &uuid);
switch (uuid.type) {
case BT_UUID16:
name = bt_uuid16_to_str(uuid.value.u16);
sdp_uuid16_create(&svc, uuid.value.u16);
break;
case BT_UUID32:
name = bt_uuid32_to_str(uuid.value.u32);
sdp_uuid32_create(&svc, uuid.value.u32);
break;
case BT_UUID128:
bt_uuid_to_string(&uuid, uuidstr, sizeof(uuidstr));
name = bt_uuidstr_to_str(uuidstr);
sdp_uuid128_create(&svc, (void *) &uuid.value.u128);
break;
case BT_UUID_UNSPEC:
return;
}
gatt_db_attribute_get_service_handles(attr, &start, &end);
record = record_new(&svc, start, end);
if (!record)
return;
if (name != NULL)
sdp_set_info_attr(record, name, "BlueZ", NULL);
sdp_uuid16_create(&gap_uuid, UUID_GAP);
if (sdp_uuid_cmp(&svc, &gap_uuid) == 0) {
sdp_set_url_attr(record, "http://www.bluez.org/",
"http://www.bluez.org/",
"http://www.bluez.org/");
}
if (adapter_service_add(database->adapter, record) < 0) {
sdp_record_free(record);
return;
}
rec = new0(struct gatt_record, 1);
rec->database = database;
rec->handle = record->handle;
rec->attr = attr;
queue_push_tail(database->records, rec);
}
static void populate_gap_service(struct btd_gatt_database *database)
{
bt_uuid_t uuid;
struct gatt_db_attribute *service;
/* Add the GAP service */
bt_uuid16_create(&uuid, UUID_GAP);
service = gatt_db_add_service(database->db, &uuid, true, 5);
/*
* Device Name characteristic.
*/
bt_uuid16_create(&uuid, GATT_CHARAC_DEVICE_NAME);
gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ,
gap_device_name_read_cb,
NULL, database);
/*
* Device Appearance characteristic.
*/
bt_uuid16_create(&uuid, GATT_CHARAC_APPEARANCE);
gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ,
gap_appearance_read_cb,
NULL, database);
gatt_db_service_set_active(service, true);
}
static void gatt_ccc_read_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct btd_gatt_database *database = user_data;
struct ccc_state *ccc;
uint16_t handle;
uint8_t ecode = 0;
const uint8_t *value = NULL;
size_t len = 0;
handle = gatt_db_attribute_get_handle(attrib);
DBG("CCC read called for handle: 0x%04x", handle);
if (offset > 2) {
ecode = BT_ATT_ERROR_INVALID_OFFSET;
goto done;
}
ccc = get_ccc_state(database, att, handle);
if (!ccc) {
ecode = BT_ATT_ERROR_UNLIKELY;
goto done;
}
len = 2 - offset;
value = len ? &ccc->value[offset] : NULL;
done:
gatt_db_attribute_read_result(attrib, id, ecode, value, len);
}
static struct btd_device *att_get_device(struct bt_att *att)
{
GIOChannel *io = NULL;
GError *gerr = NULL;
bdaddr_t src, dst;
uint8_t dst_type;
struct btd_adapter *adapter;
io = g_io_channel_unix_new(bt_att_get_fd(att));
if (!io)
return NULL;
bt_io_get(io, &gerr, BT_IO_OPT_SOURCE_BDADDR, &src,
BT_IO_OPT_DEST_BDADDR, &dst,
BT_IO_OPT_DEST_TYPE, &dst_type,
BT_IO_OPT_INVALID);
if (gerr) {
error("bt_io_get: %s", gerr->message);
g_error_free(gerr);
g_io_channel_unref(io);
return NULL;
}
g_io_channel_unref(io);
adapter = adapter_find(&src);
if (!adapter) {
error("Unable to find adapter object");
return NULL;
}
return btd_adapter_find_device(adapter, &dst, dst_type);
}
static struct pending_op *pending_ccc_new(struct bt_att *att,
struct gatt_db_attribute *attrib,
uint16_t value,
uint8_t link_type)
{
struct pending_op *op;
struct btd_device *device;
device = att_get_device(att);
if (!device) {
error("Unable to find device object");
return NULL;
}
op = new0(struct pending_op, 1);
op->data.iov_base = UINT_TO_PTR(value);
op->data.iov_len = sizeof(value);
op->device = device;
op->attrib = attrib;
op->link_type = link_type;
return op;
}
static void pending_op_free(void *data)
{
struct pending_op *op = data;
if (op->owner_queue)
queue_remove(op->owner_queue, op);
free(op);
}
static void gatt_ccc_write_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
const uint8_t *value, size_t len,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct btd_gatt_database *database = user_data;
struct ccc_state *ccc;
struct ccc_cb_data *ccc_cb;
uint16_t handle;
uint8_t ecode = 0;
handle = gatt_db_attribute_get_handle(attrib);
DBG("CCC write called for handle: 0x%04x", handle);
if (!value || len != 2) {
ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
goto done;
}
if (offset > 2) {
ecode = BT_ATT_ERROR_INVALID_OFFSET;
goto done;
}
ccc = get_ccc_state(database, att, handle);
if (!ccc) {
ecode = BT_ATT_ERROR_UNLIKELY;
goto done;
}
ccc_cb = queue_find(database->ccc_callbacks, ccc_cb_match_handle,
UINT_TO_PTR(gatt_db_attribute_get_handle(attrib)));
if (!ccc_cb) {
ecode = BT_ATT_ERROR_UNLIKELY;
goto done;
}
/* If value is identical, then just succeed */
if (ccc->value[0] == value[0] && ccc->value[1] == value[1])
goto done;
if (ccc_cb->callback) {
struct pending_op *op;
op = pending_ccc_new(att, attrib, get_le16(value),
bt_att_get_link_type(att));
if (!op) {
ecode = BT_ATT_ERROR_UNLIKELY;
goto done;
}
ecode = ccc_cb->callback(op, ccc_cb->user_data);
if (ecode)
pending_op_free(op);
}
if (!ecode) {
ccc->value[0] = value[0];
ccc->value[1] = value[1];
}
done:
gatt_db_attribute_write_result(attrib, id, ecode);
}
static struct gatt_db_attribute *
service_add_ccc(struct gatt_db_attribute *service,
struct btd_gatt_database *database,
btd_gatt_database_ccc_write_t write_callback,
void *user_data,
btd_gatt_database_destroy_t destroy)
{
struct gatt_db_attribute *ccc;
struct ccc_cb_data *ccc_cb;
bt_uuid_t uuid;
ccc_cb = new0(struct ccc_cb_data, 1);
bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID);
ccc = gatt_db_service_add_descriptor(service, &uuid,
BT_ATT_PERM_READ | BT_ATT_PERM_WRITE,
gatt_ccc_read_cb, gatt_ccc_write_cb, database);
if (!ccc) {
error("Failed to create CCC entry in database");
free(ccc_cb);
return NULL;
}
ccc_cb->handle = gatt_db_attribute_get_handle(ccc);
ccc_cb->callback = write_callback;
ccc_cb->destroy = destroy;
ccc_cb->user_data = user_data;
queue_push_tail(database->ccc_callbacks, ccc_cb);
return ccc;
}
static void populate_gatt_service(struct btd_gatt_database *database)
{
bt_uuid_t uuid;
struct gatt_db_attribute *service;
/* Add the GATT service */
bt_uuid16_create(&uuid, UUID_GATT);
service = gatt_db_add_service(database->db, &uuid, true, 4);
bt_uuid16_create(&uuid, GATT_CHARAC_SERVICE_CHANGED);
database->svc_chngd = gatt_db_service_add_characteristic(service, &uuid,
BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_INDICATE,
NULL, NULL, database);
database->svc_chngd_ccc = service_add_ccc(service, database, NULL, NULL,
NULL);
gatt_db_service_set_active(service, true);
}
static void register_core_services(struct btd_gatt_database *database)
{
populate_gap_service(database);
populate_gatt_service(database);
}
static void conf_cb(void *user_data)
{
GDBusProxy *proxy = user_data;
DBG("GATT server received confirmation");
if (proxy != NULL)
{
g_dbus_proxy_method_call(proxy, "Confirm", NULL, NULL, NULL, NULL);
}
}
static void service_changed_conf(void *user_data)
{
DBG("");
}
static void state_set_pending(struct device_state *state, struct notify *notify)
{
uint16_t start, end, old_start, old_end;
/* Cache this only for Service Changed */
if (notify->conf != service_changed_conf)
return;
if (state->pending) {
old_start = get_le16(state->pending->value);
old_end = get_le16(state->pending->value + 2);
start = get_le16(notify->value);
end = get_le16(notify->value + 2);
if (start < old_start)
put_le16(start, state->pending->value);
if (end > old_end)
put_le16(end, state->pending->value + 2);
return;
}
/* Copy notify contents to pending */
state->pending = new0(struct notify, 1);
memcpy(state->pending, notify, sizeof(*notify));
state->pending->value = malloc(notify->len);
memcpy(state->pending->value, notify->value, notify->len);
}
static void send_notification_to_device(void *data, void *user_data)
{
struct device_state *device_state = data;
struct notify *notify = user_data;
struct ccc_state *ccc;
struct btd_device *device;
struct bt_gatt_server *server;
ccc = find_ccc_state(device_state, notify->ccc_handle);
if (!ccc)
return;
if (!ccc->value[0] || (notify->conf && !(ccc->value[0] & 0x02)))
return;
device = btd_adapter_get_device(notify->database->adapter,
&device_state->bdaddr,
device_state->bdaddr_type);
if (!device)
goto remove;
server = btd_device_get_gatt_server(device);
if (!server) {
if (!device_is_bonded(device, device_state->bdaddr_type))
goto remove;
state_set_pending(device_state, notify);
return;
}
/*
* TODO: If the device is not connected but bonded, send the
* notification/indication when it becomes connected.
*/
if (!notify->conf) {
DBG("GATT server sending notification");
bt_gatt_server_send_notification(server,
notify->handle, notify->value,
notify->len);
return;
}
DBG("GATT server sending indication");
bt_gatt_server_send_indication(server, notify->handle, notify->value,
notify->len, notify->conf,
notify->user_data, NULL);
return;
remove:
/* Remove device state if device no longer exists or is not paired */
if (queue_remove(notify->database->device_states, device_state)) {
queue_foreach(device_state->ccc_states, clear_ccc_state,
notify->database);
device_state_free(device_state);
}
}
static void send_notification_to_devices(struct btd_gatt_database *database,
uint16_t handle, uint8_t *value,
uint16_t len, uint16_t ccc_handle,
bt_gatt_server_conf_func_t conf,
void *user_data)
{
struct notify notify;
memset(&notify, 0, sizeof(notify));
notify.database = database;
notify.handle = handle;
notify.ccc_handle = ccc_handle;
notify.value = value;
notify.len = len;
notify.conf = conf;
notify.user_data = user_data;
queue_foreach(database->device_states, send_notification_to_device,
&notify);
}
static void send_service_changed(struct btd_gatt_database *database,
struct gatt_db_attribute *attrib)
{
uint16_t start, end;
uint8_t value[4];
uint16_t handle, ccc_handle;
if (!gatt_db_attribute_get_service_handles(attrib, &start, &end)) {
error("Failed to obtain changed service handles");
return;
}
handle = gatt_db_attribute_get_handle(database->svc_chngd);
ccc_handle = gatt_db_attribute_get_handle(database->svc_chngd_ccc);
if (!handle || !ccc_handle) {
error("Failed to obtain handles for \"Service Changed\""
" characteristic");
return;
}
put_le16(start, value);
put_le16(end, value + 2);
send_notification_to_devices(database, handle, value, sizeof(value),
ccc_handle, service_changed_conf, NULL);
}
static void gatt_db_service_added(struct gatt_db_attribute *attrib,
void *user_data)
{
struct btd_gatt_database *database = user_data;
DBG("GATT Service added to local database");
database_add_record(database, attrib);
send_service_changed(database, attrib);
}
static bool ccc_match_service(const void *data, const void *match_data)
{
const struct ccc_state *ccc = data;
const struct gatt_db_attribute *attrib = match_data;
uint16_t start, end;
if (!gatt_db_attribute_get_service_handles(attrib, &start, &end))
return false;
return ccc->handle >= start && ccc->handle <= end;
}
static void remove_device_ccc(void *data, void *user_data)
{
struct device_state *state = data;
queue_remove_all(state->ccc_states, ccc_match_service, user_data, free);
}
static bool match_gatt_record(const void *data, const void *user_data)
{
const struct gatt_record *rec = data;
const struct gatt_db_attribute *attr = user_data;
return (rec->attr == attr);
}
static void gatt_db_service_removed(struct gatt_db_attribute *attrib,
void *user_data)
{
struct btd_gatt_database *database = user_data;
struct gatt_record *rec;
DBG("Local GATT service removed");
rec = queue_remove_if(database->records, match_gatt_record, attrib);
if (rec)
gatt_record_free(rec);
send_service_changed(database, attrib);
queue_foreach(database->device_states, remove_device_ccc, attrib);
queue_remove_all(database->ccc_callbacks, ccc_cb_match_service, attrib,
ccc_cb_free);
}
struct svc_match_data {
const char *path;
const char *sender;
};
static bool match_app(const void *a, const void *b)
{
const struct gatt_app *app = a;
const struct svc_match_data *data = b;
return g_strcmp0(app->path, data->path) == 0 &&
g_strcmp0(app->owner, data->sender) == 0;
}
static gboolean app_free_idle_cb(void *data)
{
app_free(data);
return FALSE;
}
static void client_disconnect_cb(DBusConnection *conn, void *user_data)
{
struct gatt_app *app = user_data;
struct btd_gatt_database *database = app->database;
DBG("Client disconnected");
if (queue_remove(database->apps, app))
app_free(app);
}
static void remove_app(void *data)
{
struct gatt_app *app = data;
/*
* Set callback to NULL to avoid potential race condition
* when calling remove_app and GDBusClient unref.
*/
g_dbus_client_set_disconnect_watch(app->client, NULL, NULL);
/*
* Set proxy handlers to NULL, so that this gets called only once when
* the first proxy that belongs to this service gets removed.
*/
g_dbus_client_set_proxy_handlers(app->client, NULL, NULL, NULL, NULL);
queue_remove(app->database->apps, app);
/*
* Do not run in the same loop, this may be a disconnect
* watch call and GDBusClient should not be destroyed.
*/
g_idle_add(app_free_idle_cb, app);
}
static bool match_service_by_path(const void *a, const void *b)
{
const struct external_service *service = a;
const char *path = b;
return strcmp(service->path, path) == 0;
}
static bool parse_path(GDBusProxy *proxy, const char *name, const char **path)
{
DBusMessageIter iter;
if (!g_dbus_proxy_get_property(proxy, name, &iter))
return false;
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_OBJECT_PATH)
return false;
dbus_message_iter_get_basic(&iter, path);
return true;
}
static bool incr_attr_count(struct external_service *service, uint16_t incr)
{
if (service->attr_cnt > UINT16_MAX - incr)
return false;
service->attr_cnt += incr;
return true;
}
static bool parse_chrc_flags(DBusMessageIter *array, uint8_t *props,
uint8_t *ext_props, uint32_t *perm,
bool *req_prep_authorization)
{
const char *flag;
*props = *ext_props = 0;
do {
if (dbus_message_iter_get_arg_type(array) != DBUS_TYPE_STRING)
return false;
dbus_message_iter_get_basic(array, &flag);
if (!strcmp("broadcast", flag))
*props |= BT_GATT_CHRC_PROP_BROADCAST;
else if (!strcmp("read", flag)) {
*props |= BT_GATT_CHRC_PROP_READ;
*perm |= BT_ATT_PERM_READ;
} else if (!strcmp("write-without-response", flag)) {
*props |= BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP;
*perm |= BT_ATT_PERM_WRITE;
} else if (!strcmp("write", flag)) {
*props |= BT_GATT_CHRC_PROP_WRITE;
*perm |= BT_ATT_PERM_WRITE;
} else if (!strcmp("notify", flag)) {
*props |= BT_GATT_CHRC_PROP_NOTIFY;
} else if (!strcmp("indicate", flag)) {
*props |= BT_GATT_CHRC_PROP_INDICATE;
} else if (!strcmp("authenticated-signed-writes", flag)) {
*props |= BT_GATT_CHRC_PROP_AUTH;
*perm |= BT_ATT_PERM_WRITE;
} else if (!strcmp("reliable-write", flag)) {
*ext_props |= BT_GATT_CHRC_EXT_PROP_RELIABLE_WRITE;
*perm |= BT_ATT_PERM_WRITE;
} else if (!strcmp("writable-auxiliaries", flag)) {
*ext_props |= BT_GATT_CHRC_EXT_PROP_WRITABLE_AUX;
} else if (!strcmp("encrypt-read", flag)) {
*props |= BT_GATT_CHRC_PROP_READ;
*ext_props |= BT_GATT_CHRC_EXT_PROP_ENC_READ;
*perm |= BT_ATT_PERM_READ | BT_ATT_PERM_READ_ENCRYPT;
} else if (!strcmp("encrypt-write", flag)) {
*props |= BT_GATT_CHRC_PROP_WRITE;
*ext_props |= BT_GATT_CHRC_EXT_PROP_ENC_WRITE;
*perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_ENCRYPT;
} else if (!strcmp("encrypt-authenticated-read", flag)) {
*props |= BT_GATT_CHRC_PROP_READ;
*ext_props |= BT_GATT_CHRC_EXT_PROP_AUTH_READ;
*perm |= BT_ATT_PERM_READ | BT_ATT_PERM_READ_AUTHEN;
} else if (!strcmp("encrypt-authenticated-write", flag)) {
*props |= BT_GATT_CHRC_PROP_WRITE;
*ext_props |= BT_GATT_CHRC_EXT_PROP_AUTH_WRITE;
*perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_AUTHEN;
} else if (!strcmp("secure-read", flag)) {
*props |= BT_GATT_CHRC_PROP_READ;
*ext_props |= BT_GATT_CHRC_EXT_PROP_AUTH_READ;
*perm |= BT_ATT_PERM_READ | BT_ATT_PERM_READ_SECURE;
} else if (!strcmp("secure-write", flag)) {
*props |= BT_GATT_CHRC_PROP_WRITE;
*ext_props |= BT_GATT_CHRC_EXT_PROP_AUTH_WRITE;
*perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_SECURE;
} else if (!strcmp("authorize", flag)) {
*req_prep_authorization = true;
} else {
error("Invalid characteristic flag: %s", flag);
return false;
}
} while (dbus_message_iter_next(array));
if (*ext_props)
*props |= BT_GATT_CHRC_PROP_EXT_PROP;
return true;
}
static bool parse_desc_flags(DBusMessageIter *array, uint32_t *perm,
bool *req_prep_authorization)
{
const char *flag;
*perm = 0;
do {
if (dbus_message_iter_get_arg_type(array) != DBUS_TYPE_STRING)
return false;
dbus_message_iter_get_basic(array, &flag);
if (!strcmp("read", flag))
*perm |= BT_ATT_PERM_READ;
else if (!strcmp("write", flag))
*perm |= BT_ATT_PERM_WRITE;
else if (!strcmp("encrypt-read", flag))
*perm |= BT_ATT_PERM_READ | BT_ATT_PERM_READ_ENCRYPT;
else if (!strcmp("encrypt-write", flag))
*perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_ENCRYPT;
else if (!strcmp("encrypt-authenticated-read", flag))
*perm |= BT_ATT_PERM_READ | BT_ATT_PERM_READ_AUTHEN;
else if (!strcmp("encrypt-authenticated-write", flag))
*perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_AUTHEN;
else if (!strcmp("secure-read", flag))
*perm |= BT_ATT_PERM_READ | BT_ATT_PERM_READ_SECURE;
else if (!strcmp("secure-write", flag))
*perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_SECURE;
else if (!strcmp("authorize", flag))
*req_prep_authorization = true;
else {
error("Invalid descriptor flag: %s", flag);
return false;
}
} while (dbus_message_iter_next(array));
return true;
}
static bool parse_flags(GDBusProxy *proxy, uint8_t *props, uint8_t *ext_props,
uint32_t *perm, bool *req_prep_authorization)
{
DBusMessageIter iter, array;
const char *iface;
if (!g_dbus_proxy_get_property(proxy, "Flags", &iter))
return false;
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY)
return false;
dbus_message_iter_recurse(&iter, &array);
iface = g_dbus_proxy_get_interface(proxy);
if (!strcmp(iface, GATT_DESC_IFACE))
return parse_desc_flags(&array, perm, req_prep_authorization);
return parse_chrc_flags(&array, props, ext_props, perm,
req_prep_authorization);
}
static struct external_chrc *chrc_create(struct gatt_app *app,
GDBusProxy *proxy,
const char *path)
{
struct external_service *service;
struct external_chrc *chrc;
const char *service_path;
if (!parse_path(proxy, "Service", &service_path)) {
error("Failed to obtain service path for characteristic");
return NULL;
}
service = queue_find(app->services, match_service_by_path,
service_path);
if (!service) {
error("Unable to find service for characteristic: %s", path);
return NULL;
}
chrc = new0(struct external_chrc, 1);
chrc->pending_reads = queue_new();
chrc->pending_writes = queue_new();
chrc->path = g_strdup(path);
if (!chrc->path)
goto fail;
chrc->service = service;
chrc->proxy = g_dbus_proxy_ref(proxy);
/*
* Add 2 for the characteristic declaration and the value
* attribute.
*/
if (!incr_attr_count(chrc->service, 2)) {
error("Failed to increment attribute count");
goto fail;
}
/*
* Parse characteristic flags (i.e. properties) here since they
* are used to determine if any special descriptors should be
* created.
*/
if (!parse_flags(proxy, &chrc->props, &chrc->ext_props, &chrc->perm,
&chrc->req_prep_authorization)) {
error("Failed to parse characteristic properties");
goto fail;
}
if ((chrc->props & BT_GATT_CHRC_PROP_NOTIFY ||
chrc->props & BT_GATT_CHRC_PROP_INDICATE) &&
!incr_attr_count(chrc->service, 1)) {
error("Failed to increment attribute count for CCC");
goto fail;
}
if (chrc->ext_props && !incr_attr_count(chrc->service, 1)) {
error("Failed to increment attribute count for CEP");
goto fail;
}
queue_push_tail(chrc->service->chrcs, chrc);
return chrc;
fail:
chrc_free(chrc);
return NULL;
}
static bool match_chrc(const void *a, const void *b)
{
const struct external_chrc *chrc = a;
const char *path = b;
return strcmp(chrc->path, path) == 0;
}
static bool match_service_by_chrc(const void *a, const void *b)
{
const struct external_service *service = a;
const char *path = b;
return queue_find(service->chrcs, match_chrc, path);
}
static struct external_desc *desc_create(struct gatt_app *app,
GDBusProxy *proxy)
{
struct external_service *service;
struct external_desc *desc;
const char *chrc_path;
if (!parse_path(proxy, "Characteristic", &chrc_path)) {
error("Failed to obtain characteristic path for descriptor");
return NULL;
}
service = queue_find(app->services, match_service_by_chrc, chrc_path);
if (!service) {
error("Unable to find service for characteristic: %s",
chrc_path);
return NULL;
}
desc = new0(struct external_desc, 1);
desc->pending_reads = queue_new();
desc->pending_writes = queue_new();
desc->chrc_path = g_strdup(chrc_path);
if (!desc->chrc_path)
goto fail;
desc->service = service;
desc->proxy = g_dbus_proxy_ref(proxy);
/* Add 1 for the descriptor attribute */
if (!incr_attr_count(desc->service, 1)) {
error("Failed to increment attribute count");
goto fail;
}
/*
* Parse descriptors flags here since they are used to
* determine the permission the descriptor should have
*/
if (!parse_flags(proxy, NULL, NULL, &desc->perm,
&desc->req_prep_authorization)) {
error("Failed to parse characteristic properties");
goto fail;
}
queue_push_tail(desc->service->descs, desc);
return desc;
fail:
desc_free(desc);
return NULL;
}
static bool check_service_path(GDBusProxy *proxy,
struct external_service *service)
{
const char *service_path;
if (!parse_path(proxy, "Service", &service_path))
return false;
return g_strcmp0(service_path, service->path) == 0;
}
static struct external_service *create_service(struct gatt_app *app,
GDBusProxy *proxy,
const char *path)
{
struct external_service *service;
if (!path || !g_str_has_prefix(path, "/"))
return NULL;
service = queue_find(app->services, match_service_by_path, path);
if (service) {
error("Duplicated service: %s", path);
return NULL;
}
service = new0(struct external_service, 1);
service->app = app;
service->path = g_strdup(path);
if (!service->path)
goto fail;
service->proxy = g_dbus_proxy_ref(proxy);
service->chrcs = queue_new();
service->descs = queue_new();
service->includes = queue_new();
/* Add 1 for the service declaration */
if (!incr_attr_count(service, 1)) {
error("Failed to increment attribute count");
goto fail;
}
queue_push_tail(app->services, service);
return service;
fail:
service_free(service);
return NULL;
}
static void proxy_added_cb(GDBusProxy *proxy, void *user_data)
{
struct gatt_app *app = user_data;
const char *iface, *path;
if (app->failed)
return;
queue_push_tail(app->proxies, proxy);
iface = g_dbus_proxy_get_interface(proxy);
path = g_dbus_proxy_get_path(proxy);
DBG("Object received: %s, iface: %s", path, iface);
}
static void proxy_removed_cb(GDBusProxy *proxy, void *user_data)
{
struct gatt_app *app = user_data;
struct external_service *service;
const char *path;
path = g_dbus_proxy_get_path(proxy);
service = queue_remove_if(app->services, match_service_by_path,
(void *) path);
if (!service)
return;
DBG("Proxy removed - removing service: %s", service->path);
service_free(service);
}
static bool parse_uuid(GDBusProxy *proxy, bt_uuid_t *uuid)
{
DBusMessageIter iter;
bt_uuid_t tmp;
const char *uuidstr;
if (!g_dbus_proxy_get_property(proxy, "UUID", &iter))
return false;
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
return false;
dbus_message_iter_get_basic(&iter, &uuidstr);
if (bt_string_to_uuid(uuid, uuidstr) < 0)
return false;
/* GAP & GATT services are created and managed by BlueZ */
bt_uuid16_create(&tmp, UUID_GAP);
if (!bt_uuid_cmp(&tmp, uuid)) {
error("GAP service must be handled by BlueZ");
return false;
}
bt_uuid16_create(&tmp, UUID_GATT);
if (!bt_uuid_cmp(&tmp, uuid)) {
error("GATT service must be handled by BlueZ");
return false;
}
return true;
}
static bool parse_includes(GDBusProxy *proxy, struct external_service *service)
{
DBusMessageIter iter;
DBusMessageIter array;
char *obj;
/* Includes property is optional */
if (!g_dbus_proxy_get_property(proxy, "Includes", &iter))
return true;
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY)
return false;
dbus_message_iter_recurse(&iter, &array);
do {
if (dbus_message_iter_get_arg_type(&array) !=
DBUS_TYPE_OBJECT_PATH)
return false;
dbus_message_iter_get_basic(&array, &obj);
if (!queue_push_tail(service->includes, obj)) {
error("Failed to add Includes path in queue\n");
return false;
}
incr_attr_count(service, 1);
} while (dbus_message_iter_next(&array));
return true;
}
static bool parse_primary(GDBusProxy *proxy, bool *primary)
{
DBusMessageIter iter;
dbus_bool_t val;
if (!g_dbus_proxy_get_property(proxy, "Primary", &iter))
return false;
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN)
return false;
dbus_message_iter_get_basic(&iter, &val);
*primary = val;
return true;
}
static uint8_t dbus_error_to_att_ecode(const char *error_name)
{
if (strcmp(error_name, "org.bluez.Error.Failed") == 0)
return 0x80; /* For now return this "application error" */
if (strcmp(error_name, "org.bluez.Error.NotSupported") == 0)
return BT_ATT_ERROR_REQUEST_NOT_SUPPORTED;
if (strcmp(error_name, "org.bluez.Error.NotAuthorized") == 0)
return BT_ATT_ERROR_AUTHORIZATION;
if (strcmp(error_name, "org.bluez.Error.InvalidValueLength") == 0)
return BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
if (strcmp(error_name, "org.bluez.Error.InvalidOffset") == 0)
return BT_ATT_ERROR_INVALID_OFFSET;
if (strcmp(error_name, "org.bluez.Error.InProgress") == 0)
return BT_ERROR_ALREADY_IN_PROGRESS;
return 0;
}
static void read_reply_cb(DBusMessage *message, void *user_data)
{
struct pending_op *op = user_data;
DBusError err;
DBusMessageIter iter, array;
uint8_t ecode = 0;
uint8_t *value = NULL;
int len = 0;
if (!op->owner_queue) {
DBG("Pending read was canceled when object got removed");
return;
}
dbus_error_init(&err);
if (dbus_set_error_from_message(&err, message) == TRUE) {
DBG("Failed to read value: %s: %s", err.name, err.message);
ecode = dbus_error_to_att_ecode(err.name);
ecode = ecode ? ecode : BT_ATT_ERROR_READ_NOT_PERMITTED;
dbus_error_free(&err);
goto done;
}
dbus_message_iter_init(message, &iter);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
/*
* Return not supported for this, as the external app basically
* doesn't properly support reading from this characteristic.
*/
ecode = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED;
error("Invalid return value received for \"ReadValue\"");
goto done;
}
dbus_message_iter_recurse(&iter, &array);
dbus_message_iter_get_fixed_array(&array, &value, &len);
if (len < 0) {
ecode = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED;
value = NULL;
len = 0;
goto done;
}
/* Truncate the value if it's too large */
len = MIN(BT_ATT_MAX_VALUE_LEN, len);
value = len ? value : NULL;
done:
gatt_db_attribute_read_result(op->attrib, op->id, ecode, value, len);
}
static struct pending_op *pending_read_new(struct btd_device *device,
struct queue *owner_queue,
struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t link_type)
{
struct pending_op *op;
op = new0(struct pending_op, 1);
op->owner_queue = owner_queue;
op->device = device;
op->attrib = attrib;
op->id = id;
op->offset = offset;
op->link_type = link_type;
queue_push_tail(owner_queue, op);
return op;
}
static void append_options(DBusMessageIter *iter, void *user_data)
{
struct pending_op *op = user_data;
const char *path = device_get_path(op->device);
const char *link;
switch (op->link_type) {
case BT_ATT_LINK_BREDR:
link = "BR/EDR";
break;
case BT_ATT_LINK_LE:
link = "LE";
break;
default:
link = NULL;
break;
}
dict_append_entry(iter, "device", DBUS_TYPE_OBJECT_PATH, &path);
if (op->offset)
dict_append_entry(iter, "offset", DBUS_TYPE_UINT16,
&op->offset);
if (link)
dict_append_entry(iter, "link", DBUS_TYPE_STRING, &link);
if (op->prep_authorize)
dict_append_entry(iter, "prepare-authorize", DBUS_TYPE_BOOLEAN,
&op->prep_authorize);
}
static void read_setup_cb(DBusMessageIter *iter, void *user_data)
{
struct pending_op *op = user_data;
DBusMessageIter dict;
dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_STRING_AS_STRING
DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
&dict);
append_options(&dict, op);
dbus_message_iter_close_container(iter, &dict);
}
static struct pending_op *send_read(struct btd_device *device,
struct gatt_db_attribute *attrib,
GDBusProxy *proxy,
struct queue *owner_queue,
unsigned int id,
uint16_t offset,
uint8_t link_type)
{
struct pending_op *op;
op = pending_read_new(device, owner_queue, attrib, id, offset,
link_type);
if (g_dbus_proxy_method_call(proxy, "ReadValue", read_setup_cb,
read_reply_cb, op, pending_op_free) == TRUE)
return op;
pending_op_free(op);
return NULL;
}
static void write_setup_cb(DBusMessageIter *iter, void *user_data)
{
struct pending_op *op = user_data;
DBusMessageIter array, dict;
dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "y", &array);
dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE,
&op->data.iov_base, op->data.iov_len);
dbus_message_iter_close_container(iter, &array);
dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_STRING_AS_STRING
DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
&dict);
append_options(&dict, op);
dbus_message_iter_close_container(iter, &dict);
if (!op->owner_queue) {
gatt_db_attribute_write_result(op->attrib, op->id, 0);
pending_op_free(op);
}
}
static void write_reply_cb(DBusMessage *message, void *user_data)
{
struct pending_op *op = user_data;
struct external_chrc *chrc;
struct external_desc *desc;
DBusError err;
DBusMessageIter iter;
uint8_t ecode = 0;
if (!op->owner_queue) {
DBG("Pending write was canceled when object got removed");
return;
}
dbus_error_init(&err);
if (dbus_set_error_from_message(&err, message) == TRUE) {
DBG("Failed to write value: %s: %s", err.name, err.message);
ecode = dbus_error_to_att_ecode(err.name);
ecode = ecode ? ecode : BT_ATT_ERROR_WRITE_NOT_PERMITTED;
dbus_error_free(&err);
goto done;
}
if (op->prep_authorize) {
if (op->is_characteristic) {
chrc = gatt_db_attribute_get_user_data(op->attrib);
chrc->prep_authorized = true;
} else {
desc = gatt_db_attribute_get_user_data(op->attrib);
desc->prep_authorized = true;
}
}
dbus_message_iter_init(message, &iter);
if (dbus_message_iter_has_next(&iter)) {
/*
* Return not supported for this, as the external app basically
* doesn't properly support the "WriteValue" API.
*/
ecode = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED;
error("Invalid return value received for \"WriteValue\"");
}
done:
gatt_db_attribute_write_result(op->attrib, op->id, ecode);
}
static struct pending_op *pending_write_new(struct btd_device *device,
struct queue *owner_queue,
struct gatt_db_attribute *attrib,
unsigned int id,
const uint8_t *value, size_t len,
uint16_t offset, uint8_t link_type,
bool is_characteristic,
bool prep_authorize)
{
struct pending_op *op;
op = new0(struct pending_op, 1);
op->data.iov_base = (uint8_t *) value;
op->data.iov_len = len;
op->device = device;
op->owner_queue = owner_queue;
op->attrib = attrib;
op->id = id;
op->offset = offset;
op->link_type = link_type;
op->is_characteristic = is_characteristic;
op->prep_authorize = prep_authorize;
queue_push_tail(owner_queue, op);
return op;
}
static struct pending_op *send_write(struct btd_device *device,
struct gatt_db_attribute *attrib,
GDBusProxy *proxy,
struct queue *owner_queue,
unsigned int id,
const uint8_t *value, size_t len,
uint16_t offset, uint8_t link_type,
bool is_characteristic,
bool prep_authorize)
{
struct pending_op *op;
op = pending_write_new(device, owner_queue, attrib, id, value, len,
offset, link_type, is_characteristic,
prep_authorize);
if (g_dbus_proxy_method_call(proxy, "WriteValue", write_setup_cb,
owner_queue ? write_reply_cb : NULL,
op, pending_op_free) == TRUE)
return op;
pending_op_free(op);
return NULL;
}
static bool pipe_hup(struct io *io, void *user_data)
{
struct external_chrc *chrc = user_data;
DBG("%p closed\n", io);
if (io == chrc->write_io)
chrc->write_io = NULL;
else
chrc->notify_io = NULL;
io_destroy(io);
return false;
}
static bool pipe_io_read(struct io *io, void *user_data)
{
struct external_chrc *chrc = user_data;
uint8_t buf[512];
int fd = io_get_fd(io);
ssize_t bytes_read;
bytes_read = read(fd, buf, sizeof(buf));
if (bytes_read < 0)
return false;
send_notification_to_devices(chrc->service->app->database,
gatt_db_attribute_get_handle(chrc->attrib),
buf, bytes_read,
gatt_db_attribute_get_handle(chrc->ccc),
chrc->props & BT_GATT_CHRC_PROP_INDICATE ?
conf_cb : NULL, chrc->proxy);
return true;
}
static struct io *pipe_io_new(int fd, void *user_data)
{
struct io *io;
io = io_new(fd);
io_set_close_on_destroy(io, true);
io_set_read_handler(io, pipe_io_read, user_data, NULL);
io_set_disconnect_handler(io, pipe_hup, user_data, NULL);
return io;
}
static int pipe_io_send(struct io *io, const void *data, size_t len)
{
struct iovec iov;
iov.iov_base = (void *) data;
iov.iov_len = len;
return io_send(io, &iov, 1);
}
static void acquire_write_reply(DBusMessage *message, void *user_data)
{
struct pending_op *op = user_data;
struct external_chrc *chrc;
DBusError err;
int fd;
uint16_t mtu;
chrc = gatt_db_attribute_get_user_data(op->attrib);
dbus_error_init(&err);
if (dbus_set_error_from_message(&err, message) == TRUE) {
error("Failed to acquire write: %s\n", err.name);
dbus_error_free(&err);
goto retry;
}
if ((dbus_message_get_args(message, NULL, DBUS_TYPE_UNIX_FD, &fd,
DBUS_TYPE_UINT16, &mtu,
DBUS_TYPE_INVALID) == false)) {
error("Invalid AcquireWrite response\n");
goto retry;
}
DBG("AcquireWrite success: fd %d MTU %u\n", fd, mtu);
chrc->write_io = pipe_io_new(fd, chrc);
if (pipe_io_send(chrc->write_io, op->data.iov_base,
op->data.iov_len) < 0)
goto retry;
gatt_db_attribute_write_result(op->attrib, op->id, 0);
return;
retry:
send_write(op->device, op->attrib, chrc->proxy, NULL, op->id,
op->data.iov_base, op->data.iov_len, 0,
op->link_type, false, false);
}
static void acquire_write_setup(DBusMessageIter *iter, void *user_data)
{
struct pending_op *op = user_data;
DBusMessageIter dict;
struct bt_gatt_server *server;
uint16_t mtu;
dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_STRING_AS_STRING
DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
&dict);
append_options(&dict, op);
server = btd_device_get_gatt_server(op->device);
mtu = bt_gatt_server_get_mtu(server);
dict_append_entry(&dict, "MTU", DBUS_TYPE_UINT16, &mtu);
dbus_message_iter_close_container(iter, &dict);
}
static struct pending_op *acquire_write(struct external_chrc *chrc,
struct btd_device *device,
struct gatt_db_attribute *attrib,
unsigned int id,
const uint8_t *value, size_t len,
uint8_t link_type)
{
struct pending_op *op;
op = pending_write_new(device, NULL, attrib, id, value, len, 0,
link_type, false, false);
if (g_dbus_proxy_method_call(chrc->proxy, "AcquireWrite",
acquire_write_setup,
acquire_write_reply,
op, pending_op_free))
return op;
pending_op_free(op);
return NULL;
}
static void acquire_notify_reply(DBusMessage *message, void *user_data)
{
struct pending_op *op = user_data;
struct external_chrc *chrc = (void *) op->data.iov_base;
DBusError err;
int fd;
uint16_t mtu;
dbus_error_init(&err);
if (dbus_set_error_from_message(&err, message) == TRUE) {
error("Failed to acquire notify: %s\n", err.name);
dbus_error_free(&err);
goto retry;
}
if ((dbus_message_get_args(message, NULL, DBUS_TYPE_UNIX_FD, &fd,
DBUS_TYPE_UINT16, &mtu,
DBUS_TYPE_INVALID) == false)) {
error("Invalid AcquirNotify response\n");
goto retry;
}
DBG("AcquireNotify success: fd %d MTU %u\n", fd, mtu);
chrc->notify_io = pipe_io_new(fd, chrc);
__sync_fetch_and_add(&chrc->ntfy_cnt, 1);
return;
retry:
g_dbus_proxy_method_call(chrc->proxy, "StartNotify", NULL, NULL,
NULL, NULL);
__sync_fetch_and_add(&chrc->ntfy_cnt, 1);
}
static void acquire_notify_setup(DBusMessageIter *iter, void *user_data)
{
DBusMessageIter dict;
struct pending_op *op = user_data;
struct bt_gatt_server *server;
uint16_t mtu;
dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_STRING_AS_STRING
DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
&dict);
append_options(&dict, op);
server = btd_device_get_gatt_server(op->device);
mtu = bt_gatt_server_get_mtu(server);
dict_append_entry(&dict, "MTU", DBUS_TYPE_UINT16, &mtu);
dbus_message_iter_close_container(iter, &dict);
}
static uint8_t ccc_write_cb(struct pending_op *op, void *user_data)
{
struct external_chrc *chrc = user_data;
DBusMessageIter iter;
uint16_t value;
value = op ? PTR_TO_UINT(op->data.iov_base) : 0;
DBG("External CCC write received with value: 0x%04x", value);
/* Notifications/indications disabled */
if (!value) {
if (!chrc->ntfy_cnt)
goto done;
if (__sync_sub_and_fetch(&chrc->ntfy_cnt, 1))
goto done;
if (chrc->notify_io) {
io_destroy(chrc->notify_io);
chrc->notify_io = NULL;
goto done;
}
/*
* Send request to stop notifying. This is best-effort
* operation, so simply ignore the return the value.
*/
g_dbus_proxy_method_call(chrc->proxy, "StopNotify", NULL,
NULL, NULL, NULL);
goto done;
}
if (chrc->ntfy_cnt == UINT_MAX)
/* Maximum number of per-device CCC descriptors configured */
return BT_ATT_ERROR_INSUFFICIENT_RESOURCES;
/* Don't support undefined CCC values yet */
if (value > 2 ||
(value == 1 && !(chrc->props & BT_GATT_CHRC_PROP_NOTIFY)) ||
(value == 2 && !(chrc->props & BT_GATT_CHRC_PROP_INDICATE)))
return BT_ERROR_CCC_IMPROPERLY_CONFIGURED;
if (chrc->notify_io) {
__sync_fetch_and_add(&chrc->ntfy_cnt, 1);
goto done;
}
/* Make use of AcquireNotify if supported */
if (g_dbus_proxy_get_property(chrc->proxy, "NotifyAcquired", &iter)) {
op->data.iov_base = (void *) chrc;
op->data.iov_len = sizeof(chrc);
if (g_dbus_proxy_method_call(chrc->proxy, "AcquireNotify",
acquire_notify_setup,
acquire_notify_reply,
op, pending_op_free))
return 0;
}
/*
* Always call StartNotify for an incoming enable and ignore the return
* value for now.
*/
if (g_dbus_proxy_method_call(chrc->proxy, "StartNotify", NULL, NULL,
NULL, NULL) == FALSE)
return BT_ATT_ERROR_UNLIKELY;
__sync_fetch_and_add(&chrc->ntfy_cnt, 1);
done:
if (op)
pending_op_free(op);
return 0;
}
static void property_changed_cb(GDBusProxy *proxy, const char *name,
DBusMessageIter *iter, void *user_data)
{
struct external_chrc *chrc = user_data;
DBusMessageIter array;
uint8_t *value = NULL;
int len = 0;
if (strcmp(name, "Value"))
return;
if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) {
DBG("Malformed \"Value\" property received");
return;
}
dbus_message_iter_recurse(iter, &array);
dbus_message_iter_get_fixed_array(&array, &value, &len);
if (len < 0) {
DBG("Malformed \"Value\" property received");
return;
}
/* Truncate the value if it's too large */
len = MIN(BT_ATT_MAX_VALUE_LEN, len);
value = len ? value : NULL;
send_notification_to_devices(chrc->service->app->database,
gatt_db_attribute_get_handle(chrc->attrib),
value, len,
gatt_db_attribute_get_handle(chrc->ccc),
chrc->props & BT_GATT_CHRC_PROP_INDICATE ?
conf_cb : NULL, proxy);
}
static bool database_add_ccc(struct external_service *service,
struct external_chrc *chrc)
{
if (!(chrc->props & BT_GATT_CHRC_PROP_NOTIFY) &&
!(chrc->props & BT_GATT_CHRC_PROP_INDICATE))
return true;
chrc->ccc = service_add_ccc(service->attrib, service->app->database,
ccc_write_cb, chrc, NULL);
if (!chrc->ccc) {
error("Failed to create CCC entry for characteristic");
return false;
}
if (g_dbus_proxy_set_property_watch(chrc->proxy, property_changed_cb,
chrc) == FALSE) {
error("Failed to set up property watch for characteristic");
return false;
}
DBG("Created CCC entry for characteristic");
return true;
}
static void cep_write_cb(struct gatt_db_attribute *attrib, int err,
void *user_data)
{
if (err)
DBG("Failed to store CEP value in the database");
else
DBG("Stored CEP value in the database");
}
static bool database_add_cep(struct external_service *service,
struct external_chrc *chrc)
{
struct gatt_db_attribute *cep;
bt_uuid_t uuid;
uint8_t value[2];
if (!chrc->ext_props)
return true;
bt_uuid16_create(&uuid, GATT_CHARAC_EXT_PROPER_UUID);
cep = gatt_db_service_add_descriptor(service->attrib, &uuid,
BT_ATT_PERM_READ,
NULL, NULL, NULL);
if (!cep) {
error("Failed to create CEP entry for characteristic");
return false;
}
memset(value, 0, sizeof(value));
value[0] = chrc->ext_props;
if (!gatt_db_attribute_write(cep, 0, value, sizeof(value), 0, NULL,
cep_write_cb, NULL)) {
DBG("Failed to store CEP value in the database");
return false;
}
DBG("Created CEP entry for characteristic");
return true;
}
static void desc_read_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct external_desc *desc = user_data;
struct btd_device *device;
if (desc->attrib != attrib) {
error("Read callback called with incorrect attribute");
goto fail;
}
device = att_get_device(att);
if (!device) {
error("Unable to find device object");
goto fail;
}
if (send_read(device, attrib, desc->proxy, desc->pending_reads, id,
offset, bt_att_get_link_type(att)))
return;
fail:
gatt_db_attribute_read_result(attrib, id, BT_ATT_ERROR_UNLIKELY,
NULL, 0);
}
static void desc_write_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
const uint8_t *value, size_t len,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct external_desc *desc = user_data;
struct btd_device *device;
if (desc->attrib != attrib) {
error("Read callback called with incorrect attribute");
goto fail;
}
device = att_get_device(att);
if (!device) {
error("Unable to find device object");
goto fail;
}
if (opcode == BT_ATT_OP_PREP_WRITE_REQ) {
if (!device_is_trusted(device) && !desc->prep_authorized &&
desc->req_prep_authorization)
send_write(device, attrib, desc->proxy,
desc->pending_writes, id, value, len,
offset, bt_att_get_link_type(att),
false, true);
else
gatt_db_attribute_write_result(attrib, id, 0);
return;
}
if (opcode == BT_ATT_OP_EXEC_WRITE_REQ)
desc->prep_authorized = false;
if (send_write(device, attrib, desc->proxy, desc->pending_writes, id,
value, len, offset, bt_att_get_link_type(att), false,
false))
return;
fail:
gatt_db_attribute_write_result(attrib, id, BT_ATT_ERROR_UNLIKELY);
}
static bool database_add_desc(struct external_service *service,
struct external_desc *desc)
{
bt_uuid_t uuid;
if (!parse_uuid(desc->proxy, &uuid)) {
error("Failed to read \"UUID\" property of descriptor");
return false;
}
desc->attrib = gatt_db_service_add_descriptor(service->attrib, &uuid,
desc->perm,
desc_read_cb,
desc_write_cb, desc);
if (!desc->attrib) {
error("Failed to create descriptor entry in database");
return false;
}
desc->handled = true;
return true;
}
static void chrc_read_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct external_chrc *chrc = user_data;
struct btd_device *device;
if (chrc->attrib != attrib) {
error("Read callback called with incorrect attribute");
goto fail;
}
device = att_get_device(att);
if (!device) {
error("Unable to find device object");
goto fail;
}
if (send_read(device, attrib, chrc->proxy, chrc->pending_reads, id,
offset, bt_att_get_link_type(att)))
return;
fail:
gatt_db_attribute_read_result(attrib, id, BT_ATT_ERROR_UNLIKELY,
NULL, 0);
}
static void chrc_write_cb(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
const uint8_t *value, size_t len,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct external_chrc *chrc = user_data;
struct btd_device *device;
struct queue *queue;
DBusMessageIter iter;
if (chrc->attrib != attrib) {
error("Write callback called with incorrect attribute");
goto fail;
}
device = att_get_device(att);
if (!device) {
error("Unable to find device object");
goto fail;
}
if (!(chrc->props & BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP))
queue = chrc->pending_writes;
else
queue = NULL;
if (opcode == BT_ATT_OP_PREP_WRITE_REQ) {
if (!device_is_trusted(device) && !chrc->prep_authorized &&
chrc->req_prep_authorization)
send_write(device, attrib, chrc->proxy, queue,
id, value, len, offset,
bt_att_get_link_type(att), true, true);
else
gatt_db_attribute_write_result(attrib, id, 0);
return;
}
if (opcode == BT_ATT_OP_EXEC_WRITE_REQ)
chrc->prep_authorized = false;
if (chrc->write_io) {
if (pipe_io_send(chrc->write_io, value, len) < 0) {
error("Unable to write: %s", strerror(errno));
goto fail;
}
gatt_db_attribute_write_result(attrib, id, 0);
return;
}
if (g_dbus_proxy_get_property(chrc->proxy, "WriteAcquired", &iter)) {
if (acquire_write(chrc, device, attrib, id, value, len,
bt_att_get_link_type(att)))
return;
}
if (send_write(device, attrib, chrc->proxy, queue, id, value, len,
offset, bt_att_get_link_type(att), false, false))
return;
fail:
gatt_db_attribute_write_result(attrib, id, BT_ATT_ERROR_UNLIKELY);
}
static void include_services(void *data ,void *userdata)
{
char *obj = data;
struct external_service *service = userdata;
struct gatt_db_attribute *attrib;
struct external_service *service_inc;
DBG("path %s", obj);
service_inc = queue_find(service->app->services, match_service_by_path,
obj);
if (!service_inc) {
error("include service not found\n");
return;
}
attrib = gatt_db_service_add_included(service->attrib,
service_inc->attrib);
if (!attrib) {
error("include service attributes failed\n");
return;
}
service->attrib = attrib;
}
static void database_add_includes(struct external_service *service)
{
queue_foreach(service->includes, include_services, service);
}
static bool database_add_chrc(struct external_service *service,
struct external_chrc *chrc)
{
bt_uuid_t uuid;
const struct queue_entry *entry;
if (!parse_uuid(chrc->proxy, &uuid)) {
error("Failed to read \"UUID\" property of characteristic");
return false;
}
if (!check_service_path(chrc->proxy, service)) {
error("Invalid service path for characteristic");
return false;
}
chrc->attrib = gatt_db_service_add_characteristic(service->attrib,
&uuid, chrc->perm,
chrc->props, chrc_read_cb,
chrc_write_cb, chrc);
if (!chrc->attrib) {
error("Failed to create characteristic entry in database");
return false;
}
if (!database_add_ccc(service, chrc))
return false;
if (!database_add_cep(service, chrc))
return false;
/* Handle the descriptors that belong to this characteristic. */
for (entry = queue_get_entries(service->descs); entry;
entry = entry-&