// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2015 Google Inc. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #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 "btd.h" #include "adapter.h" #include "device.h" #include "gatt-database.h" #include "dbus-common.h" #include "profile.h" #include "service.h" #include "textfile.h" #include "settings.h" #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 ERROR_FAILED ERROR_INTERFACE ".Failed" #define UUID_GAP 0x1800 #define UUID_GATT 0x1801 #define UUID_DIS 0x180a #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 *eatt_io; GIOChannel *bredr_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 gatt_db_attribute *cli_feat; struct gatt_db_attribute *db_hash; struct gatt_db_attribute *eatt; 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; uint32_t ccc_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 bt_att *att; unsigned int id; unsigned int disconn_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; }; #define CLI_FEAT_SIZE 1 struct device_state { struct btd_gatt_database *db; bdaddr_t bdaddr; uint8_t bdaddr_type; unsigned int disc_id; uint8_t cli_feat[CLI_FEAT_SIZE]; bool change_aware; bool out_of_sync; 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; uint16_t value; }; 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, const 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) 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; state->out_of_sync = false; device = btd_adapter_find_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 && ccc->value) device_store_svc_chng_ccc(device, state->bdaddr_type, ccc->value); 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; int fd; fd = bt_att_get_fd(att); if (fd < 0) return false; io = g_io_channel_unix_new(fd); 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 * find_device_state_by_att(struct btd_gatt_database *database, struct bt_att *att) { bdaddr_t bdaddr; uint8_t bdaddr_type; if (!get_dst_info(att, &bdaddr, &bdaddr_type)) return NULL; return find_device_state(database, &bdaddr, bdaddr_type); } 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->eatt_io) { g_io_channel_shutdown(database->eatt_io, FALSE, NULL); g_io_channel_unref(database->eatt_io); } if (database->bredr_io) { g_io_channel_shutdown(database->bredr_io, FALSE, NULL); g_io_channel_unref(database->bredr_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); appearance[0] = dev_class & 0x00ff; appearance[1] = (dev_class >> 8) & 0x001f; len -= offset; value = len ? &appearance[offset] : NULL; gatt_db_attribute_read_result(attrib, id, error, value, len); } static void gap_car_read_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { uint8_t value = 0x01; DBG("GAP Central Address Resolution read request\n"); gatt_db_attribute_read_result(attrib, id, 0, &value, sizeof(value)); } 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 = BT_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, *attrib; /* Add the GAP service */ bt_uuid16_create(&uuid, UUID_GAP); service = gatt_db_add_service(database->db, &uuid, true, 7); /* * 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); attrib = gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, gap_appearance_read_cb, NULL, database); gatt_db_attribute_set_fixed_length(attrib, 2); /* * Central Address Resolution characteristic. */ bt_uuid16_create(&uuid, GATT_CHARAC_CAR); attrib = gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, gap_car_read_cb, NULL, database); gatt_db_attribute_set_fixed_length(attrib, 1); gatt_db_service_set_active(service, true); database_add_record(database, service); } 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); ccc = get_ccc_state(database, att, handle); if (!ccc) { ecode = BT_ATT_ERROR_UNLIKELY; goto done; } len = sizeof(ccc->value); value = (void *) &ccc->value; 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 void pending_op_free(void *data) { struct pending_op *op = data; if (op->owner_queue) queue_remove(op->owner_queue, op); bt_att_unregister_disconnect(op->att, op->disconn_id); bt_att_unref(op->att); free(op); } static void pending_disconnect_cb(int err, void *user_data) { struct pending_op *op = user_data; op->owner_queue = NULL; } 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->att = bt_att_ref(att); op->attrib = attrib; op->link_type = link_type; op->disconn_id = bt_att_register_disconnect(att, pending_disconnect_cb, op, NULL); return 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, val; 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; } if (len == 1) val = *value; else val = get_le16(value); /* If value is identical, then just succeed */ if (val == ccc->value) 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) { struct pending_op *op; op = pending_ccc_new(att, attrib, val, 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 = val; done: gatt_db_attribute_write_result(attrib, id, ecode); } static void ccc_add_cb(struct btd_gatt_database *database, struct gatt_db_attribute *ccc, btd_gatt_database_ccc_write_t callback, void *user_data, btd_gatt_database_destroy_t destroy) { struct ccc_cb_data *ccc_cb; ccc_cb = new0(struct ccc_cb_data, 1); ccc_cb->handle = gatt_db_attribute_get_handle(ccc); ccc_cb->callback = callback; ccc_cb->destroy = destroy; ccc_cb->user_data = user_data; queue_push_tail(database->ccc_callbacks, ccc_cb); } 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, uint32_t perm, btd_gatt_database_destroy_t destroy) { struct gatt_db_attribute *ccc; ccc = gatt_db_service_add_ccc(service, perm); if (!ccc) return ccc; /* Only add ccc_cb if callback is set */ if (write_callback) ccc_add_cb(database, ccc, write_callback, user_data, destroy); return ccc; } static void cli_feat_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 device_state *state; uint8_t ecode = 0; const uint8_t *value = NULL; size_t len = 0; DBG("Client Features read"); state = get_device_state(database, att); if (!state) { ecode = BT_ATT_ERROR_UNLIKELY; goto done; } len = sizeof(state->cli_feat) - offset; value = len ? &state->cli_feat[offset] : NULL; done: gatt_db_attribute_read_result(attrib, id, ecode, value, len); } static void cli_feat_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 device_state *state; uint8_t bits[] = { BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING, BT_GATT_CHRC_CLI_FEAT_EATT, BT_GATT_CHRC_CLI_FEAT_NFY_MULTI }; uint8_t ecode = 0; unsigned int i; DBG("Client Features write"); state = get_device_state(database, att); if (!state) { ecode = BT_ATT_ERROR_UNLIKELY; goto done; } if (!value || !len) { ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; goto done; } for (i = 0; i < sizeof(bits); i++) { /* A client shall never clear a bit it has set */ if (state->cli_feat[0] & (1 << i) && !(value[0] & (1 << i))) { ecode = BT_ATT_ERROR_VALUE_NOT_ALLOWED; goto done; } } /* Shall we reallocate the feat array if bigger? */ len = MIN(sizeof(state->cli_feat), len); while (len) { state->cli_feat[len - 1] |= value[len - 1]; len--; } state->cli_feat[0] &= ((1 << sizeof(bits)) - 1); state->change_aware = true; done: gatt_db_attribute_write_result(attrib, id, ecode); } static void db_hash_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; const uint8_t *hash; struct device_state *state; bdaddr_t bdaddr; uint8_t bdaddr_type; DBG("Database Hash read"); hash = gatt_db_get_hash(database->db); gatt_db_attribute_read_result(attrib, id, 0, hash, 16); if (!get_dst_info(att, &bdaddr, &bdaddr_type)) return; state = find_device_state(database, &bdaddr, bdaddr_type); if (state) state->change_aware = true; } static void server_feat_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 device_state *state; uint8_t ecode = 0; uint8_t value = 0; state = get_device_state(database, att); if (!state) { ecode = BT_ATT_ERROR_UNLIKELY; goto done; } value |= BT_GATT_CHRC_SERVER_FEAT_EATT; done: gatt_db_attribute_read_result(attrib, id, ecode, &value, sizeof(value)); } 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, 10); bt_uuid16_create(&uuid, GATT_CHARAC_SERVICE_CHANGED); database->svc_chngd = gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_NONE, BT_GATT_CHRC_PROP_INDICATE, NULL, NULL, database); database->svc_chngd_ccc = service_add_ccc(service, database, NULL, NULL, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, NULL); bt_uuid16_create(&uuid, GATT_CHARAC_CLI_FEAT); database->cli_feat = gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_WRITE, cli_feat_read_cb, cli_feat_write_cb, database); gatt_db_attribute_set_fixed_length(database->cli_feat, CLI_FEAT_SIZE); /* Only expose database hash chrc if supported */ if (gatt_db_hash_support(database->db)) { bt_uuid16_create(&uuid, GATT_CHARAC_DB_HASH); database->db_hash = gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, db_hash_read_cb, NULL, database); gatt_db_attribute_set_fixed_length(database->db_hash, 16); } /* Only enable EATT if there is a socket listening */ if (database->eatt_io) { bt_uuid16_create(&uuid, GATT_CHARAC_SERVER_FEAT); database->eatt = gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, server_feat_read_cb, NULL, database); gatt_db_attribute_set_fixed_length(database->eatt, 1); } gatt_db_service_set_active(service, true); database_add_record(database, service); } static void device_info_read_pnp_id_cb(struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset, uint8_t opcode, struct bt_att *att, void *user_data) { uint8_t pdu[7]; pdu[0] = btd_opts.did_source; put_le16(btd_opts.did_vendor, &pdu[1]); put_le16(btd_opts.did_product, &pdu[3]); put_le16(btd_opts.did_version, &pdu[5]); gatt_db_attribute_read_result(attrib, id, 0, pdu, sizeof(pdu)); } static void populate_devinfo_service(struct btd_gatt_database *database) { struct gatt_db_attribute *service; struct gatt_db_attribute *attrib; bt_uuid_t uuid; if (!btd_opts.did_source) return; bt_uuid16_create(&uuid, UUID_DIS); service = gatt_db_add_service(database->db, &uuid, true, 3); bt_uuid16_create(&uuid, GATT_CHARAC_PNP_ID); attrib = gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, device_info_read_pnp_id_cb, NULL, database); gatt_db_attribute_set_fixed_length(attrib, 7); gatt_db_service_set_active(service, true); database_add_record(database, service); } 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) { struct device_state *state = user_data; DBG(""); if (!state) return; state->change_aware = true; } 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; if (notify->conf == service_changed_conf) { if (device_state->cli_feat[0] & BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING) { device_state->change_aware = false; notify->user_data = device_state; } } ccc = find_ccc_state(device_state, notify->ccc_handle); if (!ccc || !(ccc->value & 0x0003)) return; device = btd_adapter_find_device(notify->database->adapter, &device_state->bdaddr, device_state->bdaddr_type); if (!device) { /* If ATT has not disconnect yet don't remove the state as it * will eventually be removed when att_disconnected is called. */ if (device_state->disc_id) return; 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 (!(ccc->value & 0x0002)) { DBG("GATT server sending notification"); bt_gatt_server_send_notification(server, notify->handle, notify->value, notify->len, device_state->cli_feat[0] & BT_GATT_CHRC_CLI_FEAT_NFY_MULTI); 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 gatt_notify_cb(struct gatt_db_attribute *attrib, struct gatt_db_attribute *ccc, const uint8_t *value, size_t len, struct bt_att *att, void *user_data) { struct btd_gatt_database *database = user_data; struct notify notify; memset(¬ify, 0, sizeof(notify)); notify.database = database; notify.handle = gatt_db_attribute_get_handle(attrib); notify.ccc_handle = gatt_db_attribute_get_handle(ccc); notify.value = (void *) value; notify.len = len; if (attrib == database->svc_chngd) notify.conf = service_changed_conf; /* If a specific att is provided notify only to that device */ if (att) { struct device_state *state; state = find_device_state_by_att(database, att); if (!state) return; send_notification_to_device(state, ¬ify); } else queue_foreach(database->device_states, send_notification_to_device, ¬ify); } static void register_core_services(struct btd_gatt_database *database) { gatt_db_ccc_register(database->db, gatt_ccc_read_cb, gatt_ccc_write_cb, gatt_notify_cb, database); populate_gap_service(database); populate_gatt_service(database); populate_devinfo_service(database); } 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(¬ify, 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, ¬ify); } 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); if (!gatt_db_attribute_notify(database->svc_chngd, value, sizeof(value), NULL)) error("Failed to notify Service Changed"); } static void database_store(struct btd_gatt_database *database) { char filename[PATH_MAX]; create_filename(filename, PATH_MAX, "/%s/attributes", btd_adapter_get_storage_dir(database->adapter)); create_file(filename, 0600); btd_settings_gatt_db_store(database->db, filename); } 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); database_store(database); } 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, uint32_t *ccc_perm, bool *req_prep_authorization) { const char *flag; if (!props || !ext_props || !perm || !ccc_perm) return false; *props = 0; *ext_props = 0; *perm = 0; *ccc_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("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; *ccc_perm |= BT_ATT_PERM_WRITE; } else if (!strcmp("indicate", flag)) { *props |= BT_GATT_CHRC_PROP_INDICATE; *ccc_perm |= BT_ATT_PERM_WRITE; } else if (!strcmp("authenticated-signed-writes", flag)) { *props |= BT_GATT_CHRC_PROP_AUTH; *perm |= BT_ATT_PERM_WRITE; } else if (!strcmp("extended-properties", flag)) { *props |= BT_GATT_CHRC_PROP_EXT_PROP; } 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; *perm |= BT_ATT_PERM_READ | BT_ATT_PERM_READ_ENCRYPT; } else if (!strcmp("encrypt-write", flag)) { *props |= BT_GATT_CHRC_PROP_WRITE; *perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_ENCRYPT; } else if (!strcmp("encrypt-authenticated-read", flag)) { *props |= BT_GATT_CHRC_PROP_READ; *perm |= BT_ATT_PERM_READ | BT_ATT_PERM_READ_AUTHEN; } else if (!strcmp("encrypt-authenticated-write", flag)) { *props |= BT_GATT_CHRC_PROP_WRITE; *perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_AUTHEN; } else if (!strcmp("secure-read", flag)) { *props |= BT_GATT_CHRC_PROP_READ; *perm |= BT_ATT_PERM_READ | BT_ATT_PERM_READ_SECURE; } else if (!strcmp("secure-write", flag)) { *props |= BT_GATT_CHRC_PROP_WRITE; *perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_SECURE; } else if (!strcmp("authorize", flag)) { *req_prep_authorization = true; } else if (!strcmp("encrypt-notify", flag)) { *ccc_perm |= BT_ATT_PERM_WRITE_ENCRYPT; *props |= BT_GATT_CHRC_PROP_NOTIFY; } else if (!strcmp("encrypt-authenticated-notify", flag)) { *ccc_perm |= BT_ATT_PERM_WRITE_AUTHEN; *props |= BT_GATT_CHRC_PROP_NOTIFY; } else if (!strcmp("secure-notify", flag)) { *ccc_perm |= BT_ATT_PERM_WRITE_SECURE; *props |= BT_GATT_CHRC_PROP_NOTIFY; } else if (!strcmp("encrypt-indicate", flag)) { *ccc_perm |= BT_ATT_PERM_WRITE_ENCRYPT; *props |= BT_GATT_CHRC_PROP_INDICATE; } else if (!strcmp("encrypt-authenticated-indicate", flag)) { *ccc_perm |= BT_ATT_PERM_WRITE_AUTHEN; *props |= BT_GATT_CHRC_PROP_INDICATE; } else if (!strcmp("secure-indicate", flag)) { *ccc_perm |= BT_ATT_PERM_WRITE_SECURE; *props |= BT_GATT_CHRC_PROP_INDICATE; } 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, uint32_t *ccc_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, ccc_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->ccc_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, NULL, &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; char *includes; int type; /* 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); while ((type = dbus_message_iter_get_arg_type(&array)) != DBUS_TYPE_INVALID) { if (type != DBUS_TYPE_OBJECT_PATH) return false; dbus_message_iter_get_basic(&array, &obj); includes = g_strdup(obj); if (!includes) return false; if (!queue_push_tail(service->includes, includes)) { error("Failed to add Includes path in queue\n"); return false; } incr_attr_count(service, 1); 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 bool parse_handle(GDBusProxy *proxy, uint16_t *handle) { DBusMessageIter iter; *handle = 0; /* Handle property is optional */ if (!g_dbus_proxy_get_property(proxy, "Handle", &iter)) return true; if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT16) return false; dbus_message_iter_get_basic(&iter, handle); return true; } static uint8_t dbus_error_to_att_ecode(const char *name, const char *msg, uint8_t perm_err) { if (strcmp(name, ERROR_INTERFACE ".Failed") == 0) { char *endptr = NULL; uint32_t ecode; ecode = strtol(msg, &endptr, 0); /* If message doesn't set an error code just use 0x80 */ if (!endptr || *endptr != '\0') return 0x80; if (ecode < 0x80 || ecode > 0x9f) { error("Invalid error code: %s", msg); return BT_ATT_ERROR_UNLIKELY; } return ecode; } if (strcmp(name, ERROR_INTERFACE ".NotSupported") == 0) return BT_ATT_ERROR_REQUEST_NOT_SUPPORTED; if (strcmp(name, ERROR_INTERFACE ".NotAuthorized") == 0) return BT_ATT_ERROR_AUTHORIZATION; if (strcmp(name, ERROR_INTERFACE ".InvalidValueLength") == 0) return BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; if (strcmp(name, ERROR_INTERFACE ".InvalidOffset") == 0) return BT_ATT_ERROR_INVALID_OFFSET; if (strcmp(name, ERROR_INTERFACE ".InProgress") == 0) return BT_ERROR_ALREADY_IN_PROGRESS; if (strcmp(name, ERROR_INTERFACE ".NotPermitted") == 0) return perm_err; return BT_ATT_ERROR_UNLIKELY; } 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, err.message, 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 bt_att *att, struct queue *owner_queue, struct gatt_db_attribute *attrib, unsigned int id, uint16_t offset) { struct pending_op *op; op = new0(struct pending_op, 1); op->owner_queue = owner_queue; op->att = bt_att_ref(att); op->attrib = attrib; op->id = id; op->offset = offset; op->link_type = bt_att_get_link_type(att); queue_push_tail(owner_queue, op); op->disconn_id = bt_att_register_disconnect(att, pending_disconnect_cb, op, NULL); return op; } static void append_options(DBusMessageIter *iter, void *user_data) { struct pending_op *op = user_data; struct btd_device *device = att_get_device(op->att); const char *path = device_get_path(device); struct bt_gatt_server *server; const char *link; uint16_t mtu; switch (op->link_type) { case BT_ATT_BREDR: link = "BR/EDR"; break; case BT_ATT_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); server = btd_device_get_gatt_server(device); mtu = bt_gatt_server_get_mtu(server); dict_append_entry(iter, "mtu", DBUS_TYPE_UINT16, &mtu); } 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 bt_att *att, struct gatt_db_attribute *attrib, GDBusProxy *proxy, struct queue *owner_queue, unsigned int id, uint16_t offset) { struct pending_op *op; op = pending_read_new(att, owner_queue, attrib, id, offset); 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, err.message, 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 bt_att *att, struct queue *owner_queue, struct gatt_db_attribute *attrib, unsigned int id, const uint8_t *value, size_t len, uint16_t offset, 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->att = bt_att_ref(att); op->owner_queue = owner_queue; op->attrib = attrib; op->id = id; op->offset = offset; op->link_type = bt_att_get_link_type(att); op->is_characteristic = is_characteristic; op->prep_authorize = prep_authorize; queue_push_tail(owner_queue, op); op->disconn_id = bt_att_register_disconnect(att, pending_disconnect_cb, op, NULL); return op; } static struct pending_op *send_write(struct bt_att *att, struct gatt_db_attribute *attrib, GDBusProxy *proxy, struct queue *owner_queue, unsigned int id, const uint8_t *value, size_t len, uint16_t offset, bool is_characteristic, bool prep_authorize) { struct pending_op *op; op = pending_write_new(att, owner_queue, attrib, id, value, len, offset, 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 void flush_pending_write(void *data, void *user_data) { GDBusProxy *proxy = user_data; struct pending_op *op = data; if (g_dbus_proxy_method_call(proxy, "WriteValue", write_setup_cb, write_reply_cb, op, pending_op_free) == TRUE) return; pending_op_free(op); } static void flush_pending_writes(GDBusProxy *proxy, struct queue *owner_queue) { queue_foreach(owner_queue, flush_pending_write, proxy); queue_remove_all(owner_queue, NULL, NULL, NULL); } static bool sock_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 sock_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), conf_cb, chrc->proxy); return true; } static struct io *sock_io_new(int fd, void *user_data) { struct io *io; io = io_new(fd); io_set_close_on_destroy(io, true); io_set_disconnect_handler(io, sock_hup, user_data, NULL); return io; } static int sock_io_send(struct io *io, const void *data, size_t len) { struct msghdr msg; struct iovec iov; iov.iov_base = (void *) data; iov.iov_len = len; memset(&msg, 0, sizeof(msg)); msg.msg_iov = &iov; msg.msg_iovlen = 1; return sendmsg(io_get_fd(io), &msg, MSG_NOSIGNAL); } 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; if (!op->owner_queue) { DBG("Pending write was canceled when object got removed"); return; } chrc = gatt_db_attribute_get_user_data(op->attrib); dbus_error_init(&err); if (dbus_set_error_from_message(&err, message) == TRUE) { uint8_t ecode; error("Failed to acquire write: %s\n", err.name); ecode = dbus_error_to_att_ecode(err.name, err.message, BT_ATT_ERROR_WRITE_NOT_PERMITTED); dbus_error_free(&err); if (ecode != BT_ATT_ERROR_UNLIKELY) { gatt_db_attribute_write_result(op->attrib, op->id, ecode); return; } 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 = sock_io_new(fd, chrc); while ((op = queue_peek_head(chrc->pending_writes)) != NULL) { if (sock_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); pending_op_free(op); } return; retry: flush_pending_writes(chrc->proxy, chrc->pending_writes); } static void acquire_write_setup(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 *acquire_write(struct external_chrc *chrc, struct bt_att *att, struct gatt_db_attribute *attrib, unsigned int id, const uint8_t *value, size_t len) { struct pending_op *op; bool acquiring = !queue_isempty(chrc->pending_writes); op = pending_write_new(att, chrc->pending_writes, attrib, id, value, len, 0, false, false); if (acquiring) return op; if (g_dbus_proxy_method_call(chrc->proxy, "AcquireWrite", acquire_write_setup, acquire_write_reply, op, NULL)) 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; if (!op->owner_queue) { DBG("Pending notify was canceled when object got removed"); return; } dbus_error_init(&err); if (dbus_set_error_from_message(&err, message) == TRUE) { error("Failed to acquire notify: %s\n", err.name); if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY) || dbus_error_has_name(&err, ERROR_FAILED)) { dbus_error_free(&err); return; } 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 = sock_io_new(fd, chrc); io_set_read_handler(chrc->notify_io, sock_io_read, chrc, NULL); __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; 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 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); op->owner_queue = chrc->pending_writes; 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 (iter) { 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), conf_cb, 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; /* Always set read/write permissions */ chrc->ccc_perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_READ; chrc->ccc = service_add_ccc(service->attrib, service->app->database, ccc_write_cb, chrc, chrc->ccc_perm, 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(att, attrib, desc->proxy, desc->pending_reads, id, offset)) 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 (!btd_device_is_trusted(device) && !desc->prep_authorized && desc->req_prep_authorization) send_write(att, attrib, desc->proxy, desc->pending_writes, id, value, len, offset, 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(att, attrib, desc->proxy, desc->pending_writes, id, value, len, offset, false, false)) return; fail: gatt_db_attribute_write_result(attrib, id, BT_ATT_ERROR_UNLIKELY); } static void write_handle(struct GDBusProxy *proxy, uint16_t handle) { DBusMessageIter iter; /* Check if the attribute has the Handle property */ if (!g_dbus_proxy_get_property(proxy, "Handle", &iter)) return; g_dbus_proxy_set_property_basic(proxy, "Handle", DBUS_TYPE_UINT16, &handle, NULL, NULL, NULL); } static bool database_add_desc(struct external_service *service, struct external_desc *desc) { uint16_t handle; bt_uuid_t uuid; char str[MAX_LEN_UUID_STR]; if (!parse_handle(desc->proxy, &handle)) { error("Failed to read \"Handle\" property of descriptor"); return false; } if (!parse_uuid(desc->proxy, &uuid)) { error("Failed to read \"UUID\" property of descriptor"); return false; } desc->attrib = gatt_db_service_insert_descriptor(service->attrib, handle, &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; if (!handle) { handle = gatt_db_attribute_get_handle(desc->attrib); write_handle(desc->proxy, handle); } bt_uuid_to_string(&uuid, str, sizeof(str)); DBG("handle 0x%04x UUID %s", handle, str); 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(att, attrib, chrc->proxy, chrc->pending_reads, id, offset)) 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 (!btd_device_is_trusted(device) && !chrc->prep_authorized && chrc->req_prep_authorization) send_write(att, attrib, chrc->proxy, queue, id, value, len, offset, 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 (sock_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, att, attrib, id, value, len)) return; } if (send_write(att, attrib, chrc->proxy, queue, id, value, len, offset, 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) { uint16_t handle; bt_uuid_t uuid; char str[MAX_LEN_UUID_STR]; const struct queue_entry *entry; if (!parse_handle(chrc->proxy, &handle)) { error("Failed to read \"Handle\" property of characteristic"); return false; } 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_insert_characteristic(service->attrib, handle, &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; if (!handle) { handle = gatt_db_attribute_get_handle(chrc->attrib); write_handle(chrc->proxy, handle); } bt_uuid_to_string(&uuid, str, sizeof(str)); DBG("handle 0x%04x UUID %s", handle, str); /* Handle the descriptors that belong to this characteristic. */ for (entry = queue_get_entries(service->descs); entry; entry = entry->next) { struct external_desc *desc = entry->data; if (desc->handled || g_strcmp0(desc->chrc_path, chrc->path)) continue; if (!database_add_desc(service, desc)) { chrc->attrib = NULL; error("Failed to create descriptor entry"); return false; } } return true; } static bool match_desc_unhandled(const void *a, const void *b) { const struct external_desc *desc = a; return !desc->handled; } static bool database_add_service(struct external_service *service) { bt_uuid_t uuid; bool primary; uint16_t handle; const struct queue_entry *entry; char str[MAX_LEN_UUID_STR]; if (!parse_uuid(service->proxy, &uuid)) { error("Failed to read \"UUID\" property of service"); return false; } if (!parse_primary(service->proxy, &primary)) { error("Failed to read \"Primary\" property of service"); return false; } if (!parse_includes(service->proxy, service)) { error("Failed to read \"Includes\" property of service"); return false; } if (!parse_handle(service->proxy, &handle)) { error("Failed to read \"Handle\" property of service"); return false; } service->attrib = gatt_db_insert_service(service->app->database->db, handle, &uuid, primary, service->attr_cnt); if (!service->attrib) return false; if (!handle) { handle = gatt_db_attribute_get_handle(service->attrib); write_handle(service->proxy, handle); } bt_uuid_to_string(&uuid, str, sizeof(str)); DBG("handle 0x%04x UUID %s", handle, str); database_add_includes(service); entry = queue_get_entries(service->chrcs); while (entry) { struct external_chrc *chrc = entry->data; if (!database_add_chrc(service, chrc)) { error("Failed to add characteristic"); goto fail; } entry = entry->next; } /* If there are any unhandled descriptors, return an error */ if (queue_find(service->descs, match_desc_unhandled, NULL)) { error("Found descriptor with no matching characteristic!"); goto fail; } gatt_db_service_set_active(service->attrib, true); return true; fail: gatt_db_remove_service(service->app->database->db, service->attrib); service->attrib = NULL; return false; } static bool database_add_app(struct gatt_app *app) { const struct queue_entry *entry; entry = queue_get_entries(app->services); while (entry) { if (!database_add_service(entry->data)) { error("Failed to add service"); return false; } entry = entry->next; } return true; } static int profile_device_probe(struct btd_service *service) { struct btd_profile *p = btd_service_get_profile(service); DBG("%s probed", p->name); return 0; } static void profile_device_remove(struct btd_service *service) { struct btd_profile *p = btd_service_get_profile(service); DBG("%s removed", p->name); } static int profile_device_accept(struct btd_service *service) { struct btd_profile *p = btd_service_get_profile(service); DBG("%s accept", p->name); return 0; } static int profile_add(struct external_profile *profile, const char *uuid) { struct btd_profile *p; p = new0(struct btd_profile, 1); /* Assign directly to avoid having extra fields */ p->name = (const void *) g_strdup_printf("%s%s/%s", profile->app->owner, g_dbus_proxy_get_path(profile->proxy), uuid); if (!p->name) { free(p); return -ENOMEM; } p->remote_uuid = (const void *) g_strdup(uuid); if (!p->remote_uuid) { g_free((void *) p->name); free(p); return -ENOMEM; } p->device_probe = profile_device_probe; p->device_remove = profile_device_remove; p->accept = profile_device_accept; p->auto_connect = true; p->external = true; queue_push_tail(profile->profiles, p); DBG("Added \"%s\"", p->name); return 0; } static void add_profile(void *data, void *user_data) { struct btd_adapter *adapter = user_data; btd_profile_register(data); adapter_add_profile(adapter, data); } static struct external_profile *create_profile(struct gatt_app *app, GDBusProxy *proxy, const char *path) { struct external_profile *profile; DBusMessageIter iter, array; if (!path || !g_str_has_prefix(path, "/")) return NULL; profile = new0(struct external_profile, 1); profile->app = app; profile->proxy = g_dbus_proxy_ref(proxy); profile->profiles = queue_new(); if (!g_dbus_proxy_get_property(proxy, "UUIDs", &iter)) { DBG("UUIDs property not found"); goto fail; } if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { DBG("UUIDs wrongly formatted"); goto fail; } dbus_message_iter_recurse(&iter, &array); while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) { const char *uuid; dbus_message_iter_get_basic(&array, &uuid); if (profile_add(profile, uuid) < 0) goto fail; dbus_message_iter_next(&array); } if (queue_isempty(profile->profiles)) goto fail; queue_foreach(profile->profiles, add_profile, app->database->adapter); queue_push_tail(app->profiles, profile); return profile; fail: profile_free(profile); return NULL; } static void register_profile(void *data, void *user_data) { struct gatt_app *app = user_data; GDBusProxy *proxy = data; const char *iface = g_dbus_proxy_get_interface(proxy); const char *path = g_dbus_proxy_get_path(proxy); if (app->failed) return; if (g_strcmp0(iface, GATT_PROFILE_IFACE) == 0) { struct external_profile *profile; profile = create_profile(app, proxy, path); if (!profile) { app->failed = true; return; } } } static void register_service(void *data, void *user_data) { struct gatt_app *app = user_data; GDBusProxy *proxy = data; const char *iface = g_dbus_proxy_get_interface(proxy); const char *path = g_dbus_proxy_get_path(proxy); if (app->failed) return; if (g_strcmp0(iface, GATT_SERVICE_IFACE) == 0) { struct external_service *service; service = create_service(app, proxy, path); if (!service) { app->failed = true; return; } } } static void register_characteristic(void *data, void *user_data) { struct gatt_app *app = user_data; GDBusProxy *proxy = data; const char *iface; const char *path; if (app->failed) return; iface = g_dbus_proxy_get_interface(proxy); path = g_dbus_proxy_get_path(proxy); if (g_strcmp0(iface, GATT_CHRC_IFACE) == 0) { struct external_chrc *chrc; chrc = chrc_create(app, proxy, path); if (!chrc) { app->failed = true; return; } } } static void register_descriptor(void *data, void *user_data) { struct gatt_app *app = user_data; GDBusProxy *proxy = data; const char *iface = g_dbus_proxy_get_interface(proxy); if (app->failed) return; if (g_strcmp0(iface, GATT_DESC_IFACE) == 0) { struct external_desc *desc; desc = desc_create(app, proxy); if (!desc) { app->failed = true; return; } } } static void client_ready_cb(GDBusClient *client, void *user_data) { struct gatt_app *app = user_data; DBusMessage *reply; bool fail = false; /* * Process received objects */ if (queue_isempty(app->proxies)) { error("No object received"); fail = true; reply = btd_error_failed(app->reg, "No object received"); goto reply; } queue_foreach(app->proxies, register_profile, app); queue_foreach(app->proxies, register_service, app); queue_foreach(app->proxies, register_characteristic, app); queue_foreach(app->proxies, register_descriptor, app); if ((queue_isempty(app->services) && queue_isempty(app->profiles)) || app->failed) { error("No valid external GATT objects found"); fail = true; reply = btd_error_failed(app->reg, "No valid service object found"); goto reply; } if (!database_add_app(app)) { error("Failed to create GATT service entry in local database"); fail = true; reply = btd_error_failed(app->reg, "Failed to create entry in database"); goto reply; } DBG("GATT application registered: %s:%s", app->owner, app->path); reply = dbus_message_new_method_return(app->reg); reply: g_dbus_send_message(btd_get_dbus_connection(), reply); dbus_message_unref(app->reg); app->reg = NULL; if (fail) remove_app(app); } static struct gatt_app *create_app(DBusConnection *conn, DBusMessage *msg, const char *path) { struct gatt_app *app; const char *sender = dbus_message_get_sender(msg); if (!path || !g_str_has_prefix(path, "/")) return NULL; app = new0(struct gatt_app, 1); app->client = g_dbus_client_new_full(conn, sender, path, path); if (!app->client) goto fail; app->owner = g_strdup(sender); if (!app->owner) goto fail; app->path = g_strdup(path); if (!app->path) goto fail; app->services = queue_new(); app->profiles = queue_new(); app->proxies = queue_new(); app->reg = dbus_message_ref(msg); g_dbus_client_set_disconnect_watch(app->client, client_disconnect_cb, app); g_dbus_client_set_proxy_handlers(app->client, proxy_added_cb, proxy_removed_cb, NULL, app); g_dbus_client_set_ready_watch(app->client, client_ready_cb, app); return app; fail: app_free(app); return NULL; } static DBusMessage *manager_register_app(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_gatt_database *database = user_data; const char *sender = dbus_message_get_sender(msg); DBusMessageIter args; const char *path; struct gatt_app *app; struct svc_match_data match_data; if (!dbus_message_iter_init(msg, &args)) return btd_error_invalid_args(msg); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH) return btd_error_invalid_args(msg); dbus_message_iter_get_basic(&args, &path); match_data.path = path; match_data.sender = sender; if (queue_find(database->apps, match_app, &match_data)) return btd_error_already_exists(msg); dbus_message_iter_next(&args); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY) return btd_error_invalid_args(msg); app = create_app(conn, msg, path); if (!app) return btd_error_failed(msg, "Failed to register application"); DBG("Registering application: %s:%s", sender, path); app->database = database; queue_push_tail(database->apps, app); return NULL; } static DBusMessage *manager_unregister_app(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_gatt_database *database = user_data; const char *sender = dbus_message_get_sender(msg); const char *path; DBusMessageIter args; struct gatt_app *app; struct svc_match_data match_data; if (!dbus_message_iter_init(msg, &args)) return btd_error_invalid_args(msg); if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH) return btd_error_invalid_args(msg); dbus_message_iter_get_basic(&args, &path); match_data.path = path; match_data.sender = sender; app = queue_remove_if(database->apps, match_app, &match_data); if (!app) return btd_error_does_not_exist(msg); app_free(app); return dbus_message_new_method_return(msg); } static const GDBusMethodTable manager_methods[] = { { GDBUS_ASYNC_METHOD("RegisterApplication", GDBUS_ARGS({ "application", "o" }, { "options", "a{sv}" }), NULL, manager_register_app) }, { GDBUS_ASYNC_METHOD("UnregisterApplication", GDBUS_ARGS({ "application", "o" }), NULL, manager_unregister_app) }, { } }; static uint8_t server_authorize(struct bt_att *att, uint8_t opcode, uint16_t handle, void *user_data) { struct btd_gatt_database *database = user_data; struct device_state *state; bdaddr_t bdaddr; uint8_t bdaddr_type; if (!get_dst_info(att, &bdaddr, &bdaddr_type)) return 0; /* Skip if there is no device state */ state = find_device_state(database, &bdaddr, bdaddr_type); if (!state) return 0; /* Skip if client doesn't support Robust Caching */ if (!(state->cli_feat[0] & BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING)) return 0; if (state->change_aware) return 0; if (state->out_of_sync) { state->out_of_sync = false; state->change_aware = true; return 0; } state->out_of_sync = true; return BT_ATT_ERROR_DB_OUT_OF_SYNC; } static void eatt_confirm_cb(GIOChannel *io, gpointer data) { char address[18]; uint8_t dst_type; bdaddr_t src, dst; GError *gerr = NULL; struct btd_device *device; struct bt_gatt_server *server; struct bt_att *att; 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_DEST, address, BT_IO_OPT_INVALID); if (gerr) { error("bt_io_get: %s", gerr->message); g_error_free(gerr); goto drop; } DBG("New incoming EATT connection"); /* Confirm the device exists before accepting the connection, if the * device is using an RPA it could be that the MGMT event has not been * processed yet which would lead to create a second copy of the same * device using its identity address. */ device = btd_adapter_find_device(adapter_find(&src), &dst, dst_type); if (!device) { error("Unable to find device: %s", address); goto drop; } /* Only allow EATT connection from central */ if (btd_device_is_initiator(device)) { warn("EATT connection from peripheral may cause collisions"); goto drop; } server = btd_device_get_gatt_server(device); if (!server) { error("Unable to resolve bt_server"); goto drop; } att = bt_gatt_server_get_att(server); if (bt_att_get_channels(att) == btd_opts.gatt_channels) { DBG("EATT channel limit reached"); goto drop; } if (!bt_io_accept(io, connect_cb, NULL, NULL, &gerr)) { error("bt_io_accept: %s", gerr->message); g_error_free(gerr); goto drop; } return; drop: g_io_channel_shutdown(io, TRUE, NULL); } struct btd_gatt_database *btd_gatt_database_new(struct btd_adapter *adapter) { struct btd_gatt_database *database; GError *gerr = NULL; const bdaddr_t *addr; if (!adapter) return NULL; database = new0(struct btd_gatt_database, 1); database->adapter = btd_adapter_ref(adapter); database->db = gatt_db_new(); database->records = queue_new(); database->device_states = queue_new(); database->apps = queue_new(); database->profiles = queue_new(); database->ccc_callbacks = queue_new(); addr = btd_adapter_get_address(adapter); database->le_io = bt_io_listen(connect_cb, NULL, NULL, NULL, &gerr, BT_IO_OPT_SOURCE_BDADDR, addr, BT_IO_OPT_SOURCE_TYPE, btd_adapter_get_address_type(adapter), BT_IO_OPT_CID, BT_ATT_CID, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, BT_IO_OPT_INVALID); if (!database->le_io) { error("Failed to start listening: %s", gerr->message); g_error_free(gerr); goto fail; } /* If just just 1 channel is enabled EATT is not required */ if (btd_opts.gatt_channels == 1) goto bredr; /* EATT socket, encryption is required */ database->eatt_io = bt_io_listen(NULL, eatt_confirm_cb, NULL, NULL, &gerr, BT_IO_OPT_SOURCE_BDADDR, addr, BT_IO_OPT_SOURCE_TYPE, btd_adapter_get_address_type(adapter), BT_IO_OPT_PSM, BT_ATT_EATT_PSM, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, BT_IO_OPT_MTU, btd_opts.gatt_mtu, BT_IO_OPT_INVALID); if (!database->eatt_io) { g_error_free(gerr); goto fail; } bredr: /* BR/EDR socket */ database->bredr_io = bt_io_listen(connect_cb, NULL, NULL, NULL, &gerr, BT_IO_OPT_SOURCE_BDADDR, addr, BT_IO_OPT_PSM, BT_ATT_PSM, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, BT_IO_OPT_MTU, btd_opts.gatt_mtu, BT_IO_OPT_INVALID); if (database->bredr_io == NULL) { error("Failed to start listening: %s", gerr->message); g_error_free(gerr); goto fail; } if (g_dbus_register_interface(btd_get_dbus_connection(), adapter_get_path(adapter), GATT_MANAGER_IFACE, manager_methods, NULL, NULL, database, NULL)) DBG("GATT Manager registered for adapter: %s", adapter_get_path(adapter)); register_core_services(database); database->db_id = gatt_db_register(database->db, gatt_db_service_added, gatt_db_service_removed, database, NULL); if (!database->db_id) goto fail; return database; fail: gatt_database_free(database); return NULL; } void btd_gatt_database_destroy(struct btd_gatt_database *database) { if (!database) return; g_dbus_unregister_interface(btd_get_dbus_connection(), adapter_get_path(database->adapter), GATT_MANAGER_IFACE); gatt_database_free(database); } struct gatt_db *btd_gatt_database_get_db(struct btd_gatt_database *database) { if (!database) return NULL; return database->db; } void btd_gatt_database_server_connected(struct btd_gatt_database *database, struct bt_gatt_server *server) { struct bt_att *att = bt_gatt_server_get_att(server); struct device_state *state; bdaddr_t bdaddr; uint8_t bdaddr_type; if (!get_dst_info(att, &bdaddr, &bdaddr_type)) return; bt_gatt_server_set_authorize(server, server_authorize, database); state = find_device_state(database, &bdaddr, bdaddr_type); if (!state || !state->pending) return; send_notification_to_device(state, state->pending); state = find_device_state(database, &bdaddr, bdaddr_type); if (!state || !state->pending) return; free(state->pending->value); free(state->pending); state->pending = NULL; } void btd_gatt_database_att_disconnected(struct btd_gatt_database *database, struct btd_device *device) { struct bt_gatt_server *server = btd_device_get_gatt_server(device); struct bt_att *att = bt_gatt_server_get_att(server); struct device_state *state; const bdaddr_t *addr; uint8_t type; DBG(""); addr = device_get_address(device); type = btd_device_get_bdaddr_type(device); state = find_device_state(database, addr, type); if (!state) return; if (state->disc_id) bt_att_unregister_disconnect(att, state->disc_id); att_disconnected(0, state); } static void restore_ccc(struct btd_gatt_database *database, const bdaddr_t *addr, uint8_t addr_type, uint16_t value) { struct device_state *dev_state; struct ccc_state *ccc; dev_state = device_state_create(database, addr, addr_type); queue_push_tail(database->device_states, dev_state); ccc = new0(struct ccc_state, 1); ccc->handle = gatt_db_attribute_get_handle(database->svc_chngd_ccc); ccc->value = value; queue_push_tail(dev_state->ccc_states, ccc); } static void restore_state(struct btd_device *device, void *data) { struct btd_gatt_database *database = data; uint16_t ccc_le, ccc_bredr; device_load_svc_chng_ccc(device, &ccc_le, &ccc_bredr); if (ccc_le) { restore_ccc(database, device_get_address(device), device_get_le_address_type(device), ccc_le); DBG("%s LE", device_get_path(device)); } if (ccc_bredr) { restore_ccc(database, device_get_address(device), BDADDR_BREDR, ccc_bredr); DBG("%s BR/EDR", device_get_path(device)); } } void btd_gatt_database_restore_svc_chng_ccc(struct btd_gatt_database *database) { uint8_t value[4]; uint16_t handle, ccc_handle; if (!database) 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; } /* restore states for bonded device that registered for Service Changed * indication */ btd_adapter_for_each_device(database->adapter, restore_state, database); put_le16(0x0001, value); put_le16(0xffff, value + 2); if (!gatt_db_attribute_notify(database->svc_chngd, value, sizeof(value), NULL)) error("Failed to notify Service Changed"); }