diff options
Diffstat (limited to 'attrib/client.c')
-rw-r--r-- | attrib/client.c | 1116 |
1 files changed, 1116 insertions, 0 deletions
diff --git a/attrib/client.c b/attrib/client.c new file mode 100644 index 0000000..54bdc79 --- /dev/null +++ b/attrib/client.c @@ -0,0 +1,1116 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdlib.h> +#include <glib.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/uuid.h> + +#include "adapter.h" +#include "device.h" +#include "log.h" +#include "gdbus.h" +#include "error.h" +#include "dbus-common.h" +#include "btio.h" +#include "storage.h" + +#include "att.h" +#include "gattrib.h" +#include "gatt.h" +#include "client.h" + +#define CHAR_INTERFACE "org.bluez.Characteristic" + +struct gatt_service { + struct btd_device *dev; + bdaddr_t sba; + bdaddr_t dba; + char *path; + GSList *primary; + GAttrib *attrib; + DBusMessage *msg; + int psm; + gboolean listen; +}; + +struct format { + guint8 format; + guint8 exponent; + guint16 unit; + guint8 namespace; + guint16 desc; +} __attribute__ ((packed)); + +struct primary { + struct gatt_service *gatt; + struct att_primary *att; + char *path; + GSList *chars; + GSList *watchers; +}; + +struct characteristic { + struct primary *prim; + char *path; + uint16_t handle; + uint16_t end; + uint8_t perm; + char type[MAX_LEN_UUID_STR + 1]; + char *name; + char *desc; + struct format *format; + uint8_t *value; + size_t vlen; +}; + +struct query_data { + struct primary *prim; + struct characteristic *chr; + DBusMessage *msg; + uint16_t handle; +}; + +struct watcher { + guint id; + char *name; + char *path; + struct primary *prim; +}; + +static GSList *gatt_services = NULL; + +static DBusConnection *connection; + +static void characteristic_free(void *user_data) +{ + struct characteristic *chr = user_data; + + g_free(chr->path); + g_free(chr->desc); + g_free(chr->format); + g_free(chr->value); + g_free(chr->name); + g_free(chr); +} + +static void watcher_free(void *user_data) +{ + struct watcher *watcher = user_data; + + g_free(watcher->path); + g_free(watcher->name); + g_free(watcher); +} + +static void primary_free(void *user_data) +{ + struct primary *prim = user_data; + GSList *l; + + for (l = prim->watchers; l; l = l->next) { + struct watcher *watcher = l->data; + g_dbus_remove_watch(connection, watcher->id); + } + + g_slist_foreach(prim->chars, (GFunc) characteristic_free, NULL); + g_slist_free(prim->chars); + g_free(prim->path); + g_free(prim); +} + +static void gatt_service_free(void *user_data) +{ + struct gatt_service *gatt = user_data; + + g_slist_foreach(gatt->primary, (GFunc) primary_free, NULL); + g_slist_free(gatt->primary); + g_attrib_unref(gatt->attrib); + g_free(gatt->path); + btd_device_unref(gatt->dev); + g_free(gatt); +} + +static int gatt_dev_cmp(gconstpointer a, gconstpointer b) +{ + const struct gatt_service *gatt = a; + const struct btd_device *dev = b; + + return gatt->dev != dev; +} + +static int characteristic_handle_cmp(gconstpointer a, gconstpointer b) +{ + const struct characteristic *chr = a; + uint16_t handle = GPOINTER_TO_UINT(b); + + return chr->handle - handle; +} + +static int watcher_cmp(gconstpointer a, gconstpointer b) +{ + const struct watcher *watcher = a; + const struct watcher *match = b; + int ret; + + ret = g_strcmp0(watcher->name, match->name); + if (ret != 0) + return ret; + + return g_strcmp0(watcher->path, match->path); +} + +static void append_char_dict(DBusMessageIter *iter, struct characteristic *chr) +{ + DBusMessageIter dict; + const char *name = ""; + char *uuid; + + 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); + + uuid = g_strdup(chr->type); + dict_append_entry(&dict, "UUID", DBUS_TYPE_STRING, &uuid); + g_free(uuid); + + /* FIXME: Translate UUID to name. */ + dict_append_entry(&dict, "Name", DBUS_TYPE_STRING, &name); + + if (chr->desc) + dict_append_entry(&dict, "Description", DBUS_TYPE_STRING, + &chr->desc); + + if (chr->value) + dict_append_array(&dict, "Value", DBUS_TYPE_BYTE, &chr->value, + chr->vlen); + + /* FIXME: Missing Format, Value and Representation */ + + dbus_message_iter_close_container(iter, &dict); +} + +static void watcher_exit(DBusConnection *conn, void *user_data) +{ + struct watcher *watcher = user_data; + struct primary *prim = watcher->prim; + struct gatt_service *gatt = prim->gatt; + + DBG("%s watcher %s exited", prim->path, watcher->name); + + prim->watchers = g_slist_remove(prim->watchers, watcher); + + g_attrib_unref(gatt->attrib); +} + +static int characteristic_set_value(struct characteristic *chr, + const uint8_t *value, size_t vlen) +{ + chr->value = g_try_realloc(chr->value, vlen); + if (chr->value == NULL) + return -ENOMEM; + + memcpy(chr->value, value, vlen); + chr->vlen = vlen; + + return 0; +} + +static void update_watchers(gpointer data, gpointer user_data) +{ + struct watcher *w = data; + struct characteristic *chr = user_data; + DBusMessage *msg; + + msg = dbus_message_new_method_call(w->name, w->path, + "org.bluez.Watcher", "ValueChanged"); + if (msg == NULL) + return; + + dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &chr->path, + DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, + &chr->value, chr->vlen, DBUS_TYPE_INVALID); + + dbus_message_set_no_reply(msg, TRUE); + g_dbus_send_message(connection, msg); +} + +static void events_handler(const uint8_t *pdu, uint16_t len, + gpointer user_data) +{ + struct gatt_service *gatt = user_data; + struct characteristic *chr; + struct primary *prim; + GSList *lprim, *lchr; + uint8_t opdu[ATT_MAX_MTU]; + guint handle; + uint16_t olen; + + if (len < 3) { + DBG("Malformed notification/indication packet (opcode 0x%02x)", + pdu[0]); + return; + } + + handle = att_get_u16(&pdu[1]); + + for (lprim = gatt->primary, prim = NULL, chr = NULL; lprim; + lprim = lprim->next) { + prim = lprim->data; + + lchr = g_slist_find_custom(prim->chars, + GUINT_TO_POINTER(handle), characteristic_handle_cmp); + if (lchr) { + chr = lchr->data; + break; + } + } + + if (chr == NULL) { + DBG("Attribute handle 0x%02x not found", handle); + return; + } + + switch (pdu[0]) { + case ATT_OP_HANDLE_IND: + olen = enc_confirmation(opdu, sizeof(opdu)); + g_attrib_send(gatt->attrib, 0, opdu[0], opdu, olen, + NULL, NULL, NULL); + case ATT_OP_HANDLE_NOTIFY: + if (characteristic_set_value(chr, &pdu[3], len - 3) < 0) + DBG("Can't change Characteristic 0x%02x", handle); + + g_slist_foreach(prim->watchers, update_watchers, chr); + break; + } +} + +static void attrib_destroy(gpointer user_data) +{ + struct gatt_service *gatt = user_data; + + gatt->attrib = NULL; +} + +static void attrib_disconnect(gpointer user_data) +{ + struct gatt_service *gatt = user_data; + + /* Remote initiated disconnection only */ + g_attrib_unref(gatt->attrib); +} + +static void connect_cb(GIOChannel *chan, GError *gerr, gpointer user_data) +{ + struct gatt_service *gatt = user_data; + + if (gerr) { + if (gatt->msg) { + DBusMessage *reply = btd_error_failed(gatt->msg, + gerr->message); + g_dbus_send_message(connection, reply); + } + + error("%s", gerr->message); + goto fail; + } + + if (gatt->attrib == NULL) + return; + + /* Listen mode: used for notification and indication */ + if (gatt->listen == TRUE) { + g_attrib_register(gatt->attrib, + ATT_OP_HANDLE_NOTIFY, + events_handler, gatt, NULL); + g_attrib_register(gatt->attrib, + ATT_OP_HANDLE_IND, + events_handler, gatt, NULL); + return; + } + + return; +fail: + g_attrib_unref(gatt->attrib); +} + +static int l2cap_connect(struct gatt_service *gatt, GError **gerr, + gboolean listen) +{ + GIOChannel *io; + + if (gatt->attrib != NULL) { + gatt->attrib = g_attrib_ref(gatt->attrib); + gatt->listen = listen; + return 0; + } + + /* + * FIXME: If the service doesn't support Client Characteristic + * Configuration it is necessary to poll the server from time + * to time checking for modifications. + */ + if (gatt->psm < 0) + io = bt_io_connect(BT_IO_L2CAP, connect_cb, gatt, NULL, gerr, + BT_IO_OPT_SOURCE_BDADDR, &gatt->sba, + BT_IO_OPT_DEST_BDADDR, &gatt->dba, + BT_IO_OPT_CID, GATT_CID, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + else + io = bt_io_connect(BT_IO_L2CAP, connect_cb, gatt, NULL, gerr, + BT_IO_OPT_SOURCE_BDADDR, &gatt->sba, + BT_IO_OPT_DEST_BDADDR, &gatt->dba, + BT_IO_OPT_PSM, gatt->psm, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + if (!io) + return -1; + + gatt->attrib = g_attrib_new(io); + g_io_channel_unref(io); + gatt->listen = listen; + + g_attrib_set_destroy_function(gatt->attrib, attrib_destroy, gatt); + g_attrib_set_disconnect_function(gatt->attrib, attrib_disconnect, + gatt); + + return 0; +} + +static DBusMessage *register_watcher(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *sender = dbus_message_get_sender(msg); + struct primary *prim = data; + struct watcher *watcher; + GError *gerr = NULL; + char *path; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + if (l2cap_connect(prim->gatt, &gerr, TRUE) < 0) { + DBusMessage *reply = btd_error_failed(msg, gerr->message); + g_error_free(gerr); + return reply; + } + + watcher = g_new0(struct watcher, 1); + watcher->name = g_strdup(sender); + watcher->prim = prim; + watcher->path = g_strdup(path); + watcher->id = g_dbus_add_disconnect_watch(conn, sender, watcher_exit, + watcher, watcher_free); + + prim->watchers = g_slist_append(prim->watchers, watcher); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *unregister_watcher(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *sender = dbus_message_get_sender(msg); + struct primary *prim = data; + struct watcher *watcher, *match; + GSList *l; + char *path; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + match = g_new0(struct watcher, 1); + match->name = g_strdup(sender); + match->path = g_strdup(path); + l = g_slist_find_custom(prim->watchers, match, watcher_cmp); + watcher_free(match); + if (!l) + return btd_error_not_authorized(msg); + + watcher = l->data; + g_dbus_remove_watch(conn, watcher->id); + prim->watchers = g_slist_remove(prim->watchers, watcher); + watcher_free(watcher); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *set_value(DBusConnection *conn, DBusMessage *msg, + DBusMessageIter *iter, struct characteristic *chr) +{ + struct gatt_service *gatt = chr->prim->gatt; + DBusMessageIter sub; + GError *gerr = NULL; + uint8_t *value; + int len; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(iter) != DBUS_TYPE_BYTE) + return btd_error_invalid_args(msg); + + dbus_message_iter_recurse(iter, &sub); + + dbus_message_iter_get_fixed_array(&sub, &value, &len); + + if (l2cap_connect(gatt, &gerr, FALSE) < 0) { + DBusMessage *reply = btd_error_failed(msg, gerr->message); + g_error_free(gerr); + return reply; + } + + gatt_write_cmd(gatt->attrib, chr->handle, value, len, NULL, NULL); + + characteristic_set_value(chr, value, len); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *get_properties(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct characteristic *chr = data; + DBusMessage *reply; + DBusMessageIter iter; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + append_char_dict(&iter, chr); + + return reply; +} + +static DBusMessage *set_property(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct characteristic *chr = data; + DBusMessageIter iter; + DBusMessageIter sub; + const char *property; + + if (!dbus_message_iter_init(msg, &iter)) + return btd_error_invalid_args(msg); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&iter, &property); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) + return btd_error_invalid_args(msg); + + dbus_message_iter_recurse(&iter, &sub); + + if (g_str_equal("Value", property)) + return set_value(conn, msg, &sub, chr); + + return btd_error_invalid_args(msg); +} + +static GDBusMethodTable char_methods[] = { + { "GetProperties", "", "a{sv}", get_properties }, + { "SetProperty", "sv", "", set_property, + G_DBUS_METHOD_FLAG_ASYNC}, + { } +}; + +static char *characteristic_list_to_string(GSList *chars) +{ + GString *characteristics; + GSList *l; + + characteristics = g_string_new(NULL); + + for (l = chars; l; l = l->next) { + struct characteristic *chr = l->data; + char chr_str[64]; + + memset(chr_str, 0, sizeof(chr_str)); + + snprintf(chr_str, sizeof(chr_str), "%04X#%02X#%04X#%s ", + chr->handle, chr->perm, chr->end, chr->type); + + characteristics = g_string_append(characteristics, chr_str); + } + + return g_string_free(characteristics, FALSE); +} + +static void store_characteristics(struct gatt_service *gatt, + struct primary *prim) +{ + char *characteristics; + struct att_primary *att = prim->att; + + characteristics = characteristic_list_to_string(prim->chars); + + write_device_characteristics(&gatt->sba, &gatt->dba, att->start, + characteristics); + + g_free(characteristics); +} + +static void register_characteristics(struct primary *prim) +{ + GSList *lc; + + for (lc = prim->chars; lc; lc = lc->next) { + struct characteristic *chr = lc->data; + g_dbus_register_interface(connection, chr->path, + CHAR_INTERFACE, char_methods, + NULL, NULL, chr, NULL); + DBG("Registered: %s", chr->path); + } +} + +static GSList *string_to_characteristic_list(struct primary *prim, + const char *str) +{ + GSList *l = NULL; + char **chars; + int i; + + if (str == NULL) + return NULL; + + chars = g_strsplit(str, " ", 0); + if (chars == NULL) + return NULL; + + for (i = 0; chars[i]; i++) { + struct characteristic *chr; + int ret; + + chr = g_new0(struct characteristic, 1); + + ret = sscanf(chars[i], "%04hX#%02hhX#%04hX#%s", &chr->handle, + &chr->perm, &chr->end, chr->type); + if (ret < 4) { + g_free(chr); + continue; + } + + chr->prim = prim; + chr->path = g_strdup_printf("%s/characteristic%04x", + prim->path, chr->handle); + + l = g_slist_append(l, chr); + } + + g_strfreev(chars); + + return l; +} + +static void load_characteristics(gpointer data, gpointer user_data) +{ + struct primary *prim = data; + struct att_primary *att = prim->att; + struct gatt_service *gatt = user_data; + GSList *chrs_list; + char *str; + + if (prim->chars) { + DBG("Characteristics already loaded"); + return; + } + + str = read_device_characteristics(&gatt->sba, &gatt->dba, att->start); + if (str == NULL) + return; + + chrs_list = string_to_characteristic_list(prim, str); + + free(str); + + if (chrs_list == NULL) + return; + + prim->chars = chrs_list; + register_characteristics(prim); + + return; +} + +static void store_attribute(struct gatt_service *gatt, uint16_t handle, + uint16_t type, uint8_t *value, gsize len) +{ + bt_uuid_t uuid; + char *str, *tmp; + guint i; + + str = g_malloc0(MAX_LEN_UUID_STR + len * 2 + 1); + + bt_uuid16_create(&uuid, type); + bt_uuid_to_string(&uuid, str, MAX_LEN_UUID_STR); + + str[MAX_LEN_UUID_STR - 1] = '#'; + + for (i = 0, tmp = str + MAX_LEN_UUID_STR; i < len; i++, tmp += 2) + sprintf(tmp, "%02X", value[i]); + + write_device_attribute(&gatt->sba, &gatt->dba, handle, str); + g_free(str); +} + +static void update_char_desc(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + struct query_data *current = user_data; + struct gatt_service *gatt = current->prim->gatt; + struct characteristic *chr = current->chr; + + if (status == 0) { + + g_free(chr->desc); + + chr->desc = g_malloc(len); + memcpy(chr->desc, pdu + 1, len - 1); + chr->desc[len - 1] = '\0'; + + store_attribute(gatt, current->handle, + GATT_CHARAC_USER_DESC_UUID, + (void *) chr->desc, len); + } else if (status == ATT_ECODE_INSUFF_ENC) { + GIOChannel *io = g_attrib_get_channel(gatt->attrib); + + if (bt_io_set(io, BT_IO_L2CAP, NULL, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_HIGH, + BT_IO_OPT_INVALID)) { + gatt_read_char(gatt->attrib, current->handle, 0, + update_char_desc, current); + return; + } + } + + g_attrib_unref(gatt->attrib); + g_free(current); +} + +static void update_char_format(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + struct query_data *current = user_data; + struct gatt_service *gatt = current->prim->gatt; + struct characteristic *chr = current->chr; + + if (status != 0) + goto done; + + if (len < 8) + goto done; + + g_free(chr->format); + + chr->format = g_new0(struct format, 1); + memcpy(chr->format, pdu + 1, 7); + + store_attribute(gatt, current->handle, GATT_CHARAC_FMT_UUID, + (void *) chr->format, sizeof(*chr->format)); + +done: + g_attrib_unref(gatt->attrib); + g_free(current); +} + +static void update_char_value(guint8 status, const guint8 *pdu, + guint16 len, gpointer user_data) +{ + struct query_data *current = user_data; + struct gatt_service *gatt = current->prim->gatt; + struct characteristic *chr = current->chr; + + if (status == 0) + characteristic_set_value(chr, pdu + 1, len - 1); + else if (status == ATT_ECODE_INSUFF_ENC) { + GIOChannel *io = g_attrib_get_channel(gatt->attrib); + + if (bt_io_set(io, BT_IO_L2CAP, NULL, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_HIGH, + BT_IO_OPT_INVALID)) { + gatt_read_char(gatt->attrib, chr->handle, 0, + update_char_value, current); + return; + } + } + + g_attrib_unref(gatt->attrib); + g_free(current); +} + +static int uuid_desc16_cmp(bt_uuid_t *uuid, guint16 desc) +{ + bt_uuid_t u16; + + bt_uuid16_create(&u16, desc); + + return bt_uuid_cmp(uuid, &u16); +} + +static void descriptor_cb(guint8 status, const guint8 *pdu, guint16 plen, + gpointer user_data) +{ + struct query_data *current = user_data; + struct gatt_service *gatt = current->prim->gatt; + struct att_data_list *list; + guint8 format; + int i; + + if (status != 0) + goto done; + + DBG("Find Information Response received"); + + list = dec_find_info_resp(pdu, plen, &format); + if (list == NULL) + goto done; + + for (i = 0; i < list->num; i++) { + guint16 handle; + bt_uuid_t uuid; + uint8_t *info = list->data[i]; + struct query_data *qfmt; + + handle = att_get_u16(info); + + if (format == 0x01) { + uuid = att_get_uuid16(&info[2]); + } else { + /* Currently, only "user description" and "presentation + * format" descriptors are used, and both have 16-bit + * UUIDs. Therefore there is no need to support format + * 0x02 yet. */ + continue; + } + qfmt = g_new0(struct query_data, 1); + qfmt->prim = current->prim; + qfmt->chr = current->chr; + qfmt->handle = handle; + + if (uuid_desc16_cmp(&uuid, GATT_CHARAC_USER_DESC_UUID) == 0) { + gatt->attrib = g_attrib_ref(gatt->attrib); + gatt_read_char(gatt->attrib, handle, 0, update_char_desc, + qfmt); + } else if (uuid_desc16_cmp(&uuid, GATT_CHARAC_FMT_UUID) == 0) { + gatt->attrib = g_attrib_ref(gatt->attrib); + gatt_read_char(gatt->attrib, handle, 0, + update_char_format, qfmt); + } else + g_free(qfmt); + } + + att_data_list_free(list); +done: + g_attrib_unref(gatt->attrib); + g_free(current); +} + +static void update_all_chars(gpointer data, gpointer user_data) +{ + struct query_data *qdesc, *qvalue; + struct characteristic *chr = data; + struct primary *prim = user_data; + struct gatt_service *gatt = prim->gatt; + + qdesc = g_new0(struct query_data, 1); + qdesc->prim = prim; + qdesc->chr = chr; + + gatt->attrib = g_attrib_ref(gatt->attrib); + gatt_find_info(gatt->attrib, chr->handle + 1, chr->end, descriptor_cb, + qdesc); + + qvalue = g_new0(struct query_data, 1); + qvalue->prim = prim; + qvalue->chr = chr; + + gatt->attrib = g_attrib_ref(gatt->attrib); + gatt_read_char(gatt->attrib, chr->handle, 0, update_char_value, qvalue); +} + +static void char_discovered_cb(GSList *characteristics, guint8 status, + gpointer user_data) +{ + DBusMessage *reply; + DBusMessageIter iter, array_iter; + struct query_data *current = user_data; + struct primary *prim = current->prim; + struct att_primary *att = prim->att; + struct gatt_service *gatt = prim->gatt; + uint16_t *previous_end = NULL; + GSList *l; + + if (status != 0) { + const char *str = att_ecode2str(status); + + DBG("Discover all characteristics failed: %s", str); + reply = btd_error_failed(current->msg, str); + goto fail; + } + + for (l = characteristics; l; l = l->next) { + struct att_char *current_chr = l->data; + struct characteristic *chr; + guint handle = current_chr->value_handle; + GSList *lchr; + + lchr = g_slist_find_custom(prim->chars, + GUINT_TO_POINTER(handle), characteristic_handle_cmp); + if (lchr) + continue; + + chr = g_new0(struct characteristic, 1); + chr->prim = prim; + chr->perm = current_chr->properties; + chr->handle = current_chr->value_handle; + chr->path = g_strdup_printf("%s/characteristic%04x", + prim->path, chr->handle); + strncpy(chr->type, current_chr->uuid, sizeof(chr->type)); + + if (previous_end) + *previous_end = current_chr->handle; + + previous_end = &chr->end; + + prim->chars = g_slist_append(prim->chars, chr); + } + + if (previous_end) + *previous_end = att->end; + + store_characteristics(gatt, prim); + register_characteristics(prim); + + reply = dbus_message_new_method_return(current->msg); + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_OBJECT_PATH_AS_STRING, &array_iter); + + for (l = prim->chars; l; l = l->next) { + struct characteristic *chr = l->data; + + dbus_message_iter_append_basic(&array_iter, + DBUS_TYPE_OBJECT_PATH, &chr->path); + } + + dbus_message_iter_close_container(&iter, &array_iter); + + g_slist_foreach(prim->chars, update_all_chars, prim); + +fail: + g_dbus_send_message(connection, reply); + g_attrib_unref(gatt->attrib); + g_free(current); +} + +static DBusMessage *discover_char(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct primary *prim = data; + struct att_primary *att = prim->att; + struct gatt_service *gatt = prim->gatt; + struct query_data *qchr; + GError *gerr = NULL; + + if (l2cap_connect(prim->gatt, &gerr, FALSE) < 0) { + DBusMessage *reply = btd_error_failed(msg, gerr->message); + g_error_free(gerr); + return reply; + } + + qchr = g_new0(struct query_data, 1); + qchr->prim = prim; + qchr->msg = dbus_message_ref(msg); + + gatt_discover_char(gatt->attrib, att->start, att->end, NULL, + char_discovered_cb, qchr); + + return NULL; +} + +static DBusMessage *prim_get_properties(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct primary *prim = data; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + GSList *l; + char **chars; + const char *uuid; + int i; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + 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); + + chars = g_new0(char *, g_slist_length(prim->chars) + 1); + + for (i = 0, l = prim->chars; l; l = l->next, i++) { + struct characteristic *chr = l->data; + chars[i] = chr->path; + } + + dict_append_array(&dict, "Characteristics", DBUS_TYPE_OBJECT_PATH, + &chars, i); + uuid = prim->att->uuid; + dict_append_entry(&dict, "UUID", DBUS_TYPE_STRING, &uuid); + + g_free(chars); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static GDBusMethodTable prim_methods[] = { + { "DiscoverCharacteristics", "", "ao", discover_char, + G_DBUS_METHOD_FLAG_ASYNC }, + { "RegisterCharacteristicsWatcher", "o", "", + register_watcher }, + { "UnregisterCharacteristicsWatcher", "o", "", + unregister_watcher }, + { "GetProperties", "", "a{sv}",prim_get_properties }, + { } +}; + +static void register_primaries(struct gatt_service *gatt, GSList *primaries) +{ + GSList *l; + + for (l = primaries; l; l = l->next) { + struct att_primary *att = l->data; + struct primary *prim; + + prim = g_new0(struct primary, 1); + prim->att = att; + prim->gatt = gatt; + prim->path = g_strdup_printf("%s/service%04x", gatt->path, + att->start); + + g_dbus_register_interface(connection, prim->path, + CHAR_INTERFACE, prim_methods, + NULL, NULL, prim, NULL); + DBG("Registered: %s", prim->path); + + gatt->primary = g_slist_append(gatt->primary, prim); + btd_device_add_service(gatt->dev, prim->path); + load_characteristics(prim, gatt); + } +} + +int attrib_client_register(struct btd_device *device, int psm) +{ + struct btd_adapter *adapter = device_get_adapter(device); + const char *path = device_get_path(device); + struct gatt_service *gatt; + GSList *primaries = btd_device_get_primaries(device); + bdaddr_t sba, dba; + + adapter_get_address(adapter, &sba); + device_get_address(device, &dba); + + gatt = g_new0(struct gatt_service, 1); + gatt->dev = btd_device_ref(device); + gatt->listen = FALSE; + gatt->path = g_strdup(path); + bacpy(&gatt->sba, &sba); + bacpy(&gatt->dba, &dba); + gatt->psm = psm; + + register_primaries(gatt, primaries); + + gatt_services = g_slist_append(gatt_services, gatt); + + return 0; +} + +void attrib_client_unregister(struct btd_device *device) +{ + struct gatt_service *gatt; + GSList *l, *lp, *lc; + + l = g_slist_find_custom(gatt_services, device, gatt_dev_cmp); + if (!l) + return; + + gatt = l->data; + gatt_services = g_slist_remove(gatt_services, gatt); + + for (lp = gatt->primary; lp; lp = lp->next) { + struct primary *prim = lp->data; + for (lc = prim->chars; lc; lc = lc->next) { + struct characteristic *chr = lc->data; + g_dbus_unregister_interface(connection, chr->path, + CHAR_INTERFACE); + } + g_dbus_unregister_interface(connection, prim->path, + CHAR_INTERFACE); + } + + gatt_service_free(gatt); +} + +int attrib_client_init(DBusConnection *conn) +{ + + connection = dbus_connection_ref(conn); + + /* + * FIXME: if the adapter supports BLE start scanning. Temporary + * solution, this approach doesn't allow to control scanning based + * on the discoverable property. + */ + + return 0; +} + +void attrib_client_exit(void) +{ + dbus_connection_unref(connection); +} |