From 799757ccf1d03c33c75bc597cd5ef77741dcb6a7 Mon Sep 17 00:00:00 2001 From: Adrian Bunk Date: Fri, 3 Jun 2011 09:17:04 +0000 Subject: Imported upstream 4.91 --- src/adapter.c | 3752 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/adapter.h | 302 +++++ src/agent.c | 794 +++++++++++ src/agent.h | 77 ++ src/attrib-server.c | 1310 ++++++++++++++++++ src/attrib-server.h | 35 + src/bluetooth.conf | 33 + src/bluetooth.ver | 10 + src/bluetoothd.8.in | 91 ++ src/dbus-common.c | 254 ++++ src/dbus-common.h | 47 + src/device.c | 2388 ++++++++++++++++++++++++++++++++ src/device.h | 119 ++ src/error.c | 116 ++ src/error.h | 43 + src/event.c | 731 ++++++++++ src/event.h | 44 + src/genbuiltin | 17 + src/glib-helper.c | 587 ++++++++ src/glib-helper.h | 36 + src/hcid.h | 66 + src/log.c | 136 ++ src/log.h | 57 + src/main.c | 509 +++++++ src/main.conf | 66 + src/manager.c | 431 ++++++ src/manager.h | 43 + src/oob.c | 41 + src/oob.h | 32 + src/oui.c | 101 ++ src/oui.h | 25 + src/plugin.c | 249 ++++ src/plugin.h | 47 + src/ppoll.h | 16 + src/rfkill.c | 173 +++ src/sdp-xml.c | 789 +++++++++++ src/sdp-xml.h | 59 + src/sdpd-database.c | 346 +++++ src/sdpd-request.c | 1094 +++++++++++++++ src/sdpd-server.c | 292 ++++ src/sdpd-service.c | 537 ++++++++ src/sdpd.h | 97 ++ src/storage.c | 1345 ++++++++++++++++++ src/storage.h | 92 ++ src/textfile.c | 492 +++++++ src/textfile.h | 43 + src/uinput.h | 724 ++++++++++ 47 files changed, 18688 insertions(+) create mode 100644 src/adapter.c create mode 100644 src/adapter.h create mode 100644 src/agent.c create mode 100644 src/agent.h create mode 100644 src/attrib-server.c create mode 100644 src/attrib-server.h create mode 100644 src/bluetooth.conf create mode 100644 src/bluetooth.ver create mode 100644 src/bluetoothd.8.in create mode 100644 src/dbus-common.c create mode 100644 src/dbus-common.h create mode 100644 src/device.c create mode 100644 src/device.h create mode 100644 src/error.c create mode 100644 src/error.h create mode 100644 src/event.c create mode 100644 src/event.h create mode 100755 src/genbuiltin create mode 100644 src/glib-helper.c create mode 100644 src/glib-helper.h create mode 100644 src/hcid.h create mode 100644 src/log.c create mode 100644 src/log.h create mode 100644 src/main.c create mode 100644 src/main.conf create mode 100644 src/manager.c create mode 100644 src/manager.h create mode 100644 src/oob.c create mode 100644 src/oob.h create mode 100644 src/oui.c create mode 100644 src/oui.h create mode 100644 src/plugin.c create mode 100644 src/plugin.h create mode 100644 src/ppoll.h create mode 100644 src/rfkill.c create mode 100644 src/sdp-xml.c create mode 100644 src/sdp-xml.h create mode 100644 src/sdpd-database.c create mode 100644 src/sdpd-request.c create mode 100644 src/sdpd-server.c create mode 100644 src/sdpd-service.c create mode 100644 src/sdpd.h create mode 100644 src/storage.c create mode 100644 src/storage.h create mode 100644 src/textfile.c create mode 100644 src/textfile.h create mode 100644 src/uinput.h (limited to 'src') diff --git a/src/adapter.c b/src/adapter.c new file mode 100644 index 0000000..031e141 --- /dev/null +++ b/src/adapter.c @@ -0,0 +1,3752 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "log.h" +#include "textfile.h" + +#include "hcid.h" +#include "sdpd.h" +#include "adapter.h" +#include "manager.h" +#include "device.h" +#include "dbus-common.h" +#include "event.h" +#include "error.h" +#include "glib-helper.h" +#include "agent.h" +#include "storage.h" +#include "attrib-server.h" +#include "att.h" + +/* Flags Descriptions */ +#define EIR_LIM_DISC 0x01 /* LE Limited Discoverable Mode */ +#define EIR_GEN_DISC 0x02 /* LE General Discoverable Mode */ +#define EIR_BREDR_UNSUP 0x04 /* BR/EDR Not Supported */ +#define EIR_SIM_CONTROLLER 0x08 /* Simultaneous LE and BR/EDR to Same + Device Capable (Controller) */ +#define EIR_SIM_HOST 0x10 /* Simultaneous LE and BR/EDR to Same + Device Capable (Host) */ + +#define ADV_TYPE_IND 0x00 +#define ADV_TYPE_DIRECT_IND 0x01 + +#define IO_CAPABILITY_DISPLAYONLY 0x00 +#define IO_CAPABILITY_DISPLAYYESNO 0x01 +#define IO_CAPABILITY_KEYBOARDONLY 0x02 +#define IO_CAPABILITY_NOINPUTNOOUTPUT 0x03 +#define IO_CAPABILITY_INVALID 0xFF + +#define check_address(address) bachk(address) + +static DBusConnection *connection = NULL; +static GSList *adapter_drivers = NULL; + +static GSList *ops_candidates = NULL; + +const struct btd_adapter_ops *adapter_ops = NULL; + +struct session_req { + struct btd_adapter *adapter; + DBusConnection *conn; /* Connection reference */ + DBusMessage *msg; /* Unreplied message ref */ + char *owner; /* Bus name of the owner */ + guint id; /* Listener id */ + uint8_t mode; /* Requested mode */ + int refcount; /* Session refcount */ + gboolean got_reply; /* Agent reply received */ +}; + +struct service_auth { + service_auth_cb cb; + void *user_data; + struct btd_device *device; + struct btd_adapter *adapter; +}; + +struct btd_adapter { + uint16_t dev_id; + int up; + char *path; /* adapter object path */ + bdaddr_t bdaddr; /* adapter Bluetooth Address */ + uint32_t dev_class; /* Class of Device */ + guint discov_timeout_id; /* discoverable timeout id */ + guint stop_discov_id; /* stop inquiry/scanning id */ + uint32_t discov_timeout; /* discoverable time(sec) */ + guint pairable_timeout_id; /* pairable timeout id */ + uint32_t pairable_timeout; /* pairable time(sec) */ + uint8_t scan_mode; /* scan mode: SCAN_DISABLED, SCAN_PAGE, + * SCAN_INQUIRY */ + uint8_t mode; /* off, connectable, discoverable, + * limited */ + uint8_t global_mode; /* last valid global mode */ + struct session_req *pending_mode; + int state; /* standard inq, periodic inq, name + * resolving, suspended discovery */ + GSList *found_devices; + GSList *oor_devices; /* out of range device list */ + struct agent *agent; /* For the new API */ + guint auth_idle_id; /* Ongoing authorization */ + GSList *connections; /* Connected devices */ + GSList *devices; /* Devices structure pointers */ + GSList *mode_sessions; /* Request Mode sessions */ + GSList *disc_sessions; /* Discovery sessions */ + guint scheduler_id; /* Scheduler handle */ + sdp_list_t *services; /* Services associated to adapter */ + + struct hci_dev dev; /* hci info */ + gboolean pairable; /* pairable state */ + gboolean initialized; + + gboolean off_requested; /* DEVDOWN ioctl was called */ + + gint ref; + + GSList *powered_callbacks; + + gboolean name_stored; + + GSList *loaded_drivers; +}; + +static void adapter_set_pairable_timeout(struct btd_adapter *adapter, + guint interval); + +static int found_device_cmp(const struct remote_dev_info *d1, + const struct remote_dev_info *d2) +{ + int ret; + + if (bacmp(&d2->bdaddr, BDADDR_ANY)) { + ret = bacmp(&d1->bdaddr, &d2->bdaddr); + if (ret) + return ret; + } + + if (d2->name_status != NAME_ANY) { + ret = (d1->name_status - d2->name_status); + if (ret) + return ret; + } + + return 0; +} + +static void dev_info_free(struct remote_dev_info *dev) +{ + g_free(dev->name); + g_free(dev->alias); + g_slist_foreach(dev->services, (GFunc) g_free, NULL); + g_slist_free(dev->services); + g_strfreev(dev->uuids); + g_free(dev); +} + +/* + * Device name expansion + * %d - device id + */ +static char *expand_name(char *dst, int size, char *str, int dev_id) +{ + register int sp, np, olen; + char *opt, buf[10]; + + if (!str || !dst) + return NULL; + + sp = np = 0; + while (np < size - 1 && str[sp]) { + switch (str[sp]) { + case '%': + opt = NULL; + + switch (str[sp+1]) { + case 'd': + sprintf(buf, "%d", dev_id); + opt = buf; + break; + + case 'h': + opt = main_opts.host_name; + break; + + case '%': + dst[np++] = str[sp++]; + /* fall through */ + default: + sp++; + continue; + } + + if (opt) { + /* substitute */ + olen = strlen(opt); + if (np + olen < size - 1) + memcpy(dst + np, opt, olen); + np += olen; + } + sp += 2; + continue; + + case '\\': + sp++; + /* fall through */ + default: + dst[np++] = str[sp++]; + break; + } + } + dst[np] = '\0'; + return dst; +} + +int btd_adapter_set_class(struct btd_adapter *adapter, uint8_t major, + uint8_t minor) +{ + return adapter_ops->set_dev_class(adapter->dev_id, major, minor); +} + +static int pending_remote_name_cancel(struct btd_adapter *adapter) +{ + struct remote_dev_info *dev, match; + int err; + + /* find the pending remote name request */ + memset(&match, 0, sizeof(struct remote_dev_info)); + bacpy(&match.bdaddr, BDADDR_ANY); + match.name_status = NAME_REQUESTED; + + dev = adapter_search_found_devices(adapter, &match); + if (!dev) /* no pending request */ + return -ENODATA; + + adapter->state &= ~STATE_RESOLVNAME; + err = adapter_ops->cancel_resolve_name(adapter->dev_id, &dev->bdaddr); + if (err < 0) + error("Remote name cancel failed: %s(%d)", + strerror(errno), errno); + return err; +} + +int adapter_resolve_names(struct btd_adapter *adapter) +{ + struct remote_dev_info *dev, match; + int err; + + /* Do not attempt to resolve more names if on suspended state */ + if (adapter->state & STATE_SUSPENDED) + return 0; + + memset(&match, 0, sizeof(struct remote_dev_info)); + bacpy(&match.bdaddr, BDADDR_ANY); + match.name_status = NAME_REQUIRED; + + dev = adapter_search_found_devices(adapter, &match); + if (!dev) + return -ENODATA; + + /* send at least one request or return failed if the list is empty */ + do { + /* flag to indicate the current remote name requested */ + dev->name_status = NAME_REQUESTED; + + err = adapter_ops->resolve_name(adapter->dev_id, &dev->bdaddr); + + if (!err) + break; + + error("Unable to send HCI remote name req: %s (%d)", + strerror(errno), errno); + + /* if failed, request the next element */ + /* remove the element from the list */ + adapter_remove_found_device(adapter, &dev->bdaddr); + + /* get the next element */ + dev = adapter_search_found_devices(adapter, &match); + } while (dev); + + return err; +} + +static const char *mode2str(uint8_t mode) +{ + switch(mode) { + case MODE_OFF: + return "off"; + case MODE_CONNECTABLE: + return "connectable"; + case MODE_DISCOVERABLE: + return "discoverable"; + default: + return "unknown"; + } +} + +static uint8_t get_mode(const bdaddr_t *bdaddr, const char *mode) +{ + if (strcasecmp("off", mode) == 0) + return MODE_OFF; + else if (strcasecmp("connectable", mode) == 0) + return MODE_CONNECTABLE; + else if (strcasecmp("discoverable", mode) == 0) + return MODE_DISCOVERABLE; + else if (strcasecmp("on", mode) == 0) { + char onmode[14], srcaddr[18]; + + ba2str(bdaddr, srcaddr); + if (read_on_mode(srcaddr, onmode, sizeof(onmode)) < 0) + return MODE_CONNECTABLE; + + return get_mode(bdaddr, onmode); + } else + return MODE_UNKNOWN; +} + +static void adapter_set_limited_discoverable(struct btd_adapter *adapter, + gboolean limited) +{ + DBG("%s", limited ? "TRUE" : "FALSE"); + + adapter_ops->set_limited_discoverable(adapter->dev_id, limited); +} + +static void adapter_remove_discov_timeout(struct btd_adapter *adapter) +{ + if (!adapter) + return; + + if (adapter->discov_timeout_id == 0) + return; + + g_source_remove(adapter->discov_timeout_id); + adapter->discov_timeout_id = 0; +} + +static gboolean discov_timeout_handler(gpointer user_data) +{ + struct btd_adapter *adapter = user_data; + + adapter->discov_timeout_id = 0; + + adapter_ops->set_discoverable(adapter->dev_id, FALSE); + + return FALSE; +} + +static void adapter_set_discov_timeout(struct btd_adapter *adapter, + guint interval) +{ + if (adapter->discov_timeout_id) { + g_source_remove(adapter->discov_timeout_id); + adapter->discov_timeout_id = 0; + } + + if (interval == 0) { + adapter_set_limited_discoverable(adapter, FALSE); + return; + } + + /* Set limited discoverable if pairable and interval between 0 to 60 + sec */ + if (adapter->pairable && interval <= 60) + adapter_set_limited_discoverable(adapter, TRUE); + else + adapter_set_limited_discoverable(adapter, FALSE); + + adapter->discov_timeout_id = g_timeout_add_seconds(interval, + discov_timeout_handler, + adapter); +} + +static struct session_req *session_ref(struct session_req *req) +{ + req->refcount++; + + DBG("%p: ref=%d", req, req->refcount); + + return req; +} + +static struct session_req *create_session(struct btd_adapter *adapter, + DBusConnection *conn, DBusMessage *msg, + uint8_t mode, GDBusWatchFunction cb) +{ + const char *sender = dbus_message_get_sender(msg); + struct session_req *req; + + req = g_new0(struct session_req, 1); + req->adapter = adapter; + req->conn = dbus_connection_ref(conn); + req->msg = dbus_message_ref(msg); + req->mode = mode; + + if (cb == NULL) + return session_ref(req); + + req->owner = g_strdup(sender); + req->id = g_dbus_add_disconnect_watch(conn, sender, cb, req, NULL); + + info("%s session %p with %s activated", + req->mode ? "Mode" : "Discovery", req, sender); + + return session_ref(req); +} + +static int adapter_set_mode(struct btd_adapter *adapter, uint8_t mode) +{ + int err; + + if (mode == MODE_CONNECTABLE) + err = adapter_ops->set_discoverable(adapter->dev_id, FALSE); + else + err = adapter_ops->set_discoverable(adapter->dev_id, TRUE); + + if (err < 0) + return err; + + if (mode == MODE_CONNECTABLE) + return 0; + + adapter_remove_discov_timeout(adapter); + + if (adapter->discov_timeout) + adapter_set_discov_timeout(adapter, adapter->discov_timeout); + + return 0; +} + +static struct session_req *find_session_by_msg(GSList *list, const DBusMessage *msg) +{ + GSList *l; + + for (l = list; l; l = l->next) { + struct session_req *req = l->data; + + if (req->msg == msg) + return req; + } + + return NULL; +} + +static int set_mode(struct btd_adapter *adapter, uint8_t new_mode, + DBusMessage *msg) +{ + int err; + const char *modestr; + gboolean discoverable; + + if (adapter->pending_mode != NULL) + return -EALREADY; + + discoverable = new_mode == MODE_DISCOVERABLE; + + if (!adapter->up && new_mode != MODE_OFF) { + err = adapter_ops->set_powered(adapter->dev_id, TRUE); + if (err < 0) + return err; + + goto done; + } + + if (adapter->up && new_mode == MODE_OFF) { + err = adapter_ops->set_powered(adapter->dev_id, FALSE); + if (err < 0) + return err; + + adapter->off_requested = TRUE; + + goto done; + } + + if (new_mode == adapter->mode) + return 0; + + err = adapter_set_mode(adapter, new_mode); + + if (err < 0) + return err; + +done: + modestr = mode2str(new_mode); + write_device_mode(&adapter->bdaddr, modestr); + + DBG("%s", modestr); + + if (msg != NULL) { + struct session_req *req; + + req = find_session_by_msg(adapter->mode_sessions, msg); + if (req) { + adapter->pending_mode = req; + session_ref(req); + } else + /* Wait for mode change to reply */ + adapter->pending_mode = create_session(adapter, + connection, msg, new_mode, NULL); + } else + /* Nothing to reply just write the new mode */ + adapter->mode = new_mode; + + return 0; +} + +static DBusMessage *set_discoverable(DBusConnection *conn, DBusMessage *msg, + gboolean discoverable, void *data) +{ + struct btd_adapter *adapter = data; + uint8_t mode; + int err; + + mode = discoverable ? MODE_DISCOVERABLE : MODE_CONNECTABLE; + + if (mode == adapter->mode) { + adapter->global_mode = mode; + return dbus_message_new_method_return(msg); + } + + err = set_mode(adapter, mode, msg); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + return NULL; +} + +static DBusMessage *set_powered(DBusConnection *conn, DBusMessage *msg, + gboolean powered, void *data) +{ + struct btd_adapter *adapter = data; + uint8_t mode; + int err; + + if (powered) { + mode = get_mode(&adapter->bdaddr, "on"); + return set_discoverable(conn, msg, mode == MODE_DISCOVERABLE, + data); + } + + mode = MODE_OFF; + + if (mode == adapter->mode) { + adapter->global_mode = mode; + return dbus_message_new_method_return(msg); + } + + err = set_mode(adapter, mode, msg); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + return NULL; +} + +void btd_adapter_pairable_changed(struct btd_adapter *adapter, + gboolean pairable) +{ + adapter->pairable = pairable; + + write_device_pairable(&adapter->bdaddr, pairable); + + emit_property_changed(connection, adapter->path, + ADAPTER_INTERFACE, "Pairable", + DBUS_TYPE_BOOLEAN, &pairable); + + if (pairable && adapter->pairable_timeout) + adapter_set_pairable_timeout(adapter, + adapter->pairable_timeout); +} + +static DBusMessage *set_pairable(DBusConnection *conn, DBusMessage *msg, + gboolean pairable, void *data) +{ + struct btd_adapter *adapter = data; + int err; + + if (adapter->scan_mode == SCAN_DISABLED) + return btd_error_not_ready(msg); + + if (pairable == adapter->pairable) + goto done; + + if (!(adapter->scan_mode & SCAN_INQUIRY)) + goto store; + + err = set_mode(adapter, MODE_DISCOVERABLE, NULL); + if (err < 0 && msg) + return btd_error_failed(msg, strerror(-err)); + +store: + adapter_ops->set_pairable(adapter->dev_id, pairable); + +done: + return msg ? dbus_message_new_method_return(msg) : NULL; +} + +static gboolean pairable_timeout_handler(void *data) +{ + set_pairable(NULL, NULL, FALSE, data); + + return FALSE; +} + +static void adapter_set_pairable_timeout(struct btd_adapter *adapter, + guint interval) +{ + if (adapter->pairable_timeout_id) { + g_source_remove(adapter->pairable_timeout_id); + adapter->pairable_timeout_id = 0; + } + + if (interval == 0) + return; + + adapter->pairable_timeout_id = g_timeout_add_seconds(interval, + pairable_timeout_handler, + adapter); +} + +static struct session_req *find_session(GSList *list, const char *sender) +{ + GSList *l; + + for (l = list; l; l = l->next) { + struct session_req *req = l->data; + + if (g_str_equal(req->owner, sender)) + return req; + } + + return NULL; +} + +static uint8_t get_needed_mode(struct btd_adapter *adapter, uint8_t mode) +{ + GSList *l; + + if (adapter->global_mode > mode) + mode = adapter->global_mode; + + for (l = adapter->mode_sessions; l; l = l->next) { + struct session_req *req = l->data; + + if (req->mode > mode) + mode = req->mode; + } + + return mode; +} + +static GSList *remove_bredr(GSList *all) +{ + GSList *l, *le; + + for (l = all, le = NULL; l; l = l->next) { + struct remote_dev_info *dev = l->data; + if (dev->le == FALSE) { + dev_info_free(dev); + continue; + } + + le = g_slist_append(le, dev); + } + + g_slist_free(all); + + return le; +} + +static void stop_discovery(struct btd_adapter *adapter, gboolean suspend) +{ + pending_remote_name_cancel(adapter); + + if (suspend == FALSE) + adapter->found_devices = remove_bredr(adapter->found_devices); + + if (adapter->oor_devices) { + g_slist_free(adapter->oor_devices); + adapter->oor_devices = NULL; + } + + /* Reset if suspended, otherwise remove timer (software scheduler) + or request inquiry to stop */ + if (adapter->state & STATE_SUSPENDED) { + adapter->state &= ~STATE_SUSPENDED; + return; + } + + if (adapter->scheduler_id) { + g_source_remove(adapter->scheduler_id); + adapter->scheduler_id = 0; + return; + } + + if (adapter->state & STATE_LE_SCAN) + adapter_ops->stop_scanning(adapter->dev_id); + else + adapter_ops->stop_inquiry(adapter->dev_id); +} + +static void session_remove(struct session_req *req) +{ + struct btd_adapter *adapter = req->adapter; + + /* Ignore set_mode session */ + if (req->owner == NULL) + return; + + DBG("%s session %p with %s deactivated", + req->mode ? "Mode" : "Discovery", req, req->owner); + + if (req->mode) { + uint8_t mode; + + adapter->mode_sessions = g_slist_remove(adapter->mode_sessions, + req); + + mode = get_needed_mode(adapter, adapter->global_mode); + + if (mode == adapter->mode) + return; + + DBG("Switching to '%s' mode", mode2str(mode)); + + set_mode(adapter, mode, NULL); + } else { + adapter->disc_sessions = g_slist_remove(adapter->disc_sessions, + req); + + if (adapter->disc_sessions) + return; + + DBG("Stopping discovery"); + + stop_discovery(adapter, FALSE); + } +} + +static void session_free(struct session_req *req) +{ + if (req->id) + g_dbus_remove_watch(req->conn, req->id); + + session_remove(req); + + if (req->msg) { + dbus_message_unref(req->msg); + if (!req->got_reply && req->mode && req->adapter->agent) + agent_cancel(req->adapter->agent); + } + + if (req->conn) + dbus_connection_unref(req->conn); + g_free(req->owner); + g_free(req); +} + +static void session_owner_exit(DBusConnection *conn, void *user_data) +{ + struct session_req *req = user_data; + + req->id = 0; + + session_free(req); +} + +static void session_unref(struct session_req *req) +{ + req->refcount--; + + DBG("%p: ref=%d", req, req->refcount); + + if (req->refcount) + return; + + session_free(req); +} + +static void confirm_mode_cb(struct agent *agent, DBusError *derr, void *data) +{ + struct session_req *req = data; + int err; + DBusMessage *reply; + + req->got_reply = TRUE; + + if (derr && dbus_error_is_set(derr)) { + reply = dbus_message_new_error(req->msg, derr->name, + derr->message); + g_dbus_send_message(req->conn, reply); + session_unref(req); + return; + } + + err = set_mode(req->adapter, req->mode, req->msg); + if (err < 0) + reply = btd_error_failed(req->msg, strerror(-err)); + else if (!req->adapter->pending_mode) + reply = dbus_message_new_method_return(req->msg); + else + reply = NULL; + + if (reply) { + /* + * Send reply immediately only if there was an error changing + * mode, or change is not needed. Otherwise, reply is sent in + * set_mode_complete. + */ + g_dbus_send_message(req->conn, reply); + + dbus_message_unref(req->msg); + req->msg = NULL; + } + + if (!find_session(req->adapter->mode_sessions, req->owner)) + session_unref(req); +} + +static DBusMessage *set_discoverable_timeout(DBusConnection *conn, + DBusMessage *msg, + uint32_t timeout, + void *data) +{ + struct btd_adapter *adapter = data; + const char *path; + + if (adapter->discov_timeout == timeout && timeout == 0) + return dbus_message_new_method_return(msg); + + if (adapter->scan_mode & SCAN_INQUIRY) + adapter_set_discov_timeout(adapter, timeout); + + adapter->discov_timeout = timeout; + + write_discoverable_timeout(&adapter->bdaddr, timeout); + + path = dbus_message_get_path(msg); + + emit_property_changed(conn, path, + ADAPTER_INTERFACE, "DiscoverableTimeout", + DBUS_TYPE_UINT32, &timeout); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *set_pairable_timeout(DBusConnection *conn, + DBusMessage *msg, + uint32_t timeout, + void *data) +{ + struct btd_adapter *adapter = data; + const char *path; + + if (adapter->pairable_timeout == timeout && timeout == 0) + return dbus_message_new_method_return(msg); + + if (adapter->pairable) + adapter_set_pairable_timeout(adapter, timeout); + + adapter->pairable_timeout = timeout; + + write_pairable_timeout(&adapter->bdaddr, timeout); + + path = dbus_message_get_path(msg); + + emit_property_changed(conn, path, + ADAPTER_INTERFACE, "PairableTimeout", + DBUS_TYPE_UINT32, &timeout); + + return dbus_message_new_method_return(msg); +} + +void btd_adapter_class_changed(struct btd_adapter *adapter, uint32_t new_class) +{ + uint8_t class[3]; + + class[2] = (new_class >> 16) & 0xff; + class[1] = (new_class >> 8) & 0xff; + class[0] = new_class & 0xff; + + write_local_class(&adapter->bdaddr, class); + + adapter->dev_class = new_class; + + if (main_opts.attrib_server) { + /* Removes service class */ + class[1] = class[1] & 0x1f; + attrib_gap_set(GATT_CHARAC_APPEARANCE, class, 2); + } + + emit_property_changed(connection, adapter->path, + ADAPTER_INTERFACE, "Class", + DBUS_TYPE_UINT32, &new_class); +} + +void adapter_update_local_name(struct btd_adapter *adapter, const char *name) +{ + struct hci_dev *dev = &adapter->dev; + + if (strncmp(name, dev->name, MAX_NAME_LENGTH) == 0) + return; + + strncpy(dev->name, name, MAX_NAME_LENGTH); + + if (main_opts.attrib_server) + attrib_gap_set(GATT_CHARAC_DEVICE_NAME, + (const uint8_t *) dev->name, strlen(dev->name)); + + if (!adapter->name_stored) { + char *name_ptr = dev->name; + + write_local_name(&adapter->bdaddr, dev->name); + + if (connection) + emit_property_changed(connection, adapter->path, + ADAPTER_INTERFACE, "Name", + DBUS_TYPE_STRING, &name_ptr); + } + + adapter->name_stored = FALSE; +} + +static DBusMessage *set_name(DBusConnection *conn, DBusMessage *msg, + const char *name, void *data) +{ + struct btd_adapter *adapter = data; + struct hci_dev *dev = &adapter->dev; + char *name_ptr = dev->name; + + if (!g_utf8_validate(name, -1, NULL)) { + error("Name change failed: supplied name isn't valid UTF-8"); + return btd_error_invalid_args(msg); + } + + if (strncmp(name, dev->name, MAX_NAME_LENGTH) == 0) + goto done; + + strncpy(dev->name, name, MAX_NAME_LENGTH); + write_local_name(&adapter->bdaddr, name); + emit_property_changed(connection, adapter->path, + ADAPTER_INTERFACE, "Name", + DBUS_TYPE_STRING, &name_ptr); + + if (adapter->up) { + int err = adapter_ops->set_name(adapter->dev_id, name); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + adapter->name_stored = TRUE; + } + +done: + return dbus_message_new_method_return(msg); +} + +struct btd_device *adapter_find_device(struct btd_adapter *adapter, + const char *dest) +{ + struct btd_device *device; + GSList *l; + + if (!adapter) + return NULL; + + l = g_slist_find_custom(adapter->devices, dest, + (GCompareFunc) device_address_cmp); + if (!l) + return NULL; + + device = l->data; + + return device; +} + +static void adapter_update_devices(struct btd_adapter *adapter) +{ + char **devices; + int i; + GSList *l; + + /* Devices */ + devices = g_new0(char *, g_slist_length(adapter->devices) + 1); + for (i = 0, l = adapter->devices; l; l = l->next, i++) { + struct btd_device *dev = l->data; + devices[i] = (char *) device_get_path(dev); + } + + emit_array_property_changed(connection, adapter->path, + ADAPTER_INTERFACE, "Devices", + DBUS_TYPE_OBJECT_PATH, &devices, i); + g_free(devices); +} + +static void adapter_emit_uuids_updated(struct btd_adapter *adapter) +{ + char **uuids; + int i; + sdp_list_t *list; + + if (!adapter->initialized) + return; + + uuids = g_new0(char *, sdp_list_len(adapter->services) + 1); + + for (i = 0, list = adapter->services; list; list = list->next) { + char *uuid; + sdp_record_t *rec = list->data; + + uuid = bt_uuid2string(&rec->svclass); + if (uuid) + uuids[i++] = uuid; + } + + emit_array_property_changed(connection, adapter->path, + ADAPTER_INTERFACE, "UUIDs", DBUS_TYPE_STRING, &uuids, i); + + g_strfreev(uuids); +} + +static uint8_t get_uuid_mask(uuid_t *uuid) +{ + if (uuid->type != SDP_UUID16) + return 0; + + switch (uuid->value.uuid16) { + case DIALUP_NET_SVCLASS_ID: + case CIP_SVCLASS_ID: + return 0x42; /* Telephony & Networking */ + case IRMC_SYNC_SVCLASS_ID: + case OBEX_OBJPUSH_SVCLASS_ID: + case OBEX_FILETRANS_SVCLASS_ID: + case IRMC_SYNC_CMD_SVCLASS_ID: + case PBAP_PSE_SVCLASS_ID: + return 0x10; /* Object Transfer */ + case HEADSET_SVCLASS_ID: + case HANDSFREE_SVCLASS_ID: + return 0x20; /* Audio */ + case CORDLESS_TELEPHONY_SVCLASS_ID: + case INTERCOM_SVCLASS_ID: + case FAX_SVCLASS_ID: + case SAP_SVCLASS_ID: + /* + * Setting the telephony bit for the handsfree audio gateway + * role is not required by the HFP specification, but the + * Nokia 616 carkit is just plain broken! It will refuse + * pairing without this bit set. + */ + case HANDSFREE_AGW_SVCLASS_ID: + return 0x40; /* Telephony */ + case AUDIO_SOURCE_SVCLASS_ID: + case VIDEO_SOURCE_SVCLASS_ID: + return 0x08; /* Capturing */ + case AUDIO_SINK_SVCLASS_ID: + case VIDEO_SINK_SVCLASS_ID: + return 0x04; /* Rendering */ + case PANU_SVCLASS_ID: + case NAP_SVCLASS_ID: + case GN_SVCLASS_ID: + return 0x02; /* Networking */ + default: + return 0; + } +} + +static int uuid_cmp(const void *a, const void *b) +{ + const sdp_record_t *rec = a; + const uuid_t *uuid = b; + + return sdp_uuid_cmp(&rec->svclass, uuid); +} + +void adapter_service_insert(struct btd_adapter *adapter, void *r) +{ + sdp_record_t *rec = r; + gboolean new_uuid; + + if (sdp_list_find(adapter->services, &rec->svclass, uuid_cmp) == NULL) + new_uuid = TRUE; + else + new_uuid = FALSE; + + adapter->services = sdp_list_insert_sorted(adapter->services, rec, + record_sort); + + if (new_uuid) { + uint8_t svc_hint = get_uuid_mask(&rec->svclass); + adapter_ops->add_uuid(adapter->dev_id, &rec->svclass, svc_hint); + } + + adapter_emit_uuids_updated(adapter); +} + +void adapter_service_remove(struct btd_adapter *adapter, void *r) +{ + sdp_record_t *rec = r; + + adapter->services = sdp_list_remove(adapter->services, rec); + + if (sdp_list_find(adapter->services, &rec->svclass, uuid_cmp) == NULL) + adapter_ops->remove_uuid(adapter->dev_id, &rec->svclass); + + adapter_emit_uuids_updated(adapter); +} + +static struct btd_device *adapter_create_device(DBusConnection *conn, + struct btd_adapter *adapter, + const char *address, + device_type_t type) +{ + struct btd_device *device; + const char *path; + + DBG("%s", address); + + device = device_create(conn, adapter, address, type); + if (!device) + return NULL; + + device_set_temporary(device, TRUE); + + adapter->devices = g_slist_append(adapter->devices, device); + + path = device_get_path(device); + g_dbus_emit_signal(conn, adapter->path, + ADAPTER_INTERFACE, "DeviceCreated", + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + + adapter_update_devices(adapter); + + return device; +} + +void adapter_remove_device(DBusConnection *conn, struct btd_adapter *adapter, + struct btd_device *device, + gboolean remove_storage) +{ + const gchar *dev_path = device_get_path(device); + struct agent *agent; + + adapter->devices = g_slist_remove(adapter->devices, device); + adapter->connections = g_slist_remove(adapter->connections, device); + + adapter_update_devices(adapter); + + g_dbus_emit_signal(conn, adapter->path, + ADAPTER_INTERFACE, "DeviceRemoved", + DBUS_TYPE_OBJECT_PATH, &dev_path, + DBUS_TYPE_INVALID); + + agent = device_get_agent(device); + + if (agent && device_is_authorizing(device)) + agent_cancel(agent); + + device_remove(device, remove_storage); +} + +struct btd_device *adapter_get_device(DBusConnection *conn, + struct btd_adapter *adapter, + const gchar *address) +{ + struct btd_device *device; + + DBG("%s", address); + + if (!adapter) + return NULL; + + device = adapter_find_device(adapter, address); + if (device) + return device; + + return adapter_create_device(conn, adapter, address, + DEVICE_TYPE_BREDR); +} + +static gboolean stop_scanning(gpointer user_data) +{ + struct btd_adapter *adapter = user_data; + + adapter_ops->stop_scanning(adapter->dev_id); + + return FALSE; +} + +static int start_discovery(struct btd_adapter *adapter) +{ + int err, type; + + /* Do not start if suspended */ + if (adapter->state & STATE_SUSPENDED) + return 0; + + /* Postpone discovery if still resolving names */ + if (adapter->state & STATE_RESOLVNAME) + return 1; + + pending_remote_name_cancel(adapter); + + type = adapter_get_discover_type(adapter) & ~DISC_RESOLVNAME; + + switch (type) { + case DISC_STDINQ: + case DISC_INTERLEAVE: + err = adapter_ops->start_inquiry(adapter->dev_id, + 0x08, FALSE); + break; + case DISC_PINQ: + err = adapter_ops->start_inquiry(adapter->dev_id, + 0x08, TRUE); + break; + case DISC_LE: + err = adapter_ops->start_scanning(adapter->dev_id); + break; + default: + err = -EINVAL; + } + + return err; +} + +static DBusMessage *adapter_start_discovery(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct session_req *req; + struct btd_adapter *adapter = data; + const char *sender = dbus_message_get_sender(msg); + int err; + + if (!adapter->up) + return btd_error_not_ready(msg); + + req = find_session(adapter->disc_sessions, sender); + if (req) { + session_ref(req); + return dbus_message_new_method_return(msg); + } + + if (adapter->disc_sessions) + goto done; + + g_slist_foreach(adapter->found_devices, (GFunc) dev_info_free, NULL); + g_slist_free(adapter->found_devices); + adapter->found_devices = NULL; + + g_slist_free(adapter->oor_devices); + adapter->oor_devices = NULL; + + err = start_discovery(adapter); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + +done: + req = create_session(adapter, conn, msg, 0, + session_owner_exit); + + adapter->disc_sessions = g_slist_append(adapter->disc_sessions, req); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_stop_discovery(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct btd_adapter *adapter = data; + struct session_req *req; + const char *sender = dbus_message_get_sender(msg); + + if (!adapter->up) + return btd_error_not_ready(msg); + + req = find_session(adapter->disc_sessions, sender); + if (!req) + return btd_error_failed(msg, "Invalid discovery session"); + + session_unref(req); + info("Stopping discovery"); + return dbus_message_new_method_return(msg); +} + +struct remote_device_list_t { + GSList *list; + time_t time; +}; + +static DBusMessage *get_properties(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct btd_adapter *adapter = data; + const char *property; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + char str[MAX_NAME_LENGTH + 1], srcaddr[18]; + gboolean value; + char **devices, **uuids; + int i; + GSList *l; + sdp_list_t *list; + + ba2str(&adapter->bdaddr, srcaddr); + + if (check_address(srcaddr) < 0) + return btd_error_invalid_args(msg); + + 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); + + /* Address */ + property = srcaddr; + dict_append_entry(&dict, "Address", DBUS_TYPE_STRING, &property); + + /* Name */ + memset(str, 0, sizeof(str)); + strncpy(str, (char *) adapter->dev.name, MAX_NAME_LENGTH); + property = str; + + dict_append_entry(&dict, "Name", DBUS_TYPE_STRING, &property); + + /* Class */ + dict_append_entry(&dict, "Class", + DBUS_TYPE_UINT32, &adapter->dev_class); + + /* Powered */ + value = (adapter->up && !adapter->off_requested) ? TRUE : FALSE; + dict_append_entry(&dict, "Powered", DBUS_TYPE_BOOLEAN, &value); + + /* Discoverable */ + value = adapter->scan_mode & SCAN_INQUIRY ? TRUE : FALSE; + dict_append_entry(&dict, "Discoverable", DBUS_TYPE_BOOLEAN, &value); + + /* Pairable */ + dict_append_entry(&dict, "Pairable", DBUS_TYPE_BOOLEAN, + &adapter->pairable); + + /* DiscoverableTimeout */ + dict_append_entry(&dict, "DiscoverableTimeout", + DBUS_TYPE_UINT32, &adapter->discov_timeout); + + /* PairableTimeout */ + dict_append_entry(&dict, "PairableTimeout", + DBUS_TYPE_UINT32, &adapter->pairable_timeout); + + + if (adapter->state & (STATE_PINQ | STATE_STDINQ | STATE_LE_SCAN)) + value = TRUE; + else + value = FALSE; + + /* Discovering */ + dict_append_entry(&dict, "Discovering", DBUS_TYPE_BOOLEAN, &value); + + /* Devices */ + devices = g_new0(char *, g_slist_length(adapter->devices) + 1); + for (i = 0, l = adapter->devices; l; l = l->next, i++) { + struct btd_device *dev = l->data; + devices[i] = (char *) device_get_path(dev); + } + dict_append_array(&dict, "Devices", DBUS_TYPE_OBJECT_PATH, + &devices, i); + g_free(devices); + + /* UUIDs */ + uuids = g_new0(char *, sdp_list_len(adapter->services) + 1); + + for (i = 0, list = adapter->services; list; list = list->next) { + sdp_record_t *rec = list->data; + char *uuid; + + uuid = bt_uuid2string(&rec->svclass); + if (uuid) + uuids[i++] = uuid; + } + + dict_append_array(&dict, "UUIDs", DBUS_TYPE_STRING, &uuids, i); + + g_strfreev(uuids); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static DBusMessage *set_property(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct btd_adapter *adapter = data; + DBusMessageIter iter; + DBusMessageIter sub; + const char *property; + char srcaddr[18]; + + ba2str(&adapter->bdaddr, srcaddr); + + 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("Name", property)) { + const char *name; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) + return btd_error_invalid_args(msg); + dbus_message_iter_get_basic(&sub, &name); + + return set_name(conn, msg, name, data); + } else if (g_str_equal("Powered", property)) { + gboolean powered; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&sub, &powered); + + return set_powered(conn, msg, powered, data); + } else if (g_str_equal("Discoverable", property)) { + gboolean discoverable; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&sub, &discoverable); + + return set_discoverable(conn, msg, discoverable, data); + } else if (g_str_equal("DiscoverableTimeout", property)) { + uint32_t timeout; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT32) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&sub, &timeout); + + return set_discoverable_timeout(conn, msg, timeout, data); + } else if (g_str_equal("Pairable", property)) { + gboolean pairable; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&sub, &pairable); + + return set_pairable(conn, msg, pairable, data); + } else if (g_str_equal("PairableTimeout", property)) { + uint32_t timeout; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT32) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&sub, &timeout); + + return set_pairable_timeout(conn, msg, timeout, data); + } + + return btd_error_invalid_args(msg); +} + +static DBusMessage *request_session(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct btd_adapter *adapter = data; + struct session_req *req; + const char *sender = dbus_message_get_sender(msg); + uint8_t new_mode; + int err; + + if (!adapter->agent) + return btd_error_agent_not_available(msg); + + if (!adapter->mode_sessions) + adapter->global_mode = adapter->mode; + + new_mode = get_mode(&adapter->bdaddr, "on"); + + req = find_session(adapter->mode_sessions, sender); + if (req) { + session_ref(req); + return dbus_message_new_method_return(msg); + } else { + req = create_session(adapter, conn, msg, new_mode, + session_owner_exit); + adapter->mode_sessions = g_slist_append(adapter->mode_sessions, + req); + } + + /* No need to change mode */ + if (adapter->mode >= new_mode) + return dbus_message_new_method_return(msg); + + err = agent_confirm_mode_change(adapter->agent, mode2str(new_mode), + confirm_mode_cb, req, NULL); + if (err < 0) { + session_unref(req); + return btd_error_failed(msg, strerror(-err)); + } + + return NULL; +} + +static DBusMessage *release_session(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct btd_adapter *adapter = data; + struct session_req *req; + const char *sender = dbus_message_get_sender(msg); + + req = find_session(adapter->mode_sessions, sender); + if (!req) + return btd_error_failed(msg, "Invalid Session"); + + session_unref(req); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *list_devices(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct btd_adapter *adapter = data; + DBusMessage *reply; + GSList *l; + DBusMessageIter iter; + DBusMessageIter array_iter; + const gchar *dev_path; + + if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) + return btd_error_invalid_args(msg); + + 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_TYPE_OBJECT_PATH_AS_STRING, &array_iter); + + for (l = adapter->devices; l; l = l->next) { + struct btd_device *device = l->data; + + dev_path = device_get_path(device); + + dbus_message_iter_append_basic(&array_iter, + DBUS_TYPE_OBJECT_PATH, &dev_path); + } + + dbus_message_iter_close_container(&iter, &array_iter); + + return reply; +} + +static DBusMessage *cancel_device_creation(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct btd_adapter *adapter = data; + const gchar *address, *sender = dbus_message_get_sender(msg); + struct btd_device *device; + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address, + DBUS_TYPE_INVALID) == FALSE) + return btd_error_invalid_args(msg); + + if (check_address(address) < 0) + return btd_error_invalid_args(msg); + + device = adapter_find_device(adapter, address); + if (!device || !device_is_creating(device, NULL)) + return btd_error_does_not_exist(msg); + + if (!device_is_creating(device, sender)) + return btd_error_not_authorized(msg); + + device_set_temporary(device, TRUE); + + if (device_is_connected(device)) { + device_request_disconnect(device, msg); + return NULL; + } + + adapter_remove_device(conn, adapter, device, TRUE); + + return dbus_message_new_method_return(msg); +} + +static device_type_t flags2type(uint8_t flags) +{ + /* Inferring the remote type based on the EIR Flags field */ + + /* For LE only and dual mode the following flags must be zero */ + if (flags & (EIR_SIM_CONTROLLER | EIR_SIM_HOST)) + return DEVICE_TYPE_UNKNOWN; + + /* Limited or General discoverable mode bit must be enabled */ + if (!(flags & (EIR_LIM_DISC | EIR_GEN_DISC))) + return DEVICE_TYPE_UNKNOWN; + + if (flags & EIR_BREDR_UNSUP) + return DEVICE_TYPE_LE; + else + return DEVICE_TYPE_DUALMODE; +} + +static gboolean event_is_connectable(uint8_t type) +{ + switch (type) { + case ADV_TYPE_IND: + case ADV_TYPE_DIRECT_IND: + return TRUE; + default: + return FALSE; + } +} + +static struct btd_device *create_device_internal(DBusConnection *conn, + struct btd_adapter *adapter, + const gchar *address, + gboolean force, int *err) +{ + struct remote_dev_info *dev, match; + struct btd_device *device; + device_type_t type; + + memset(&match, 0, sizeof(struct remote_dev_info)); + str2ba(address, &match.bdaddr); + match.name_status = NAME_ANY; + + dev = adapter_search_found_devices(adapter, &match); + if (dev && dev->flags) + type = flags2type(dev->flags); + else + type = DEVICE_TYPE_BREDR; + + if (!force && type == DEVICE_TYPE_LE && + !event_is_connectable(dev->evt_type)) { + if (err) + *err = -ENOTCONN; + + return NULL; + } + + device = adapter_create_device(conn, adapter, address, type); + if (!device && err) + *err = -ENOMEM; + + return device; +} + +static DBusMessage *create_device(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct btd_adapter *adapter = data; + struct btd_device *device; + const gchar *address; + DBusMessage *reply; + int err; + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address, + DBUS_TYPE_INVALID) == FALSE) + return btd_error_invalid_args(msg); + + if (check_address(address) < 0) + return btd_error_invalid_args(msg); + + if (!adapter->up) + return btd_error_not_ready(msg); + + if (adapter_find_device(adapter, address)) + return btd_error_already_exists(msg); + + DBG("%s", address); + + device = create_device_internal(conn, adapter, address, TRUE, &err); + if (!device) + goto failed; + + if (device_get_type(device) != DEVICE_TYPE_LE) + err = device_browse_sdp(device, conn, msg, NULL, FALSE); + else + err = device_browse_primary(device, conn, msg, FALSE); + + if (err < 0) { + adapter_remove_device(conn, adapter, device, TRUE); + return btd_error_failed(msg, strerror(-err)); + } + + return NULL; + +failed: + if (err == -ENOTCONN) { + /* Device is not connectable */ + const char *path = device_get_path(device); + + reply = dbus_message_new_method_return(msg); + + dbus_message_append_args(reply, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + } else + reply = btd_error_failed(msg, strerror(-err)); + + return reply; +} + +static uint8_t parse_io_capability(const char *capability) +{ + if (g_str_equal(capability, "")) + return IO_CAPABILITY_DISPLAYYESNO; + if (g_str_equal(capability, "DisplayOnly")) + return IO_CAPABILITY_DISPLAYONLY; + if (g_str_equal(capability, "DisplayYesNo")) + return IO_CAPABILITY_DISPLAYYESNO; + if (g_str_equal(capability, "KeyboardOnly")) + return IO_CAPABILITY_KEYBOARDONLY; + if (g_str_equal(capability, "NoInputNoOutput")) + return IO_CAPABILITY_NOINPUTNOOUTPUT; + return IO_CAPABILITY_INVALID; +} + +static DBusMessage *create_paired_device(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct btd_adapter *adapter = data; + struct btd_device *device; + const gchar *address, *agent_path, *capability, *sender; + uint8_t cap; + int err; + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address, + DBUS_TYPE_OBJECT_PATH, &agent_path, + DBUS_TYPE_STRING, &capability, + DBUS_TYPE_INVALID) == FALSE) + return btd_error_invalid_args(msg); + + if (check_address(address) < 0) + return btd_error_invalid_args(msg); + + if (!adapter->up) + return btd_error_not_ready(msg); + + sender = dbus_message_get_sender(msg); + if (adapter->agent && + agent_matches(adapter->agent, sender, agent_path)) { + error("Refusing adapter agent usage as device specific one"); + return btd_error_invalid_args(msg); + } + + cap = parse_io_capability(capability); + if (cap == IO_CAPABILITY_INVALID) + return btd_error_invalid_args(msg); + + device = adapter_find_device(adapter, address); + if (!device) { + device = create_device_internal(conn, adapter, address, + FALSE, &err); + if (!device) + return btd_error_failed(msg, strerror(-err)); + } + + if (device_get_type(device) != DEVICE_TYPE_LE) + return device_create_bonding(device, conn, msg, + agent_path, cap); + + err = device_browse_primary(device, conn, msg, TRUE); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + return NULL; +} + +static gint device_path_cmp(struct btd_device *device, const gchar *path) +{ + const gchar *dev_path = device_get_path(device); + + return strcasecmp(dev_path, path); +} + +static DBusMessage *remove_device(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct btd_adapter *adapter = data; + struct btd_device *device; + const char *path; + GSList *l; + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID) == FALSE) + return btd_error_invalid_args(msg); + + l = g_slist_find_custom(adapter->devices, + path, (GCompareFunc) device_path_cmp); + if (!l) + return btd_error_does_not_exist(msg); + + device = l->data; + + if (device_is_temporary(device) || device_is_busy(device)) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".DoesNotExist", + "Device creation in progress"); + + device_set_temporary(device, TRUE); + + if (!device_is_connected(device)) { + adapter_remove_device(conn, adapter, device, TRUE); + return dbus_message_new_method_return(msg); + } + + device_request_disconnect(device, msg); + return NULL; +} + +static DBusMessage *find_device(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct btd_adapter *adapter = data; + struct btd_device *device; + DBusMessage *reply; + const gchar *address; + GSList *l; + const gchar *dev_path; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + l = g_slist_find_custom(adapter->devices, + address, (GCompareFunc) device_address_cmp); + if (!l) + return btd_error_does_not_exist(msg); + + device = l->data; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dev_path = device_get_path(device); + + dbus_message_append_args(reply, + DBUS_TYPE_OBJECT_PATH, &dev_path, + DBUS_TYPE_INVALID); + + return reply; +} + +static void agent_removed(struct agent *agent, struct btd_adapter *adapter) +{ + adapter_ops->set_io_capability(adapter->dev_id, + IO_CAPABILITY_NOINPUTNOOUTPUT); + + adapter->agent = NULL; +} + +static DBusMessage *register_agent(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + const char *path, *name, *capability; + struct agent *agent; + struct btd_adapter *adapter = data; + uint8_t cap; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_STRING, &capability, DBUS_TYPE_INVALID)) + return NULL; + + if (adapter->agent) + return btd_error_already_exists(msg); + + cap = parse_io_capability(capability); + if (cap == IO_CAPABILITY_INVALID) + return btd_error_invalid_args(msg); + + name = dbus_message_get_sender(msg); + + agent = agent_create(adapter, name, path, cap, + (agent_remove_cb) agent_removed, adapter); + if (!agent) + return btd_error_failed(msg, "Failed to create a new agent"); + + adapter->agent = agent; + + DBG("Agent registered for hci%d at %s:%s", adapter->dev_id, name, + path); + + adapter_ops->set_io_capability(adapter->dev_id, cap); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *unregister_agent(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + const char *path, *name; + struct btd_adapter *adapter = data; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return NULL; + + name = dbus_message_get_sender(msg); + + if (!adapter->agent || !agent_matches(adapter->agent, name, path)) + return btd_error_does_not_exist(msg); + + agent_free(adapter->agent); + adapter->agent = NULL; + + return dbus_message_new_method_return(msg); +} + +static GDBusMethodTable adapter_methods[] = { + { "GetProperties", "", "a{sv}",get_properties }, + { "SetProperty", "sv", "", set_property, + G_DBUS_METHOD_FLAG_ASYNC}, + { "RequestSession", "", "", request_session, + G_DBUS_METHOD_FLAG_ASYNC}, + { "ReleaseSession", "", "", release_session }, + { "StartDiscovery", "", "", adapter_start_discovery }, + { "StopDiscovery", "", "", adapter_stop_discovery, + G_DBUS_METHOD_FLAG_ASYNC}, + { "ListDevices", "", "ao", list_devices, + G_DBUS_METHOD_FLAG_DEPRECATED}, + { "CreateDevice", "s", "o", create_device, + G_DBUS_METHOD_FLAG_ASYNC}, + { "CreatePairedDevice", "sos", "o", create_paired_device, + G_DBUS_METHOD_FLAG_ASYNC}, + { "CancelDeviceCreation","s", "", cancel_device_creation, + G_DBUS_METHOD_FLAG_ASYNC}, + { "RemoveDevice", "o", "", remove_device, + G_DBUS_METHOD_FLAG_ASYNC}, + { "FindDevice", "s", "o", find_device }, + { "RegisterAgent", "os", "", register_agent }, + { "UnregisterAgent", "o", "", unregister_agent }, + { } +}; + +static GDBusSignalTable adapter_signals[] = { + { "PropertyChanged", "sv" }, + { "DeviceCreated", "o" }, + { "DeviceRemoved", "o" }, + { "DeviceFound", "sa{sv}" }, + { "DeviceDisappeared", "s" }, + { } +}; + +static void create_stored_device_from_profiles(char *key, char *value, + void *user_data) +{ + struct btd_adapter *adapter = user_data; + GSList *uuids = bt_string2list(value); + struct btd_device *device; + + if (g_slist_find_custom(adapter->devices, + key, (GCompareFunc) device_address_cmp)) + return; + + device = device_create(connection, adapter, key, DEVICE_TYPE_BREDR); + if (!device) + return; + + device_set_temporary(device, FALSE); + adapter->devices = g_slist_append(adapter->devices, device); + + device_probe_drivers(device, uuids); + + g_slist_foreach(uuids, (GFunc) g_free, NULL); + g_slist_free(uuids); +} + +struct adapter_keys { + struct btd_adapter *adapter; + GSList *keys; +}; + +static struct link_key_info *get_key_info(const char *addr, const char *value) +{ + struct link_key_info *info; + char tmp[3]; + long int l; + int i; + + if (strlen(value) < 36) { + error("Unexpectedly short (%zu) link key line", strlen(value)); + return NULL; + } + + info = g_new0(struct link_key_info, 1); + + str2ba(addr, &info->bdaddr); + + memset(tmp, 0, sizeof(tmp)); + + for (i = 0; i < 16; i++) { + memcpy(tmp, value + (i * 2), 2); + info->key[i] = (uint8_t) strtol(tmp, NULL, 16); + } + + memcpy(tmp, value + 33, 2); + info->type = (uint8_t) strtol(tmp, NULL, 10); + + memcpy(tmp, value + 35, 2); + l = strtol(tmp, NULL, 10); + if (l < 0) + l = 0; + info->pin_len = l; + + return info; +} + +static void create_stored_device_from_linkkeys(char *key, char *value, + void *user_data) +{ + struct adapter_keys *keys = user_data; + struct btd_adapter *adapter = keys->adapter; + struct btd_device *device; + struct link_key_info *info; + + info = get_key_info(key, value); + if (info) + keys->keys = g_slist_append(keys->keys, info); + + if (g_slist_find_custom(adapter->devices, key, + (GCompareFunc) device_address_cmp)) + return; + + device = device_create(connection, adapter, key, DEVICE_TYPE_BREDR); + if (device) { + device_set_temporary(device, FALSE); + adapter->devices = g_slist_append(adapter->devices, device); + } +} + +static void create_stored_device_from_blocked(char *key, char *value, + void *user_data) +{ + struct btd_adapter *adapter = user_data; + struct btd_device *device; + + if (g_slist_find_custom(adapter->devices, + key, (GCompareFunc) device_address_cmp)) + return; + + device = device_create(connection, adapter, key, DEVICE_TYPE_BREDR); + if (device) { + device_set_temporary(device, FALSE); + adapter->devices = g_slist_append(adapter->devices, device); + } +} + +static void create_stored_device_from_types(char *key, char *value, + void *user_data) +{ + GSList *l; + struct btd_adapter *adapter = user_data; + struct btd_device *device; + uint8_t type; + + type = strtol(value, NULL, 16); + + l = g_slist_find_custom(adapter->devices, + key, (GCompareFunc) device_address_cmp); + if (l) { + device = l->data; + device_set_type(device, type); + return; + } + + device = device_create(connection, adapter, key, type); + if (device) { + device_set_temporary(device, FALSE); + adapter->devices = g_slist_append(adapter->devices, device); + } +} + +static GSList *string_to_primary_list(char *str) +{ + GSList *l = NULL; + char **services; + int i; + + if (str == NULL) + return NULL; + + services = g_strsplit(str, " ", 0); + if (services == NULL) + return NULL; + + for (i = 0; services[i]; i++) { + struct att_primary *prim; + int ret; + + prim = g_new0(struct att_primary, 1); + + ret = sscanf(services[i], "%04hX#%04hX#%s", &prim->start, + &prim->end, prim->uuid); + + if (ret < 3) { + g_free(prim); + continue; + } + + l = g_slist_append(l, prim); + } + + g_strfreev(services); + + return l; +} + +static void create_stored_device_from_primary(char *key, char *value, + void *user_data) +{ + struct btd_adapter *adapter = user_data; + struct btd_device *device; + GSList *services, *uuids, *l; + + l = g_slist_find_custom(adapter->devices, + key, (GCompareFunc) device_address_cmp); + if (l) + device = l->data; + else { + device = device_create(connection, adapter, key, DEVICE_TYPE_BREDR); + if (!device) + return; + + device_set_temporary(device, FALSE); + adapter->devices = g_slist_append(adapter->devices, device); + } + + services = string_to_primary_list(value); + if (services == NULL) + return; + + for (l = services, uuids = NULL; l; l = l->next) { + struct att_primary *prim = l->data; + uuids = g_slist_append(uuids, prim->uuid); + + device_add_primary(device, prim); + } + + device_probe_drivers(device, uuids); + + g_slist_free(services); + g_slist_free(uuids); +} + +static void load_devices(struct btd_adapter *adapter) +{ + char filename[PATH_MAX + 1]; + char srcaddr[18]; + struct adapter_keys keys = { adapter, NULL }; + int err; + + ba2str(&adapter->bdaddr, srcaddr); + + create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "profiles"); + textfile_foreach(filename, create_stored_device_from_profiles, + adapter); + + create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "primary"); + textfile_foreach(filename, create_stored_device_from_primary, + adapter); + + create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "linkkeys"); + textfile_foreach(filename, create_stored_device_from_linkkeys, &keys); + + err = adapter_ops->load_keys(adapter->dev_id, keys.keys, + main_opts.debug_keys); + if (err < 0) { + error("Unable to load keys to adapter_ops: %s (%d)", + strerror(-err), -err); + g_slist_foreach(keys.keys, (GFunc) g_free, NULL); + g_slist_free(keys.keys); + } + + create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "blocked"); + textfile_foreach(filename, create_stored_device_from_blocked, adapter); + + create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "types"); + textfile_foreach(filename, create_stored_device_from_types, adapter); +} + +int btd_adapter_block_address(struct btd_adapter *adapter, bdaddr_t *bdaddr) +{ + return adapter_ops->block_device(adapter->dev_id, bdaddr); +} + +int btd_adapter_unblock_address(struct btd_adapter *adapter, bdaddr_t *bdaddr) +{ + return adapter_ops->unblock_device(adapter->dev_id, bdaddr); +} + +static void clear_blocked(struct btd_adapter *adapter) +{ + int err; + + err = adapter_ops->unblock_device(adapter->dev_id, BDADDR_ANY); + if (err < 0) + error("Clearing blocked list failed: %s (%d)", + strerror(-err), -err); +} + +static void probe_driver(struct btd_adapter *adapter, gpointer user_data) +{ + struct btd_adapter_driver *driver = user_data; + int err; + + if (!adapter->up) + return; + + if (driver->probe == NULL) + return; + + err = driver->probe(adapter); + if (err < 0) { + error("%s: %s (%d)", driver->name, strerror(-err), -err); + return; + } + + adapter->loaded_drivers = g_slist_prepend(adapter->loaded_drivers, + driver); +} + +static void load_drivers(struct btd_adapter *adapter) +{ + GSList *l; + + for (l = adapter_drivers; l; l = l->next) + probe_driver(adapter, l->data); +} + +static void load_connections(struct btd_adapter *adapter) +{ + GSList *l, *conns; + int err; + + err = adapter_ops->get_conn_list(adapter->dev_id, &conns); + if (err < 0) { + error("Unable to fetch existing connections: %s (%d)", + strerror(-err), -err); + return; + } + + for (l = conns; l != NULL; l = g_slist_next(l)) { + bdaddr_t *bdaddr = l->data; + struct btd_device *device; + char address[18]; + + ba2str(bdaddr, address); + DBG("Adding existing connection to %s", address); + + device = adapter_get_device(connection, adapter, address); + if (device) + adapter_add_connection(adapter, device); + } + + g_slist_foreach(conns, (GFunc) g_free, NULL); + g_slist_free(conns); +} + +static int get_discoverable_timeout(const char *src) +{ + int timeout; + + if (read_discoverable_timeout(src, &timeout) == 0) + return timeout; + + return main_opts.discovto; +} + +static int get_pairable_timeout(const char *src) +{ + int timeout; + + if (read_pairable_timeout(src, &timeout) == 0) + return timeout; + + return main_opts.pairto; +} + +static void call_adapter_powered_callbacks(struct btd_adapter *adapter, + gboolean powered) +{ + GSList *l; + + for (l = adapter->powered_callbacks; l; l = l->next) { + btd_adapter_powered_cb cb = l->data; + + cb(adapter, powered); + } +} + +static void emit_device_disappeared(gpointer data, gpointer user_data) +{ + struct remote_dev_info *dev = data; + struct btd_adapter *adapter = user_data; + char address[18]; + const char *paddr = address; + + ba2str(&dev->bdaddr, address); + + g_dbus_emit_signal(connection, adapter->path, + ADAPTER_INTERFACE, "DeviceDisappeared", + DBUS_TYPE_STRING, &paddr, + DBUS_TYPE_INVALID); + + adapter->found_devices = g_slist_remove(adapter->found_devices, dev); +} + +static void update_oor_devices(struct btd_adapter *adapter) +{ + g_slist_foreach(adapter->oor_devices, emit_device_disappeared, adapter); + g_slist_foreach(adapter->oor_devices, (GFunc) dev_info_free, NULL); + g_slist_free(adapter->oor_devices); + adapter->oor_devices = g_slist_copy(adapter->found_devices); +} + +static gboolean bredr_capable(struct btd_adapter *adapter) +{ + struct hci_dev *dev = &adapter->dev; + + return (dev->features[4] & LMP_NO_BREDR) == 0 ? TRUE : FALSE; +} + +static gboolean le_capable(struct btd_adapter *adapter) +{ + struct hci_dev *dev = &adapter->dev; + + return (dev->features[4] & LMP_LE && + dev->extfeatures[0] & LMP_HOST_LE) ? TRUE : FALSE; +} + +int adapter_get_discover_type(struct btd_adapter *adapter) +{ + gboolean le, bredr; + int type; + + le = le_capable(adapter); + bredr = bredr_capable(adapter); + + if (le) + type = bredr ? DISC_INTERLEAVE : DISC_LE; + else + type = main_opts.discov_interval ? DISC_STDINQ : + DISC_PINQ; + + if (main_opts.name_resolv) + type |= DISC_RESOLVNAME; + + return type; +} + +void btd_adapter_get_mode(struct btd_adapter *adapter, uint8_t *mode, + uint8_t *on_mode, gboolean *pairable) +{ + char str[14], address[18]; + + ba2str(&adapter->bdaddr, address); + + if (mode) { + if (main_opts.remember_powered == FALSE) + *mode = main_opts.mode; + else if (read_device_mode(address, str, sizeof(str)) == 0) + *mode = get_mode(&adapter->bdaddr, str); + else + *mode = main_opts.mode; + } + + if (on_mode) { + if (main_opts.remember_powered == FALSE) + *on_mode = get_mode(&adapter->bdaddr, "on"); + else if (read_on_mode(address, str, sizeof(str)) == 0) + *on_mode = get_mode(&adapter->bdaddr, str); + else + *on_mode = main_opts.mode; + } + + if (pairable) + *pairable = adapter->pairable; +} + +void btd_adapter_start(struct btd_adapter *adapter) +{ + char address[18]; + uint8_t cls[3]; + gboolean powered; + + ba2str(&adapter->bdaddr, address); + + adapter->dev_class = 0; + adapter->off_requested = FALSE; + adapter->up = TRUE; + adapter->discov_timeout = get_discoverable_timeout(address); + adapter->pairable_timeout = get_pairable_timeout(address); + adapter->state = STATE_IDLE; + adapter->mode = MODE_CONNECTABLE; + + if (main_opts.le) + adapter_ops->enable_le(adapter->dev_id); + + adapter_ops->set_name(adapter->dev_id, adapter->dev.name); + + if (read_local_class(&adapter->bdaddr, cls) < 0) { + uint32_t class = htobl(main_opts.class); + memcpy(cls, &class, 3); + } + + btd_adapter_set_class(adapter, cls[1], cls[0]); + + powered = TRUE; + emit_property_changed(connection, adapter->path, + ADAPTER_INTERFACE, "Powered", + DBUS_TYPE_BOOLEAN, &powered); + + call_adapter_powered_callbacks(adapter, TRUE); + + adapter_ops->disable_cod_cache(adapter->dev_id); + + info("Adapter %s has been enabled", adapter->path); +} + +static void reply_pending_requests(struct btd_adapter *adapter) +{ + GSList *l; + + if (!adapter) + return; + + /* pending bonding */ + for (l = adapter->devices; l; l = l->next) { + struct btd_device *device = l->data; + + if (device_is_bonding(device, NULL)) + device_cancel_bonding(device, + HCI_OE_USER_ENDED_CONNECTION); + } +} + +static void remove_driver(gpointer data, gpointer user_data) +{ + struct btd_adapter_driver *driver = data; + struct btd_adapter *adapter = user_data; + + if (driver->remove) + driver->remove(adapter); +} + +static void unload_drivers(struct btd_adapter *adapter) +{ + g_slist_foreach(adapter->loaded_drivers, remove_driver, adapter); + g_slist_free(adapter->loaded_drivers); + adapter->loaded_drivers = NULL; +} + +static void set_mode_complete(struct btd_adapter *adapter) +{ + struct session_req *pending; + const char *modestr; + int err; + + DBG(""); + + /* + * g_slist_free is not called after g_slist_foreach because the list is + * updated using g_slist_remove in session_remove which is called by + * session_free, which is called for each element by g_slist_foreach. + */ + if (adapter->mode == MODE_OFF) + g_slist_foreach(adapter->mode_sessions, (GFunc) session_free, + NULL); + + if (adapter->pending_mode == NULL) + return; + + pending = adapter->pending_mode; + adapter->pending_mode = NULL; + + err = (pending->mode != adapter->mode) ? -EINVAL : 0; + + if (pending->msg != NULL) { + DBusMessage *msg = pending->msg; + DBusMessage *reply; + + if (err < 0) + reply = btd_error_failed(msg, strerror(-err)); + else { + if (strcmp(dbus_message_get_member(msg), + "SetProperty") == 0) + adapter->global_mode = adapter->mode; + reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID); + } + + g_dbus_send_message(connection, reply); + } + + modestr = mode2str(adapter->mode); + + DBG("%s", modestr); + + /* restore if the mode doesn't matches the pending */ + if (err != 0) { + write_device_mode(&adapter->bdaddr, modestr); + error("unable to set mode: %s", mode2str(pending->mode)); + } + + session_unref(pending); +} + +int btd_adapter_stop(struct btd_adapter *adapter) +{ + gboolean powered, discoverable, pairable; + + /* cancel pending timeout */ + if (adapter->discov_timeout_id) { + g_source_remove(adapter->discov_timeout_id); + adapter->discov_timeout_id = 0; + } + + /* check pending requests */ + reply_pending_requests(adapter); + + stop_discovery(adapter, FALSE); + + if (adapter->disc_sessions) { + g_slist_foreach(adapter->disc_sessions, (GFunc) session_free, + NULL); + g_slist_free(adapter->disc_sessions); + adapter->disc_sessions = NULL; + } + + while (adapter->connections) { + struct btd_device *device = adapter->connections->data; + adapter_remove_connection(adapter, device); + } + + if (adapter->scan_mode == (SCAN_PAGE | SCAN_INQUIRY)) { + discoverable = FALSE; + emit_property_changed(connection, adapter->path, + ADAPTER_INTERFACE, "Discoverable", + DBUS_TYPE_BOOLEAN, &discoverable); + } + + if ((adapter->scan_mode & SCAN_PAGE) && adapter->pairable == TRUE) { + pairable = FALSE; + emit_property_changed(connection, adapter->path, + ADAPTER_INTERFACE, "Pairable", + DBUS_TYPE_BOOLEAN, &pairable); + } + + powered = FALSE; + emit_property_changed(connection, adapter->path, ADAPTER_INTERFACE, + "Powered", DBUS_TYPE_BOOLEAN, &powered); + + adapter->up = 0; + adapter->scan_mode = SCAN_DISABLED; + adapter->mode = MODE_OFF; + adapter->state = STATE_IDLE; + adapter->off_requested = FALSE; + adapter->name_stored = FALSE; + + call_adapter_powered_callbacks(adapter, FALSE); + + info("Adapter %s has been disabled", adapter->path); + + set_mode_complete(adapter); + + return 0; +} + +int adapter_update_ssp_mode(struct btd_adapter *adapter, uint8_t mode) +{ + struct hci_dev *dev = &adapter->dev; + + dev->ssp_mode = mode; + + return 0; +} + +static void adapter_free(gpointer user_data) +{ + struct btd_adapter *adapter = user_data; + + agent_free(adapter->agent); + adapter->agent = NULL; + + DBG("%p", adapter); + + if (adapter->auth_idle_id) + g_source_remove(adapter->auth_idle_id); + + sdp_list_free(adapter->services, NULL); + + g_slist_foreach(adapter->found_devices, (GFunc) dev_info_free, NULL); + g_slist_free(adapter->found_devices); + + g_slist_free(adapter->oor_devices); + + g_free(adapter->path); + g_free(adapter); +} + +struct btd_adapter *btd_adapter_ref(struct btd_adapter *adapter) +{ + adapter->ref++; + + DBG("%p: ref=%d", adapter, adapter->ref); + + return adapter; +} + +void btd_adapter_unref(struct btd_adapter *adapter) +{ + gchar *path; + + adapter->ref--; + + DBG("%p: ref=%d", adapter, adapter->ref); + + if (adapter->ref > 0) + return; + + path = g_strdup(adapter->path); + + g_dbus_unregister_interface(connection, path, ADAPTER_INTERFACE); + + g_free(path); +} + +gboolean adapter_init(struct btd_adapter *adapter) +{ + struct hci_version ver; + struct hci_dev *dev; + int err; + + /* adapter_ops makes sure that newly registered adapters always + * start off as powered */ + adapter->up = TRUE; + + adapter_ops->read_bdaddr(adapter->dev_id, &adapter->bdaddr); + + if (bacmp(&adapter->bdaddr, BDADDR_ANY) == 0) { + error("No address available for hci%d", adapter->dev_id); + return FALSE; + } + + err = adapter_ops->read_local_version(adapter->dev_id, &ver); + if (err < 0) { + error("Can't read version info for hci%d: %s (%d)", + adapter->dev_id, strerror(-err), -err); + return FALSE; + } + + dev = &adapter->dev; + + dev->hci_rev = ver.hci_rev; + dev->lmp_ver = ver.lmp_ver; + dev->lmp_subver = ver.lmp_subver; + dev->manufacturer = ver.manufacturer; + + err = adapter_ops->read_local_features(adapter->dev_id, dev->features); + if (err < 0) { + error("Can't read features for hci%d: %s (%d)", + adapter->dev_id, strerror(-err), -err); + return FALSE; + } + + if (read_local_name(&adapter->bdaddr, adapter->dev.name) < 0) + expand_name(adapter->dev.name, MAX_NAME_LENGTH, main_opts.name, + adapter->dev_id); + + if (main_opts.attrib_server) + attrib_gap_set(GATT_CHARAC_DEVICE_NAME, + (const uint8_t *) dev->name, strlen(dev->name)); + + sdp_init_services_list(&adapter->bdaddr); + load_drivers(adapter); + clear_blocked(adapter); + load_devices(adapter); + + /* Set pairable mode */ + if (read_device_pairable(&adapter->bdaddr, &adapter->pairable) < 0) + adapter->pairable = TRUE; + + /* retrieve the active connections: address the scenario where + * the are active connections before the daemon've started */ + load_connections(adapter); + + adapter->initialized = TRUE; + + return TRUE; +} + +struct btd_adapter *adapter_create(DBusConnection *conn, int id) +{ + char path[MAX_PATH_LENGTH]; + struct btd_adapter *adapter; + const char *base_path = manager_get_base_path(); + + if (!connection) + connection = conn; + + adapter = g_try_new0(struct btd_adapter, 1); + if (!adapter) { + error("adapter_create: failed to alloc memory for hci%d", id); + return NULL; + } + + adapter->dev_id = id; + + snprintf(path, sizeof(path), "%s/hci%d", base_path, id); + adapter->path = g_strdup(path); + + if (!g_dbus_register_interface(conn, path, ADAPTER_INTERFACE, + adapter_methods, adapter_signals, NULL, + adapter, adapter_free)) { + error("Adapter interface init failed on path %s", path); + adapter_free(adapter); + return NULL; + } + + return btd_adapter_ref(adapter); +} + +void adapter_remove(struct btd_adapter *adapter) +{ + GSList *l; + + DBG("Removing adapter %s", adapter->path); + + for (l = adapter->devices; l; l = l->next) + device_remove(l->data, FALSE); + g_slist_free(adapter->devices); + + unload_drivers(adapter); + + /* Return adapter to down state if it was not up on init */ + adapter_ops->restore_powered(adapter->dev_id); + + btd_adapter_unref(adapter); +} + +uint16_t adapter_get_dev_id(struct btd_adapter *adapter) +{ + return adapter->dev_id; +} + +const gchar *adapter_get_path(struct btd_adapter *adapter) +{ + if (!adapter) + return NULL; + + return adapter->path; +} + +void adapter_get_address(struct btd_adapter *adapter, bdaddr_t *bdaddr) +{ + bacpy(bdaddr, &adapter->bdaddr); +} + +void adapter_set_state(struct btd_adapter *adapter, int state) +{ + const char *path = adapter->path; + gboolean discov_active; + int previous, type; + + if (adapter->state == state) + return; + + previous = adapter->state; + adapter->state = state; + + type = adapter_get_discover_type(adapter); + + switch (state) { + case STATE_STDINQ: + case STATE_PINQ: + discov_active = TRUE; + + /* Started a new session while resolving names ? */ + if (previous & STATE_RESOLVNAME) + return; + break; + case STATE_LE_SCAN: + /* Scanning enabled */ + if (adapter->disc_sessions) { + adapter->stop_discov_id = g_timeout_add(5120, + stop_scanning, + adapter); + + /* For dual mode: don't send "Discovering = TRUE" */ + if (bredr_capable(adapter) == TRUE) + return; + } + + /* LE only */ + discov_active = TRUE; + + break; + case STATE_IDLE: + /* + * Interleave: from inquiry to scanning. Interleave is not + * applicable to requests triggered by external applications. + */ + if (adapter->disc_sessions && (type & DISC_INTERLEAVE) && + (previous & STATE_STDINQ)) { + adapter_ops->start_scanning(adapter->dev_id); + return; + } + /* BR/EDR only: inquiry finished */ + discov_active = FALSE; + break; + default: + discov_active = FALSE; + break; + } + + if (discov_active == FALSE) { + if (type & DISC_RESOLVNAME) { + if (adapter_resolve_names(adapter) == 0) { + adapter->state |= STATE_RESOLVNAME; + return; + } + } + + update_oor_devices(adapter); + } else if (adapter->disc_sessions && main_opts.discov_interval) + adapter->scheduler_id = g_timeout_add_seconds( + main_opts.discov_interval, + (GSourceFunc) start_discovery, + adapter); + + emit_property_changed(connection, path, + ADAPTER_INTERFACE, "Discovering", + DBUS_TYPE_BOOLEAN, &discov_active); +} + +int adapter_get_state(struct btd_adapter *adapter) +{ + return adapter->state; +} + +struct remote_dev_info *adapter_search_found_devices(struct btd_adapter *adapter, + struct remote_dev_info *match) +{ + GSList *l; + + l = g_slist_find_custom(adapter->found_devices, match, + (GCompareFunc) found_device_cmp); + if (l) + return l->data; + + return NULL; +} + +static int dev_rssi_cmp(struct remote_dev_info *d1, struct remote_dev_info *d2) +{ + int rssi1, rssi2; + + rssi1 = d1->rssi < 0 ? -d1->rssi : d1->rssi; + rssi2 = d2->rssi < 0 ? -d2->rssi : d2->rssi; + + return rssi1 - rssi2; +} + +static void append_dict_valist(DBusMessageIter *iter, + const char *first_key, + va_list var_args) +{ + DBusMessageIter dict; + const char *key; + int type; + int n_elements; + void *val; + + 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); + + key = first_key; + while (key) { + type = va_arg(var_args, int); + val = va_arg(var_args, void *); + if (type == DBUS_TYPE_ARRAY) { + n_elements = va_arg(var_args, int); + if (n_elements > 0) + dict_append_array(&dict, key, DBUS_TYPE_STRING, + val, n_elements); + } else + dict_append_entry(&dict, key, type, val); + key = va_arg(var_args, char *); + } + + dbus_message_iter_close_container(iter, &dict); +} + +static void emit_device_found(const char *path, const char *address, + const char *first_key, ...) +{ + DBusMessage *signal; + DBusMessageIter iter; + va_list var_args; + + signal = dbus_message_new_signal(path, ADAPTER_INTERFACE, + "DeviceFound"); + if (!signal) { + error("Unable to allocate new %s.DeviceFound signal", + ADAPTER_INTERFACE); + return; + } + dbus_message_iter_init_append(signal, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &address); + + va_start(var_args, first_key); + append_dict_valist(&iter, first_key, var_args); + va_end(var_args); + + g_dbus_send_message(connection, signal); +} + +static char **strlist2array(GSList *list) +{ + GSList *l; + unsigned int i, n; + char **array; + + if (list == NULL) + return NULL; + + n = g_slist_length(list); + array = g_new0(char *, n + 1); + + for (l = list, i = 0; l; l = l->next, i++) + array[i] = g_strdup((const gchar *) l->data); + + return array; +} + +void adapter_emit_device_found(struct btd_adapter *adapter, + struct remote_dev_info *dev) +{ + struct btd_device *device; + char peer_addr[18], local_addr[18]; + const char *icon, *paddr = peer_addr; + dbus_bool_t paired = FALSE; + dbus_int16_t rssi = dev->rssi; + char *alias; + size_t uuid_count; + + ba2str(&dev->bdaddr, peer_addr); + ba2str(&adapter->bdaddr, local_addr); + + device = adapter_find_device(adapter, paddr); + if (device) + paired = device_is_paired(device); + + /* The uuids string array is updated only if necessary */ + uuid_count = g_slist_length(dev->services); + if (dev->services && dev->uuid_count != uuid_count) { + g_strfreev(dev->uuids); + dev->uuids = strlist2array(dev->services); + dev->uuid_count = uuid_count; + } + + if (dev->le) { + gboolean broadcaster; + + if (dev->flags & (EIR_LIM_DISC | EIR_GEN_DISC)) + broadcaster = FALSE; + else + broadcaster = TRUE; + + emit_device_found(adapter->path, paddr, + "Address", DBUS_TYPE_STRING, &paddr, + "RSSI", DBUS_TYPE_INT16, &rssi, + "Name", DBUS_TYPE_STRING, &dev->name, + "Paired", DBUS_TYPE_BOOLEAN, &paired, + "Broadcaster", DBUS_TYPE_BOOLEAN, &broadcaster, + "UUIDs", DBUS_TYPE_ARRAY, &dev->uuids, uuid_count, + NULL); + return; + } + + icon = class_to_icon(dev->class); + + if (!dev->alias) { + if (!dev->name) { + alias = g_strdup(peer_addr); + g_strdelimit(alias, ":", '-'); + } else + alias = g_strdup(dev->name); + } else + alias = g_strdup(dev->alias); + + emit_device_found(adapter->path, paddr, + "Address", DBUS_TYPE_STRING, &paddr, + "Class", DBUS_TYPE_UINT32, &dev->class, + "Icon", DBUS_TYPE_STRING, &icon, + "RSSI", DBUS_TYPE_INT16, &rssi, + "Name", DBUS_TYPE_STRING, &dev->name, + "Alias", DBUS_TYPE_STRING, &alias, + "LegacyPairing", DBUS_TYPE_BOOLEAN, &dev->legacy, + "Paired", DBUS_TYPE_BOOLEAN, &paired, + "UUIDs", DBUS_TYPE_ARRAY, &dev->uuids, uuid_count, + NULL); + + g_free(alias); +} + +static struct remote_dev_info *get_found_dev(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, + gboolean *new_dev) +{ + struct remote_dev_info *dev, match; + + memset(&match, 0, sizeof(struct remote_dev_info)); + bacpy(&match.bdaddr, bdaddr); + match.name_status = NAME_ANY; + + dev = adapter_search_found_devices(adapter, &match); + if (dev) { + *new_dev = FALSE; + /* Out of range list update */ + adapter->oor_devices = g_slist_remove(adapter->oor_devices, + dev); + } else { + *new_dev = TRUE; + dev = g_new0(struct remote_dev_info, 1); + bacpy(&dev->bdaddr, bdaddr); + adapter->found_devices = g_slist_prepend(adapter->found_devices, + dev); + } + + return dev; +} + +static void remove_same_uuid(gpointer data, gpointer user_data) +{ + struct remote_dev_info *dev = user_data; + GSList *l; + + for (l = dev->services; l; l = l->next) { + char *current_uuid = l->data; + char *new_uuid = data; + + if (strcmp(current_uuid, new_uuid) == 0) { + g_free(current_uuid); + dev->services = g_slist_delete_link(dev->services, l); + break; + } + } +} + +static void dev_prepend_uuid(gpointer data, gpointer user_data) +{ + struct remote_dev_info *dev = user_data; + char *new_uuid = data; + + dev->services = g_slist_prepend(dev->services, g_strdup(new_uuid)); +} + +void adapter_update_device_from_info(struct btd_adapter *adapter, + bdaddr_t bdaddr, int8_t rssi, + uint8_t evt_type, const char *name, + GSList *services, int flags) +{ + struct remote_dev_info *dev; + gboolean new_dev; + + dev = get_found_dev(adapter, &bdaddr, &new_dev); + + if (new_dev) { + dev->le = TRUE; + dev->evt_type = evt_type; + } else if (dev->rssi == rssi) + return; + + dev->rssi = rssi; + + adapter->found_devices = g_slist_sort(adapter->found_devices, + (GCompareFunc) dev_rssi_cmp); + + g_slist_foreach(services, remove_same_uuid, dev); + g_slist_foreach(services, dev_prepend_uuid, dev); + + if (flags >= 0) + dev->flags = flags; + + if (name) { + g_free(dev->name); + dev->name = g_strdup(name); + } + + /* FIXME: check if other information was changed before emitting the + * signal */ + adapter_emit_device_found(adapter, dev); +} + +void adapter_update_found_devices(struct btd_adapter *adapter, bdaddr_t *bdaddr, + int8_t rssi, uint32_t class, const char *name, + const char *alias, gboolean legacy, + GSList *services, name_status_t name_status) +{ + struct remote_dev_info *dev; + gboolean new_dev; + + dev = get_found_dev(adapter, bdaddr, &new_dev); + + if (new_dev) { + if (name) + dev->name = g_strdup(name); + + if (alias) + dev->alias = g_strdup(alias); + + dev->le = FALSE; + dev->class = class; + dev->legacy = legacy; + dev->name_status = name_status; + } else if (dev->rssi == rssi) + return; + + dev->rssi = rssi; + + adapter->found_devices = g_slist_sort(adapter->found_devices, + (GCompareFunc) dev_rssi_cmp); + + g_slist_foreach(services, remove_same_uuid, dev); + g_slist_foreach(services, dev_prepend_uuid, dev); + + adapter_emit_device_found(adapter, dev); +} + +int adapter_remove_found_device(struct btd_adapter *adapter, bdaddr_t *bdaddr) +{ + struct remote_dev_info *dev, match; + + memset(&match, 0, sizeof(struct remote_dev_info)); + bacpy(&match.bdaddr, bdaddr); + + dev = adapter_search_found_devices(adapter, &match); + if (!dev) + return -1; + + dev->name_status = NAME_NOT_REQUIRED; + + return 0; +} + +void adapter_mode_changed(struct btd_adapter *adapter, uint8_t scan_mode) +{ + const gchar *path = adapter_get_path(adapter); + gboolean discoverable, pairable; + + DBG("old 0x%02x new 0x%02x", adapter->scan_mode, scan_mode); + + if (adapter->scan_mode == scan_mode) + return; + + adapter_remove_discov_timeout(adapter); + + switch (scan_mode) { + case SCAN_DISABLED: + adapter->mode = MODE_OFF; + discoverable = FALSE; + pairable = FALSE; + break; + case SCAN_PAGE: + adapter->mode = MODE_CONNECTABLE; + discoverable = FALSE; + pairable = adapter->pairable; + break; + case (SCAN_PAGE | SCAN_INQUIRY): + adapter->mode = MODE_DISCOVERABLE; + discoverable = TRUE; + pairable = adapter->pairable; + if (adapter->discov_timeout != 0) + adapter_set_discov_timeout(adapter, + adapter->discov_timeout); + break; + case SCAN_INQUIRY: + /* Address the scenario where a low-level application like + * hciconfig changed the scan mode */ + if (adapter->discov_timeout != 0) + adapter_set_discov_timeout(adapter, + adapter->discov_timeout); + + /* ignore, this event should not be sent */ + default: + /* ignore, reserved */ + return; + } + + /* If page scanning gets toggled emit the Pairable property */ + if ((adapter->scan_mode & SCAN_PAGE) != (scan_mode & SCAN_PAGE)) + emit_property_changed(connection, adapter->path, + ADAPTER_INTERFACE, "Pairable", + DBUS_TYPE_BOOLEAN, &pairable); + + if (!discoverable) + adapter_set_limited_discoverable(adapter, FALSE); + + emit_property_changed(connection, path, + ADAPTER_INTERFACE, "Discoverable", + DBUS_TYPE_BOOLEAN, &discoverable); + + adapter->scan_mode = scan_mode; + + set_mode_complete(adapter); +} + +struct agent *adapter_get_agent(struct btd_adapter *adapter) +{ + if (!adapter || !adapter->agent) + return NULL; + + return adapter->agent; +} + +void adapter_add_connection(struct btd_adapter *adapter, + struct btd_device *device) +{ + if (g_slist_find(adapter->connections, device)) { + error("Device is already marked as connected"); + return; + } + + device_add_connection(device, connection); + + adapter->connections = g_slist_append(adapter->connections, device); +} + +void adapter_remove_connection(struct btd_adapter *adapter, + struct btd_device *device) +{ + bdaddr_t bdaddr; + + DBG(""); + + if (!g_slist_find(adapter->connections, device)) { + error("No matching connection for device"); + return; + } + + device_remove_connection(device, connection); + + adapter->connections = g_slist_remove(adapter->connections, device); + + /* clean pending HCI cmds */ + device_get_address(device, &bdaddr); + + if (device_is_authenticating(device)) + device_cancel_authentication(device, TRUE); + + if (device_is_temporary(device)) { + const char *path = device_get_path(device); + + DBG("Removing temporary device %s", path); + adapter_remove_device(connection, adapter, device, TRUE); + } +} + +gboolean adapter_has_discov_sessions(struct btd_adapter *adapter) +{ + if (!adapter || !adapter->disc_sessions) + return FALSE; + + return TRUE; +} + +void adapter_suspend_discovery(struct btd_adapter *adapter) +{ + if (adapter->disc_sessions == NULL || + adapter->state & STATE_SUSPENDED) + return; + + DBG("Suspending discovery"); + + stop_discovery(adapter, TRUE); + adapter->state |= STATE_SUSPENDED; +} + +void adapter_resume_discovery(struct btd_adapter *adapter) +{ + if (adapter->disc_sessions == NULL) + return; + + DBG("Resuming discovery"); + + adapter->state &= ~STATE_SUSPENDED; + start_discovery(adapter); +} + +int btd_register_adapter_driver(struct btd_adapter_driver *driver) +{ + adapter_drivers = g_slist_append(adapter_drivers, driver); + + if (driver->probe == NULL) + return 0; + + manager_foreach_adapter(probe_driver, driver); + + return 0; +} + +static void unload_driver(struct btd_adapter *adapter, gpointer data) +{ + adapter->loaded_drivers = g_slist_remove(adapter->loaded_drivers, data); +} + +void btd_unregister_adapter_driver(struct btd_adapter_driver *driver) +{ + adapter_drivers = g_slist_remove(adapter_drivers, driver); + + manager_foreach_adapter(unload_driver, driver); +} + +static void agent_auth_cb(struct agent *agent, DBusError *derr, + void *user_data) +{ + struct service_auth *auth = user_data; + + device_set_authorizing(auth->device, FALSE); + + auth->cb(derr, auth->user_data); +} + +static gboolean auth_idle_cb(gpointer user_data) +{ + struct service_auth *auth = user_data; + struct btd_adapter *adapter = auth->adapter; + + adapter->auth_idle_id = 0; + + auth->cb(NULL, auth->user_data); + + return FALSE; +} + +static int adapter_authorize(struct btd_adapter *adapter, const bdaddr_t *dst, + const char *uuid, service_auth_cb cb, + void *user_data) +{ + struct service_auth *auth; + struct btd_device *device; + struct agent *agent; + char address[18]; + const gchar *dev_path; + int err; + + ba2str(dst, address); + device = adapter_find_device(adapter, address); + if (!device) + return -EPERM; + + /* Device connected? */ + if (!g_slist_find(adapter->connections, device)) + return -ENOTCONN; + + if (adapter->auth_idle_id) + return -EBUSY; + + auth = g_try_new0(struct service_auth, 1); + if (!auth) + return -ENOMEM; + + auth->cb = cb; + auth->user_data = user_data; + auth->device = device; + auth->adapter = adapter; + + if (device_is_trusted(device) == TRUE) { + adapter->auth_idle_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, + auth_idle_cb, auth, + g_free); + return 0; + } + + agent = device_get_agent(device); + if (!agent) { + g_free(auth); + return -EPERM; + } + + dev_path = device_get_path(device); + + err = agent_authorize(agent, dev_path, uuid, agent_auth_cb, auth, g_free); + if (err < 0) + g_free(auth); + else + device_set_authorizing(device, TRUE); + + return err; +} + +int btd_request_authorization(const bdaddr_t *src, const bdaddr_t *dst, + const char *uuid, service_auth_cb cb, + void *user_data) +{ + struct btd_adapter *adapter; + GSList *l; + + if (bacmp(src, BDADDR_ANY) != 0) { + adapter = manager_find_adapter(src); + if (!adapter) + return -EPERM; + + return adapter_authorize(adapter, dst, uuid, cb, user_data); + } + + for (l = manager_get_adapters(); l != NULL; l = g_slist_next(l)) { + int err; + + adapter = l->data; + + err = adapter_authorize(adapter, dst, uuid, cb, user_data); + if (err == 0) + return 0; + } + + return -EPERM; +} + +int btd_cancel_authorization(const bdaddr_t *src, const bdaddr_t *dst) +{ + struct btd_adapter *adapter = manager_find_adapter(src); + struct btd_device *device; + struct agent *agent; + char address[18]; + int err; + + if (!adapter) + return -EPERM; + + ba2str(dst, address); + device = adapter_find_device(adapter, address); + if (!device) + return -EPERM; + + if (adapter->auth_idle_id) { + g_source_remove(adapter->auth_idle_id); + adapter->auth_idle_id = 0; + return 0; + } + + /* + * FIXME: Cancel fails if authorization is requested to adapter's + * agent and in the meanwhile CreatePairedDevice is called. + */ + + agent = device_get_agent(device); + if (!agent) + return -EPERM; + + err = agent_cancel(agent); + + if (err == 0) + device_set_authorizing(device, FALSE); + + return err; +} + +static gchar *adapter_any_path = NULL; +static int adapter_any_refcount = 0; + +const char *adapter_any_get_path(void) +{ + return adapter_any_path; +} + +const char *btd_adapter_any_request_path(void) +{ + if (adapter_any_refcount++ > 0) + return adapter_any_path; + + adapter_any_path = g_strdup_printf("%s/any", manager_get_base_path()); + + return adapter_any_path; +} + +void btd_adapter_any_release_path(void) +{ + adapter_any_refcount--; + + if (adapter_any_refcount > 0) + return; + + g_free(adapter_any_path); + adapter_any_path = NULL; +} + +gboolean adapter_is_pairable(struct btd_adapter *adapter) +{ + return adapter->pairable; +} + +gboolean adapter_powering_down(struct btd_adapter *adapter) +{ + return adapter->off_requested; +} + +int btd_adapter_restore_powered(struct btd_adapter *adapter) +{ + char mode[14], address[18]; + gboolean discoverable; + + if (!adapter_ops) + return -EINVAL; + + if (!main_opts.remember_powered) + return -EINVAL; + + if (adapter->up) + return 0; + + ba2str(&adapter->bdaddr, address); + if (read_device_mode(address, mode, sizeof(mode)) == 0 && + g_str_equal(mode, "off")) + return 0; + + discoverable = get_mode(&adapter->bdaddr, mode) == MODE_DISCOVERABLE; + + return adapter_ops->set_powered(adapter->dev_id, TRUE); +} + +int btd_adapter_switch_online(struct btd_adapter *adapter) +{ + if (!adapter_ops) + return -EINVAL; + + if (adapter->up) + return 0; + + return adapter_ops->set_powered(adapter->dev_id, TRUE); +} + +int btd_adapter_switch_offline(struct btd_adapter *adapter) +{ + if (!adapter_ops) + return -EINVAL; + + if (!adapter->up) + return 0; + + return adapter_ops->set_powered(adapter->dev_id, FALSE); +} + +int btd_register_adapter_ops(struct btd_adapter_ops *ops, gboolean priority) +{ + if (ops->setup == NULL) + return -EINVAL; + + if (priority) + ops_candidates = g_slist_prepend(ops_candidates, ops); + else + ops_candidates = g_slist_append(ops_candidates, ops); + + return 0; +} + +void btd_adapter_cleanup_ops(struct btd_adapter_ops *ops) +{ + ops_candidates = g_slist_remove(ops_candidates, ops); + ops->cleanup(); + + if (adapter_ops == ops) + adapter_ops = NULL; +} + +int adapter_ops_setup(void) +{ + GSList *l; + int ret; + + if (!ops_candidates) + return -EINVAL; + + for (l = ops_candidates; l != NULL; l = g_slist_next(l)) { + struct btd_adapter_ops *ops = l->data; + + ret = ops->setup(); + if (ret < 0) + continue; + + adapter_ops = ops; + break; + } + + return ret; +} + +void btd_adapter_register_powered_callback(struct btd_adapter *adapter, + btd_adapter_powered_cb cb) +{ + adapter->powered_callbacks = + g_slist_append(adapter->powered_callbacks, cb); +} + +void btd_adapter_unregister_powered_callback(struct btd_adapter *adapter, + btd_adapter_powered_cb cb) +{ + adapter->powered_callbacks = + g_slist_remove(adapter->powered_callbacks, cb); +} + +int btd_adapter_set_fast_connectable(struct btd_adapter *adapter, + gboolean enable) +{ + if (!adapter_ops) + return -EINVAL; + + if (!adapter->up) + return -EINVAL; + + return adapter_ops->set_fast_connectable(adapter->dev_id, enable); +} + +int btd_adapter_read_clock(struct btd_adapter *adapter, bdaddr_t *bdaddr, + int which, int timeout, uint32_t *clock, + uint16_t *accuracy) +{ + if (!adapter_ops) + return -EINVAL; + + if (!adapter->up) + return -EINVAL; + + return adapter_ops->read_clock(adapter->dev_id, bdaddr, which, + timeout, clock, accuracy); +} + +int btd_adapter_disconnect_device(struct btd_adapter *adapter, bdaddr_t *bdaddr) +{ + return adapter_ops->disconnect(adapter->dev_id, bdaddr); +} + +int btd_adapter_remove_bonding(struct btd_adapter *adapter, bdaddr_t *bdaddr) +{ + return adapter_ops->remove_bonding(adapter->dev_id, bdaddr); +} + +int btd_adapter_pincode_reply(struct btd_adapter *adapter, bdaddr_t *bdaddr, + const char *pin) +{ + return adapter_ops->pincode_reply(adapter->dev_id, bdaddr, pin); +} + +int btd_adapter_confirm_reply(struct btd_adapter *adapter, bdaddr_t *bdaddr, + gboolean success) +{ + return adapter_ops->confirm_reply(adapter->dev_id, bdaddr, success); +} + +int btd_adapter_passkey_reply(struct btd_adapter *adapter, bdaddr_t *bdaddr, + uint32_t passkey) +{ + return adapter_ops->passkey_reply(adapter->dev_id, bdaddr, passkey); +} + +void btd_adapter_update_local_ext_features(struct btd_adapter *adapter, + const uint8_t *features) +{ + struct hci_dev *dev = &adapter->dev; + + memcpy(dev->extfeatures, features, 8); +} + +int btd_adapter_encrypt_link(struct btd_adapter *adapter, bdaddr_t *bdaddr, + bt_hci_result_t cb, gpointer user_data) +{ + return adapter_ops->encrypt_link(adapter->dev_id, bdaddr, cb, user_data); +} + +int btd_adapter_set_did(struct btd_adapter *adapter, uint16_t vendor, + uint16_t product, uint16_t version) +{ + return adapter_ops->set_did(adapter->dev_id, vendor, product, version); +} + +int adapter_create_bonding(struct btd_adapter *adapter, bdaddr_t *bdaddr, + uint8_t io_cap) +{ + return adapter_ops->create_bonding(adapter->dev_id, bdaddr, io_cap); +} + +int adapter_cancel_bonding(struct btd_adapter *adapter, bdaddr_t *bdaddr) +{ + return adapter_ops->cancel_bonding(adapter->dev_id, bdaddr); +} + +int btd_adapter_read_local_oob_data(struct btd_adapter *adapter) +{ + return adapter_ops->read_local_oob_data(adapter->dev_id); +} + +int btd_adapter_add_remote_oob_data(struct btd_adapter *adapter, + bdaddr_t *bdaddr, uint8_t *hash, uint8_t *randomizer) +{ + return adapter_ops->add_remote_oob_data(adapter->dev_id, bdaddr, hash, + randomizer); +} + +int btd_adapter_remove_remote_oob_data(struct btd_adapter *adapter, + bdaddr_t *bdaddr) +{ + return adapter_ops->remove_remote_oob_data(adapter->dev_id, bdaddr); +} diff --git a/src/adapter.h b/src/adapter.h new file mode 100644 index 0000000..308af75 --- /dev/null +++ b/src/adapter.h @@ -0,0 +1,302 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 + * + */ + +#include +#include +#include +#include +#include +#include + +#define ADAPTER_INTERFACE "org.bluez.Adapter" + +#define MODE_OFF 0x00 +#define MODE_CONNECTABLE 0x01 +#define MODE_DISCOVERABLE 0x02 +#define MODE_UNKNOWN 0xff + +/* Discover states */ +#define STATE_IDLE 0x00 +#define STATE_LE_SCAN 0x01 +#define STATE_STDINQ 0x02 +#define STATE_PINQ 0x04 +#define STATE_RESOLVNAME 0x08 +#define STATE_SUSPENDED 0x10 + +/* Supported host/controller discover type */ +#define DISC_LE 0x01 +#define DISC_STDINQ 0x02 +#define DISC_INTERLEAVE 0x04 +#define DISC_PINQ 0x08 +#define DISC_RESOLVNAME 0x10 + +#define MAX_NAME_LENGTH 248 + +/* Invalid SSP passkey value used to indicate negative replies */ +#define INVALID_PASSKEY 0xffffffff + +typedef enum { + NAME_ANY, + NAME_NOT_REQUIRED, /* used by get remote name without name resolving */ + NAME_REQUIRED, /* remote name needs be resolved */ + NAME_REQUESTED, /* HCI remote name request was sent */ +} name_status_t; + +struct btd_adapter; + +struct link_key_info { + bdaddr_t bdaddr; + unsigned char key[16]; + uint8_t type; + uint8_t pin_len; +}; + +struct remote_dev_info { + bdaddr_t bdaddr; + int8_t rssi; + uint32_t class; + char *name; + char *alias; + dbus_bool_t legacy; + name_status_t name_status; + gboolean le; + char **uuids; + size_t uuid_count; + GSList *services; + uint8_t evt_type; + uint8_t bdaddr_type; + uint8_t flags; +}; + +struct hci_dev { + uint8_t features[8]; + uint8_t extfeatures[8]; + uint8_t lmp_ver; + uint16_t lmp_subver; + uint16_t hci_rev; + uint16_t manufacturer; + + uint8_t ssp_mode; + char name[MAX_NAME_LENGTH + 1]; +}; + +void btd_adapter_start(struct btd_adapter *adapter); + +int btd_adapter_stop(struct btd_adapter *adapter); + +void btd_adapter_get_mode(struct btd_adapter *adapter, uint8_t *mode, + uint8_t *on_mode, gboolean *pairable); + +int adapter_update_ssp_mode(struct btd_adapter *adapter, uint8_t mode); + +struct btd_device *adapter_get_device(DBusConnection *conn, + struct btd_adapter *adapter, const char *address); + +struct btd_device *adapter_find_device(struct btd_adapter *adapter, const char *dest); + +void adapter_remove_device(DBusConnection *conn, struct btd_adapter *adapter, + struct btd_device *device, + gboolean remove_storage); + +int adapter_resolve_names(struct btd_adapter *adapter); + +struct btd_adapter *adapter_create(DBusConnection *conn, int id); +gboolean adapter_init(struct btd_adapter *adapter); +void adapter_remove(struct btd_adapter *adapter); +uint16_t adapter_get_dev_id(struct btd_adapter *adapter); +const gchar *adapter_get_path(struct btd_adapter *adapter); +void adapter_get_address(struct btd_adapter *adapter, bdaddr_t *bdaddr); +void adapter_set_state(struct btd_adapter *adapter, int state); +int adapter_get_state(struct btd_adapter *adapter); +int adapter_get_discover_type(struct btd_adapter *adapter); +struct remote_dev_info *adapter_search_found_devices(struct btd_adapter *adapter, + struct remote_dev_info *match); +void adapter_update_device_from_info(struct btd_adapter *adapter, + bdaddr_t bdaddr, int8_t rssi, + uint8_t evt_type, const char *name, + GSList *services, int flags); +void adapter_update_found_devices(struct btd_adapter *adapter, bdaddr_t *bdaddr, + int8_t rssi, uint32_t class, const char *name, + const char *alias, gboolean legacy, + GSList *services, name_status_t name_status); +int adapter_remove_found_device(struct btd_adapter *adapter, bdaddr_t *bdaddr); +void adapter_emit_device_found(struct btd_adapter *adapter, + struct remote_dev_info *dev); +void adapter_mode_changed(struct btd_adapter *adapter, uint8_t scan_mode); +void adapter_update_local_name(struct btd_adapter *adapter, const char *name); +void adapter_service_insert(struct btd_adapter *adapter, void *rec); +void adapter_service_remove(struct btd_adapter *adapter, void *rec); +void btd_adapter_class_changed(struct btd_adapter *adapter, + uint32_t new_class); +void btd_adapter_pairable_changed(struct btd_adapter *adapter, + gboolean pairable); + +struct agent *adapter_get_agent(struct btd_adapter *adapter); +void adapter_add_connection(struct btd_adapter *adapter, + struct btd_device *device); +void adapter_remove_connection(struct btd_adapter *adapter, + struct btd_device *device); +gboolean adapter_has_discov_sessions(struct btd_adapter *adapter); +void adapter_suspend_discovery(struct btd_adapter *adapter); +void adapter_resume_discovery(struct btd_adapter *adapter); + +struct btd_adapter *btd_adapter_ref(struct btd_adapter *adapter); +void btd_adapter_unref(struct btd_adapter *adapter); + +int btd_adapter_set_class(struct btd_adapter *adapter, uint8_t major, + uint8_t minor); + +struct btd_adapter_driver { + const char *name; + int (*probe) (struct btd_adapter *adapter); + void (*remove) (struct btd_adapter *adapter); +}; + +typedef void (*service_auth_cb) (DBusError *derr, void *user_data); + +int btd_register_adapter_driver(struct btd_adapter_driver *driver); +void btd_unregister_adapter_driver(struct btd_adapter_driver *driver); +int btd_request_authorization(const bdaddr_t *src, const bdaddr_t *dst, + const char *uuid, service_auth_cb cb, void *user_data); +int btd_cancel_authorization(const bdaddr_t *src, const bdaddr_t *dst); + +const char *adapter_any_get_path(void); + +const char *btd_adapter_any_request_path(void); +void btd_adapter_any_release_path(void); +gboolean adapter_is_pairable(struct btd_adapter *adapter); +gboolean adapter_powering_down(struct btd_adapter *adapter); + +int btd_adapter_restore_powered(struct btd_adapter *adapter); +int btd_adapter_switch_online(struct btd_adapter *adapter); +int btd_adapter_switch_offline(struct btd_adapter *adapter); + +typedef void (*bt_hci_result_t) (uint8_t status, gpointer user_data); + +struct btd_adapter_ops { + int (*setup) (void); + void (*cleanup) (void); + int (*set_powered) (int index, gboolean powered); + int (*set_discoverable) (int index, gboolean discoverable); + int (*set_pairable) (int index, gboolean pairable); + int (*set_limited_discoverable) (int index, gboolean limited); + int (*start_inquiry) (int index, uint8_t length, gboolean periodic); + int (*stop_inquiry) (int index); + int (*start_scanning) (int index); + int (*stop_scanning) (int index); + + int (*resolve_name) (int index, bdaddr_t *bdaddr); + int (*cancel_resolve_name) (int index, bdaddr_t *bdaddr); + int (*set_name) (int index, const char *name); + int (*set_dev_class) (int index, uint8_t major, uint8_t minor); + int (*set_fast_connectable) (int index, gboolean enable); + int (*read_clock) (int index, bdaddr_t *bdaddr, int which, int timeout, + uint32_t *clock, uint16_t *accuracy); + int (*read_bdaddr) (int index, bdaddr_t *bdaddr); + int (*block_device) (int index, bdaddr_t *bdaddr); + int (*unblock_device) (int index, bdaddr_t *bdaddr); + int (*get_conn_list) (int index, GSList **conns); + int (*read_local_version) (int index, struct hci_version *ver); + int (*read_local_features) (int index, uint8_t *features); + int (*disconnect) (int index, bdaddr_t *bdaddr); + int (*remove_bonding) (int index, bdaddr_t *bdaddr); + int (*pincode_reply) (int index, bdaddr_t *bdaddr, const char *pin); + int (*confirm_reply) (int index, bdaddr_t *bdaddr, gboolean success); + int (*passkey_reply) (int index, bdaddr_t *bdaddr, uint32_t passkey); + int (*enable_le) (int index); + int (*encrypt_link) (int index, bdaddr_t *bdaddr, bt_hci_result_t cb, + gpointer user_data); + int (*set_did) (int index, uint16_t vendor, uint16_t product, + uint16_t version); + int (*add_uuid) (int index, uuid_t *uuid, uint8_t svc_hint); + int (*remove_uuid) (int index, uuid_t *uuid); + int (*disable_cod_cache) (int index); + int (*restore_powered) (int index); + int (*load_keys) (int index, GSList *keys, gboolean debug_keys); + int (*set_io_capability) (int index, uint8_t io_capability); + int (*create_bonding) (int index, bdaddr_t *bdaddr, uint8_t io_cap); + int (*cancel_bonding) (int index, bdaddr_t *bdaddr); + int (*read_local_oob_data) (int index); + int (*add_remote_oob_data) (int index, bdaddr_t *bdaddr, uint8_t *hash, + uint8_t *randomizer); + int (*remove_remote_oob_data) (int index, bdaddr_t *bdaddr); +}; + +int btd_register_adapter_ops(struct btd_adapter_ops *ops, gboolean priority); +void btd_adapter_cleanup_ops(struct btd_adapter_ops *btd_adapter_ops); +int adapter_ops_setup(void); + +typedef void (*btd_adapter_powered_cb) (struct btd_adapter *adapter, + gboolean powered); +void btd_adapter_register_powered_callback(struct btd_adapter *adapter, + btd_adapter_powered_cb cb); +void btd_adapter_unregister_powered_callback(struct btd_adapter *adapter, + btd_adapter_powered_cb cb); + +/* If TRUE, enables fast connectabe, i.e. reduces page scan interval and changes + * type. If FALSE, disables fast connectable, i.e. sets page scan interval and + * type to default values. Valid for both connectable and discoverable modes. */ +int btd_adapter_set_fast_connectable(struct btd_adapter *adapter, + gboolean enable); + +int btd_adapter_read_clock(struct btd_adapter *adapter, bdaddr_t *bdaddr, + int which, int timeout, uint32_t *clock, + uint16_t *accuracy); + +int btd_adapter_block_address(struct btd_adapter *adapter, bdaddr_t *bdaddr); +int btd_adapter_unblock_address(struct btd_adapter *adapter, bdaddr_t *bdaddr); + +int btd_adapter_disconnect_device(struct btd_adapter *adapter, + bdaddr_t *bdaddr); + +int btd_adapter_remove_bonding(struct btd_adapter *adapter, bdaddr_t *bdaddr); + +int btd_adapter_pincode_reply(struct btd_adapter *adapter, bdaddr_t *bdaddr, + const char *pin); +int btd_adapter_confirm_reply(struct btd_adapter *adapter, bdaddr_t *bdaddr, + gboolean success); +int btd_adapter_passkey_reply(struct btd_adapter *adapter, bdaddr_t *bdaddr, + uint32_t passkey); + +void btd_adapter_update_local_ext_features(struct btd_adapter *adapter, + const uint8_t *features); + +int btd_adapter_encrypt_link(struct btd_adapter *adapter, bdaddr_t *bdaddr, + bt_hci_result_t cb, gpointer user_data); + +int btd_adapter_set_did(struct btd_adapter *adapter, uint16_t vendor, + uint16_t product, uint16_t version); + +int adapter_create_bonding(struct btd_adapter *adapter, bdaddr_t *bdaddr, + uint8_t io_cap); + +int adapter_cancel_bonding(struct btd_adapter *adapter, bdaddr_t *bdaddr); + +int btd_adapter_read_local_oob_data(struct btd_adapter *adapter); + +int btd_adapter_add_remote_oob_data(struct btd_adapter *adapter, + bdaddr_t *bdaddr, uint8_t *hash, uint8_t *randomizer); + +int btd_adapter_remove_remote_oob_data(struct btd_adapter *adapter, + bdaddr_t *bdaddr); diff --git a/src/agent.c b/src/agent.c new file mode 100644 index 0000000..f87f253 --- /dev/null +++ b/src/agent.c @@ -0,0 +1,794 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 +#endif + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "log.h" + +#include "hcid.h" +#include "adapter.h" +#include "device.h" +#include "agent.h" + +#define REQUEST_TIMEOUT (60 * 1000) /* 60 seconds */ + +typedef enum { + AGENT_REQUEST_PASSKEY, + AGENT_REQUEST_CONFIRMATION, + AGENT_REQUEST_PINCODE, + AGENT_REQUEST_AUTHORIZE, + AGENT_REQUEST_CONFIRM_MODE +} agent_request_type_t; + +struct agent { + struct btd_adapter *adapter; + char *name; + char *path; + uint8_t capability; + struct agent_request *request; + int exited; + agent_remove_cb remove_cb; + void *remove_cb_data; + guint listener_id; +}; + +struct agent_request { + agent_request_type_t type; + struct agent *agent; + DBusMessage *msg; + DBusPendingCall *call; + void *cb; + void *user_data; + GDestroyNotify destroy; +}; + +static DBusConnection *connection = NULL; + +static int request_fallback(struct agent_request *req, + DBusPendingCallNotifyFunction function); + +static void agent_release(struct agent *agent) +{ + DBusMessage *message; + + DBG("Releasing agent %s, %s", agent->name, agent->path); + + if (agent->request) + agent_cancel(agent); + + message = dbus_message_new_method_call(agent->name, agent->path, + "org.bluez.Agent", "Release"); + if (message == NULL) { + error("Couldn't allocate D-Bus message"); + return; + } + + g_dbus_send_message(connection, message); +} + +static int send_cancel_request(struct agent_request *req) +{ + DBusMessage *message; + + DBG("Sending Cancel request to %s, %s", req->agent->name, + req->agent->path); + + message = dbus_message_new_method_call(req->agent->name, req->agent->path, + "org.bluez.Agent", "Cancel"); + if (message == NULL) { + error("Couldn't allocate D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(connection, message); + + return 0; +} + +static void agent_request_free(struct agent_request *req, gboolean destroy) +{ + if (req->msg) + dbus_message_unref(req->msg); + if (req->call) + dbus_pending_call_unref(req->call); + if (req->agent && req->agent->request) + req->agent->request = NULL; + if (destroy && req->destroy) + req->destroy(req->user_data); + g_free(req); +} + +static void agent_exited(DBusConnection *conn, void *user_data) +{ + struct agent *agent = user_data; + + DBG("Agent exited without calling Unregister"); + + agent->exited = TRUE; + + agent_free(agent); +} + +void agent_free(struct agent *agent) +{ + if (!agent) + return; + + if (agent->remove_cb) + agent->remove_cb(agent, agent->remove_cb_data); + + if (agent->request) { + DBusError err; + agent_pincode_cb pincode_cb; + agent_cb cb; + + dbus_error_init(&err); + dbus_set_error_const(&err, "org.bluez.Error.Failed", "Canceled"); + + switch (agent->request->type) { + case AGENT_REQUEST_PINCODE: + pincode_cb = agent->request->cb; + pincode_cb(agent, &err, NULL, agent->request->user_data); + break; + default: + cb = agent->request->cb; + cb(agent, &err, agent->request->user_data); + } + + dbus_error_free(&err); + + agent_cancel(agent); + } + + if (!agent->exited) { + g_dbus_remove_watch(connection, agent->listener_id); + agent_release(agent); + } + + g_free(agent->name); + g_free(agent->path); + + g_free(agent); +} + +struct agent *agent_create(struct btd_adapter *adapter, const char *name, + const char *path, uint8_t capability, + agent_remove_cb cb, void *remove_cb_data) +{ + struct agent *agent; + + agent = g_new0(struct agent, 1); + + agent->adapter = adapter; + agent->name = g_strdup(name); + agent->path = g_strdup(path); + agent->capability = capability; + agent->remove_cb = cb; + agent->remove_cb_data = remove_cb_data; + + agent->listener_id = g_dbus_add_disconnect_watch(connection, name, + agent_exited, agent, + NULL); + + return agent; +} + +static struct agent_request *agent_request_new(struct agent *agent, + agent_request_type_t type, + void *cb, + void *user_data, + GDestroyNotify destroy) +{ + struct agent_request *req; + + req = g_new0(struct agent_request, 1); + + req->agent = agent; + req->type = type; + req->cb = cb; + req->user_data = user_data; + req->destroy = destroy; + + return req; +} + +int agent_cancel(struct agent *agent) +{ + if (!agent->request) + return -EINVAL; + + if (agent->request->call) + dbus_pending_call_cancel(agent->request->call); + + if (!agent->exited) + send_cancel_request(agent->request); + + agent_request_free(agent->request, TRUE); + agent->request = NULL; + + return 0; +} + +static void simple_agent_reply(DBusPendingCall *call, void *user_data) +{ + struct agent_request *req = user_data; + struct agent *agent = req->agent; + DBusMessage *message; + DBusError err; + agent_cb cb = req->cb; + + /* steal_reply will always return non-NULL since the callback + * is only called after a reply has been received */ + message = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, message)) { + if ((g_str_equal(DBUS_ERROR_UNKNOWN_METHOD, err.name) || + g_str_equal(DBUS_ERROR_NO_REPLY, err.name)) && + request_fallback(req, simple_agent_reply) == 0) { + dbus_error_free(&err); + return; + } + + error("Agent replied with an error: %s, %s", + err.name, err.message); + + cb(agent, &err, req->user_data); + + if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY)) { + agent_cancel(agent); + dbus_message_unref(message); + dbus_error_free(&err); + return; + } + + dbus_error_free(&err); + goto done; + } + + dbus_error_init(&err); + if (!dbus_message_get_args(message, &err, DBUS_TYPE_INVALID)) { + error("Wrong reply signature: %s", err.message); + cb(agent, &err, req->user_data); + dbus_error_free(&err); + goto done; + } + + cb(agent, NULL, req->user_data); +done: + dbus_message_unref(message); + + agent->request = NULL; + agent_request_free(req, TRUE); +} + +static int agent_call_authorize(struct agent_request *req, + const char *device_path, + const char *uuid) +{ + struct agent *agent = req->agent; + + req->msg = dbus_message_new_method_call(agent->name, agent->path, + "org.bluez.Agent", "Authorize"); + if (!req->msg) { + error("Couldn't allocate D-Bus message"); + return -ENOMEM; + } + + dbus_message_append_args(req->msg, + DBUS_TYPE_OBJECT_PATH, &device_path, + DBUS_TYPE_STRING, &uuid, + DBUS_TYPE_INVALID); + + if (dbus_connection_send_with_reply(connection, req->msg, + &req->call, REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + return -EIO; + } + + dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL); + return 0; +} + +int agent_authorize(struct agent *agent, + const char *path, + const char *uuid, + agent_cb cb, + void *user_data, + GDestroyNotify destroy) +{ + struct agent_request *req; + int err; + + if (agent->request) + return -EBUSY; + + req = agent_request_new(agent, AGENT_REQUEST_AUTHORIZE, cb, + user_data, destroy); + + err = agent_call_authorize(req, path, uuid); + if (err < 0) { + agent_request_free(req, FALSE); + return -ENOMEM; + } + + agent->request = req; + + DBG("authorize request was sent for %s", path); + + return 0; +} + +static void pincode_reply(DBusPendingCall *call, void *user_data) +{ + struct agent_request *req = user_data; + struct agent *agent = req->agent; + struct btd_adapter *adapter = agent->adapter; + agent_pincode_cb cb = req->cb; + DBusMessage *message; + DBusError err; + bdaddr_t sba; + size_t len; + char *pin; + + adapter_get_address(adapter, &sba); + + /* steal_reply will always return non-NULL since the callback + * is only called after a reply has been received */ + message = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, message)) { + if ((g_str_equal(DBUS_ERROR_UNKNOWN_METHOD, err.name) || + g_str_equal(DBUS_ERROR_NO_REPLY, err.name)) && + request_fallback(req, pincode_reply) == 0) { + dbus_error_free(&err); + return; + } + + error("Agent replied with an error: %s, %s", + err.name, err.message); + + cb(agent, &err, NULL, req->user_data); + dbus_error_free(&err); + goto done; + } + + dbus_error_init(&err); + if (!dbus_message_get_args(message, &err, + DBUS_TYPE_STRING, &pin, + DBUS_TYPE_INVALID)) { + error("Wrong passkey reply signature: %s", err.message); + cb(agent, &err, NULL, req->user_data); + dbus_error_free(&err); + goto done; + } + + len = strlen(pin); + + dbus_error_init(&err); + if (len > 16 || len < 1) { + error("Invalid PIN length (%zu) from agent", len); + dbus_set_error_const(&err, "org.bluez.Error.InvalidArgs", + "Invalid passkey length"); + cb(agent, &err, NULL, req->user_data); + dbus_error_free(&err); + goto done; + } + + cb(agent, NULL, pin, req->user_data); + +done: + if (message) + dbus_message_unref(message); + + dbus_pending_call_cancel(req->call); + agent->request = NULL; + agent_request_free(req, TRUE); +} + +static int pincode_request_new(struct agent_request *req, const char *device_path, + dbus_bool_t numeric) +{ + struct agent *agent = req->agent; + + req->msg = dbus_message_new_method_call(agent->name, agent->path, + "org.bluez.Agent", "RequestPinCode"); + if (req->msg == NULL) { + error("Couldn't allocate D-Bus message"); + return -ENOMEM; + } + + dbus_message_append_args(req->msg, DBUS_TYPE_OBJECT_PATH, &device_path, + DBUS_TYPE_INVALID); + + if (dbus_connection_send_with_reply(connection, req->msg, + &req->call, REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + return -EIO; + } + + dbus_pending_call_set_notify(req->call, pincode_reply, req, NULL); + return 0; +} + +int agent_request_pincode(struct agent *agent, struct btd_device *device, + agent_pincode_cb cb, void *user_data, + GDestroyNotify destroy) +{ + struct agent_request *req; + const gchar *dev_path = device_get_path(device); + int err; + + if (agent->request) + return -EBUSY; + + req = agent_request_new(agent, AGENT_REQUEST_PINCODE, cb, + user_data, destroy); + + err = pincode_request_new(req, dev_path, FALSE); + if (err < 0) + goto failed; + + agent->request = req; + + return 0; + +failed: + g_free(req); + return err; +} + +static int confirm_mode_change_request_new(struct agent_request *req, + const char *mode) +{ + struct agent *agent = req->agent; + + req->msg = dbus_message_new_method_call(agent->name, agent->path, + "org.bluez.Agent", "ConfirmModeChange"); + if (req->msg == NULL) { + error("Couldn't allocate D-Bus message"); + return -ENOMEM; + } + + dbus_message_append_args(req->msg, + DBUS_TYPE_STRING, &mode, + DBUS_TYPE_INVALID); + + if (dbus_connection_send_with_reply(connection, req->msg, + &req->call, REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + return -EIO; + } + + dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL); + return 0; +} + +int agent_confirm_mode_change(struct agent *agent, const char *new_mode, + agent_cb cb, void *user_data, + GDestroyNotify destroy) +{ + struct agent_request *req; + int err; + + if (agent->request) + return -EBUSY; + + DBG("Calling Agent.ConfirmModeChange: name=%s, path=%s, mode=%s", + agent->name, agent->path, new_mode); + + req = agent_request_new(agent, AGENT_REQUEST_CONFIRM_MODE, + cb, user_data, destroy); + + err = confirm_mode_change_request_new(req, new_mode); + if (err < 0) + goto failed; + + agent->request = req; + + return 0; + +failed: + agent_request_free(req, FALSE); + return err; +} + +static void passkey_reply(DBusPendingCall *call, void *user_data) +{ + struct agent_request *req = user_data; + struct agent *agent = req->agent; + agent_passkey_cb cb = req->cb; + DBusMessage *message; + DBusError err; + uint32_t passkey; + + /* steal_reply will always return non-NULL since the callback + * is only called after a reply has been received */ + message = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, message)) { + if ((g_str_equal(DBUS_ERROR_UNKNOWN_METHOD, err.name) || + g_str_equal(DBUS_ERROR_NO_REPLY, err.name)) && + request_fallback(req, passkey_reply) == 0) { + dbus_error_free(&err); + return; + } + + error("Agent replied with an error: %s, %s", + err.name, err.message); + cb(agent, &err, 0, req->user_data); + dbus_error_free(&err); + goto done; + } + + dbus_error_init(&err); + if (!dbus_message_get_args(message, &err, + DBUS_TYPE_UINT32, &passkey, + DBUS_TYPE_INVALID)) { + error("Wrong passkey reply signature: %s", err.message); + cb(agent, &err, 0, req->user_data); + dbus_error_free(&err); + goto done; + } + + cb(agent, NULL, passkey, req->user_data); + +done: + if (message) + dbus_message_unref(message); + + dbus_pending_call_cancel(req->call); + agent->request = NULL; + agent_request_free(req, TRUE); +} + +static int passkey_request_new(struct agent_request *req, + const char *device_path) +{ + struct agent *agent = req->agent; + + req->msg = dbus_message_new_method_call(agent->name, agent->path, + "org.bluez.Agent", "RequestPasskey"); + if (req->msg == NULL) { + error("Couldn't allocate D-Bus message"); + return -ENOMEM; + } + + dbus_message_append_args(req->msg, DBUS_TYPE_OBJECT_PATH, &device_path, + DBUS_TYPE_INVALID); + + if (dbus_connection_send_with_reply(connection, req->msg, + &req->call, REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + return -EIO; + } + + dbus_pending_call_set_notify(req->call, passkey_reply, req, NULL); + return 0; +} + +int agent_request_passkey(struct agent *agent, struct btd_device *device, + agent_passkey_cb cb, void *user_data, + GDestroyNotify destroy) +{ + struct agent_request *req; + const gchar *dev_path = device_get_path(device); + int err; + + if (agent->request) + return -EBUSY; + + DBG("Calling Agent.RequestPasskey: name=%s, path=%s", + agent->name, agent->path); + + req = agent_request_new(agent, AGENT_REQUEST_PASSKEY, cb, + user_data, destroy); + + err = passkey_request_new(req, dev_path); + if (err < 0) + goto failed; + + agent->request = req; + + return 0; + +failed: + agent_request_free(req, FALSE); + return err; +} + +static int confirmation_request_new(struct agent_request *req, + const char *device_path, + uint32_t passkey) +{ + struct agent *agent = req->agent; + + req->msg = dbus_message_new_method_call(agent->name, agent->path, + "org.bluez.Agent", "RequestConfirmation"); + if (req->msg == NULL) { + error("Couldn't allocate D-Bus message"); + return -ENOMEM; + } + + dbus_message_append_args(req->msg, + DBUS_TYPE_OBJECT_PATH, &device_path, + DBUS_TYPE_UINT32, &passkey, + DBUS_TYPE_INVALID); + + if (dbus_connection_send_with_reply(connection, req->msg, + &req->call, REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + return -EIO; + } + + dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL); + + return 0; +} + +int agent_request_confirmation(struct agent *agent, struct btd_device *device, + uint32_t passkey, agent_cb cb, + void *user_data, GDestroyNotify destroy) +{ + struct agent_request *req; + const gchar *dev_path = device_get_path(device); + int err; + + if (agent->request) + return -EBUSY; + + DBG("Calling Agent.RequestConfirmation: name=%s, path=%s, passkey=%06u", + agent->name, agent->path, passkey); + + req = agent_request_new(agent, AGENT_REQUEST_CONFIRMATION, cb, + user_data, destroy); + + err = confirmation_request_new(req, dev_path, passkey); + if (err < 0) + goto failed; + + agent->request = req; + + return 0; + +failed: + agent_request_free(req, FALSE); + return err; +} + +static int request_fallback(struct agent_request *req, + DBusPendingCallNotifyFunction function) +{ + struct btd_adapter *adapter = req->agent->adapter; + struct agent *adapter_agent = adapter_get_agent(adapter); + DBusMessage *msg; + + if (req->agent == adapter_agent || adapter_agent == NULL) + return -EINVAL; + + dbus_pending_call_cancel(req->call); + dbus_pending_call_unref(req->call); + + msg = dbus_message_copy(req->msg); + + dbus_message_set_destination(msg, adapter_agent->name); + dbus_message_set_path(msg, adapter_agent->path); + + if (dbus_connection_send_with_reply(connection, msg, + &req->call, REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + dbus_message_unref(msg); + return -EIO; + } + + req->agent->request = NULL; + req->agent = adapter_agent; + req->agent->request = req; + + dbus_message_unref(req->msg); + req->msg = msg; + + dbus_pending_call_set_notify(req->call, function, req, NULL); + + return 0; +} + +int agent_display_passkey(struct agent *agent, struct btd_device *device, + uint32_t passkey) +{ + DBusMessage *message; + const gchar *dev_path = device_get_path(device); + + message = dbus_message_new_method_call(agent->name, agent->path, + "org.bluez.Agent", "DisplayPasskey"); + if (!message) { + error("Couldn't allocate D-Bus message"); + return -1; + } + + dbus_message_append_args(message, + DBUS_TYPE_OBJECT_PATH, &dev_path, + DBUS_TYPE_UINT32, &passkey, + DBUS_TYPE_INVALID); + + if (!g_dbus_send_message(connection, message)) { + error("D-Bus send failed"); + dbus_message_unref(message); + return -1; + } + + return 0; +} + +uint8_t agent_get_io_capability(struct agent *agent) +{ + return agent->capability; +} + +gboolean agent_matches(struct agent *agent, const char *name, const char *path) +{ + if (g_str_equal(agent->name, name) && g_str_equal(agent->path, path)) + return TRUE; + + return FALSE; +} + +gboolean agent_is_busy(struct agent *agent, void *user_data) +{ + if (!agent->request) + return FALSE; + + if (user_data && user_data != agent->request->user_data) + return FALSE; + + return TRUE; +} + +void agent_exit(void) +{ + dbus_connection_unref(connection); + connection = NULL; +} + +void agent_init(void) +{ + connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL); +} diff --git a/src/agent.h b/src/agent.h new file mode 100644 index 0000000..e184250 --- /dev/null +++ b/src/agent.h @@ -0,0 +1,77 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 + * + */ + +struct agent; + +typedef void (*agent_cb) (struct agent *agent, DBusError *err, + void *user_data); + +typedef void (*agent_pincode_cb) (struct agent *agent, DBusError *err, + const char *pincode, void *user_data); + +typedef void (*agent_passkey_cb) (struct agent *agent, DBusError *err, + uint32_t passkey, void *user_data); + +typedef void (*agent_remove_cb) (struct agent *agent, void *user_data); + +struct agent *agent_create(struct btd_adapter *adapter, const char *name, + const char *path, uint8_t capability, + agent_remove_cb cb, void *remove_cb_data); + +void agent_free(struct agent *agent); + +int agent_authorize(struct agent *agent, const char *path, + const char *uuid, agent_cb cb, void *user_data, + GDestroyNotify destroy); + +int agent_request_pincode(struct agent *agent, struct btd_device *device, + agent_pincode_cb cb, void *user_data, + GDestroyNotify destroy); + +int agent_confirm_mode_change(struct agent *agent, const char *new_mode, + agent_cb cb, void *user_data, + GDestroyNotify destroy); + +int agent_request_passkey(struct agent *agent, struct btd_device *device, + agent_passkey_cb cb, void *user_data, + GDestroyNotify destroy); + +int agent_request_confirmation(struct agent *agent, struct btd_device *device, + uint32_t passkey, agent_cb cb, + void *user_data, GDestroyNotify destroy); + +int agent_display_passkey(struct agent *agent, struct btd_device *device, + uint32_t passkey); + +int agent_cancel(struct agent *agent); + +gboolean agent_is_busy(struct agent *agent, void *user_data); + +uint8_t agent_get_io_capability(struct agent *agent); + +gboolean agent_matches(struct agent *agent, const char *name, const char *path); + +void agent_init(void); +void agent_exit(void); + diff --git a/src/attrib-server.c b/src/attrib-server.c new file mode 100644 index 0000000..98bd6b1 --- /dev/null +++ b/src/attrib-server.c @@ -0,0 +1,1310 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * 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 +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "log.h" +#include "glib-helper.h" +#include "btio.h" +#include "sdpd.h" +#include "hcid.h" +#include "att.h" +#include "gattrib.h" + +#include "attrib-server.h" + +#define GATT_PSM 0x1f +#define GATT_CID 4 + +static GSList *database = NULL; + +struct gatt_channel { + bdaddr_t src; + bdaddr_t dst; + GSList *configs; + GSList *notify; + GSList *indicate; + GAttrib *attrib; + guint mtu; + gboolean le; + guint id; + gboolean encrypted; +}; + +struct group_elem { + uint16_t handle; + uint16_t end; + uint8_t *data; + uint16_t len; +}; + +static GIOChannel *l2cap_io = NULL; +static GIOChannel *le_io = NULL; +static GSList *clients = NULL; +static uint32_t gatt_sdp_handle = 0; +static uint32_t gap_sdp_handle = 0; + +/* GAP attribute handles */ +static uint16_t name_handle = 0x0000; +static uint16_t appearance_handle = 0x0000; + +static bt_uuid_t prim_uuid = { + .type = BT_UUID16, + .value.u16 = GATT_PRIM_SVC_UUID +}; +static bt_uuid_t snd_uuid = { + .type = BT_UUID16, + .value.u16 = GATT_SND_SVC_UUID +}; + +static sdp_record_t *server_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 = GATT_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 int handle_cmp(gconstpointer a, gconstpointer b) +{ + const struct attribute *attrib = a; + uint16_t handle = GPOINTER_TO_UINT(b); + + return attrib->handle - handle; +} + +static int attribute_cmp(gconstpointer a1, gconstpointer a2) +{ + const struct attribute *attrib1 = a1; + const struct attribute *attrib2 = a2; + + return attrib1->handle - attrib2->handle; +} + +static uint8_t att_check_reqs(struct gatt_channel *channel, uint8_t opcode, + int reqs) +{ + /* FIXME: currently, it is assumed an encrypted link is enough for + * authentication. This will allow to enable the SMP negotiation once + * it is on upstream kernel. High security level should be mapped + * to authentication and medium to encryption permission. */ + if (!channel->encrypted) + channel->encrypted = g_attrib_is_encrypted(channel->attrib); + if (reqs == ATT_AUTHENTICATION && !channel->encrypted) + return ATT_ECODE_INSUFF_AUTHEN; + else if (reqs == ATT_AUTHORIZATION) + return ATT_ECODE_INSUFF_AUTHO; + + switch (opcode) { + case ATT_OP_READ_BY_GROUP_REQ: + case ATT_OP_READ_BY_TYPE_REQ: + case ATT_OP_READ_REQ: + case ATT_OP_READ_BLOB_REQ: + case ATT_OP_READ_MULTI_REQ: + if (reqs == ATT_NOT_PERMITTED) + return ATT_ECODE_READ_NOT_PERM; + break; + case ATT_OP_PREP_WRITE_REQ: + case ATT_OP_WRITE_REQ: + case ATT_OP_WRITE_CMD: + if (reqs == ATT_NOT_PERMITTED) + return ATT_ECODE_WRITE_NOT_PERM; + break; + } + + return 0; +} + +static uint8_t client_set_notifications(struct attribute *attr, + gpointer user_data) +{ + struct gatt_channel *channel = user_data; + struct attribute *last_chr_val = NULL; + uint16_t cfg_val; + uint8_t props; + bt_uuid_t uuid; + GSList *l; + + cfg_val = att_get_u16(attr->data); + + bt_uuid16_create(&uuid, GATT_CHARAC_UUID); + for (l = database, props = 0; l != NULL; l = l->next) { + struct attribute *a = l->data; + static uint16_t handle = 0; + + if (a->handle >= attr->handle) + break; + + if (bt_uuid_cmp(&a->uuid, &uuid) == 0) { + props = att_get_u8(&a->data[0]); + handle = att_get_u16(&a->data[1]); + continue; + } + + if (handle && a->handle == handle) + last_chr_val = a; + } + + if (last_chr_val == NULL) + return 0; + + if ((cfg_val & 0x0001) && !(props & ATT_CHAR_PROPER_NOTIFY)) + return ATT_ECODE_WRITE_NOT_PERM; + + if ((cfg_val & 0x0002) && !(props & ATT_CHAR_PROPER_INDICATE)) + return ATT_ECODE_WRITE_NOT_PERM; + + if (cfg_val & 0x0001) + channel->notify = g_slist_append(channel->notify, last_chr_val); + else + channel->notify = g_slist_remove(channel->notify, last_chr_val); + + if (cfg_val & 0x0002) + channel->indicate = g_slist_append(channel->indicate, + last_chr_val); + else + channel->indicate = g_slist_remove(channel->indicate, + last_chr_val); + + return 0; +} + +static struct attribute *client_cfg_attribute(struct gatt_channel *channel, + struct attribute *orig_attr, + const uint8_t *value, int vlen) +{ + guint handle = orig_attr->handle; + bt_uuid_t uuid; + GSList *l; + + bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID); + if (bt_uuid_cmp(&orig_attr->uuid, &uuid) != 0) + return NULL; + + /* Value is unchanged, not need to create a private copy yet */ + if (vlen == orig_attr->len && memcmp(orig_attr->data, value, vlen) == 0) + return orig_attr; + + l = g_slist_find_custom(channel->configs, GUINT_TO_POINTER(handle), + handle_cmp); + if (!l) { + struct attribute *a; + + /* Create a private copy of the Client Characteristic + * Configuration attribute */ + a = g_malloc0(sizeof(*a) + vlen); + memcpy(a, orig_attr, sizeof(*a)); + memcpy(a->data, value, vlen); + a->write_cb = client_set_notifications; + a->cb_user_data = channel; + + channel->configs = g_slist_insert_sorted(channel->configs, a, + attribute_cmp); + + return a; + } + + return l->data; +} + +static uint16_t read_by_group(struct gatt_channel *channel, uint16_t start, + uint16_t end, bt_uuid_t *uuid, + uint8_t *pdu, int len) +{ + struct att_data_list *adl; + struct attribute *a; + struct group_elem *cur, *old = NULL; + GSList *l, *groups; + uint16_t length, last_handle, last_size = 0; + uint8_t status; + int i; + + if (start > end || start == 0x0000) + return enc_error_resp(ATT_OP_READ_BY_GROUP_REQ, start, + ATT_ECODE_INVALID_HANDLE, pdu, len); + + /* + * Only <> and <> grouping + * types may be used in the Read By Group Type Request. + */ + + if (bt_uuid_cmp(uuid, &prim_uuid) != 0 && + bt_uuid_cmp(uuid, &snd_uuid) != 0) + return enc_error_resp(ATT_OP_READ_BY_GROUP_REQ, 0x0000, + ATT_ECODE_UNSUPP_GRP_TYPE, pdu, len); + + last_handle = end; + for (l = database, groups = NULL, cur = NULL; l; l = l->next) { + struct attribute *client_attr; + + a = l->data; + + if (a->handle < start) + continue; + + if (a->handle >= end) + break; + + /* The old group ends when a new one starts */ + if (old && (bt_uuid_cmp(&a->uuid, &prim_uuid) == 0 || + bt_uuid_cmp(&a->uuid, &snd_uuid) == 0)) { + old->end = last_handle; + old = NULL; + } + + if (bt_uuid_cmp(&a->uuid, uuid) != 0) { + /* Still inside a service, update its last handle */ + if (old) + last_handle = a->handle; + continue; + } + + if (last_size && (last_size != a->len)) + break; + + status = att_check_reqs(channel, ATT_OP_READ_BY_GROUP_REQ, + a->read_reqs); + + client_attr = client_cfg_attribute(channel, a, a->data, a->len); + if (client_attr) + a = client_attr; + + if (status == 0x00 && a->read_cb) + status = a->read_cb(a, a->cb_user_data); + + if (status) { + g_slist_foreach(groups, (GFunc) g_free, NULL); + g_slist_free(groups); + return enc_error_resp(ATT_OP_READ_BY_GROUP_REQ, + a->handle, status, pdu, len); + } + + cur = g_new0(struct group_elem, 1); + cur->handle = a->handle; + cur->data = a->data; + cur->len = a->len; + + /* Attribute Grouping Type found */ + groups = g_slist_append(groups, cur); + + last_size = a->len; + old = cur; + last_handle = cur->handle; + } + + if (groups == NULL) + return enc_error_resp(ATT_OP_READ_BY_GROUP_REQ, start, + ATT_ECODE_ATTR_NOT_FOUND, pdu, len); + + if (l == NULL) + cur->end = a->handle; + else + cur->end = last_handle; + + length = g_slist_length(groups); + + adl = att_data_list_alloc(length, last_size + 4); + + for (i = 0, l = groups; l; l = l->next, i++) { + uint8_t *value; + + cur = l->data; + + value = (void *) adl->data[i]; + + att_put_u16(cur->handle, value); + att_put_u16(cur->end, &value[2]); + /* Attribute Value */ + memcpy(&value[4], cur->data, cur->len); + } + + length = enc_read_by_grp_resp(adl, pdu, len); + + att_data_list_free(adl); + g_slist_foreach(groups, (GFunc) g_free, NULL); + g_slist_free(groups); + + return length; +} + +static uint16_t read_by_type(struct gatt_channel *channel, uint16_t start, + uint16_t end, bt_uuid_t *uuid, + uint8_t *pdu, int len) +{ + struct att_data_list *adl; + GSList *l, *types; + struct attribute *a; + uint16_t num, length; + uint8_t status; + int i; + + if (start > end || start == 0x0000) + return enc_error_resp(ATT_OP_READ_BY_TYPE_REQ, start, + ATT_ECODE_INVALID_HANDLE, pdu, len); + + for (l = database, length = 0, types = NULL; l; l = l->next) { + struct attribute *client_attr; + + a = l->data; + + if (a->handle < start) + continue; + + if (a->handle >= end) + break; + + if (bt_uuid_cmp(&a->uuid, uuid) != 0) + continue; + + status = att_check_reqs(channel, ATT_OP_READ_BY_TYPE_REQ, + a->read_reqs); + + client_attr = client_cfg_attribute(channel, a, a->data, a->len); + if (client_attr) + a = client_attr; + + if (status == 0x00 && a->read_cb) + status = a->read_cb(a, a->cb_user_data); + + if (status) { + g_slist_free(types); + return enc_error_resp(ATT_OP_READ_BY_TYPE_REQ, + a->handle, status, pdu, len); + } + + /* All elements must have the same length */ + if (length == 0) + length = a->len; + else if (a->len != length) + break; + + types = g_slist_append(types, a); + } + + if (types == NULL) + return enc_error_resp(ATT_OP_READ_BY_TYPE_REQ, start, + ATT_ECODE_ATTR_NOT_FOUND, pdu, len); + + num = g_slist_length(types); + + /* Handle length plus attribute value length */ + length += 2; + + adl = att_data_list_alloc(num, length); + + for (i = 0, l = types; l; i++, l = l->next) { + uint8_t *value; + + a = l->data; + + value = (void *) adl->data[i]; + + att_put_u16(a->handle, value); + + /* Attribute Value */ + memcpy(&value[2], a->data, a->len); + } + + length = enc_read_by_type_resp(adl, pdu, len); + + att_data_list_free(adl); + g_slist_free(types); + + return length; +} + +static int find_info(uint16_t start, uint16_t end, uint8_t *pdu, int len) +{ + struct attribute *a; + struct att_data_list *adl; + GSList *l, *info; + uint8_t format, last_type = BT_UUID_UNSPEC; + uint16_t length, num; + int i; + + if (start > end || start == 0x0000) + return enc_error_resp(ATT_OP_FIND_INFO_REQ, start, + ATT_ECODE_INVALID_HANDLE, pdu, len); + + for (l = database, info = NULL, num = 0; l; l = l->next) { + a = l->data; + + if (a->handle < start) + continue; + + if (a->handle > end) + break; + + if (last_type == BT_UUID_UNSPEC) + last_type = a->uuid.type; + + if (a->uuid.type != last_type) + break; + + info = g_slist_append(info, a); + num++; + + last_type = a->uuid.type; + } + + if (info == NULL) + return enc_error_resp(ATT_OP_FIND_INFO_REQ, start, + ATT_ECODE_ATTR_NOT_FOUND, pdu, len); + + if (last_type == BT_UUID16) { + length = 2; + format = 0x01; + } else if (last_type == BT_UUID128) { + length = 16; + format = 0x02; + } else { + g_slist_free(info); + return 0; + } + + adl = att_data_list_alloc(num, length + 2); + + for (i = 0, l = info; l; i++, l = l->next) { + uint8_t *value; + + a = l->data; + + value = (void *) adl->data[i]; + + att_put_u16(a->handle, value); + + /* Attribute Value */ + att_put_uuid(a->uuid, &value[2]); + } + + length = enc_find_info_resp(format, adl, pdu, len); + + att_data_list_free(adl); + g_slist_free(info); + + return length; +} + +static int find_by_type(uint16_t start, uint16_t end, bt_uuid_t *uuid, + const uint8_t *value, int vlen, uint8_t *opdu, int mtu) +{ + struct attribute *a; + struct att_range *range; + GSList *l, *matches; + int len; + + if (start > end || start == 0x0000) + return enc_error_resp(ATT_OP_FIND_BY_TYPE_REQ, start, + ATT_ECODE_INVALID_HANDLE, opdu, mtu); + + /* Searching first requested handle number */ + for (l = database, matches = NULL, range = NULL; l; l = l->next) { + a = l->data; + + if (a->handle < start) + continue; + + if (a->handle > end) + break; + + /* Primary service? Attribute value matches? */ + if ((bt_uuid_cmp(&a->uuid, uuid) == 0) && (a->len == vlen) && + (memcmp(a->data, value, vlen) == 0)) { + + range = g_new0(struct att_range, 1); + range->start = a->handle; + /* It is allowed to have end group handle the same as + * start handle, for groups with only one attribute. */ + range->end = a->handle; + + matches = g_slist_append(matches, range); + } else if (range) { + /* Update the last found handle or reset the pointer + * to track that a new group started: Primary or + * Secondary service. */ + if (bt_uuid_cmp(&a->uuid, &prim_uuid) == 0 || + bt_uuid_cmp(&a->uuid, &snd_uuid) == 0) + range = NULL; + else + range->end = a->handle; + } + } + + if (matches == NULL) + return enc_error_resp(ATT_OP_FIND_BY_TYPE_REQ, start, + ATT_ECODE_ATTR_NOT_FOUND, opdu, mtu); + + len = enc_find_by_type_resp(matches, opdu, mtu); + + g_slist_foreach(matches, (GFunc) g_free, NULL); + g_slist_free(matches); + + return len; +} + +static struct attribute *find_primary_range(uint16_t start, uint16_t *end) +{ + struct attribute *attrib; + guint h = start; + GSList *l; + + if (end == NULL) + return NULL; + + l = g_slist_find_custom(database, GUINT_TO_POINTER(h), handle_cmp); + if (!l) + return NULL; + + attrib = l->data; + + if (bt_uuid_cmp(&attrib->uuid, &prim_uuid) != 0) + return NULL; + + *end = start; + + for (l = l->next; l; l = l->next) { + struct attribute *a = l->data; + + if (bt_uuid_cmp(&a->uuid, &prim_uuid) == 0 || + bt_uuid_cmp(&a->uuid, &snd_uuid) == 0) + break; + + *end = a->handle; + } + + return attrib; +} + +static uint16_t read_value(struct gatt_channel *channel, uint16_t handle, + uint8_t *pdu, int len) +{ + struct attribute *a, *client_attr; + uint8_t status; + GSList *l; + guint h = handle; + + l = g_slist_find_custom(database, GUINT_TO_POINTER(h), handle_cmp); + if (!l) + return enc_error_resp(ATT_OP_READ_REQ, handle, + ATT_ECODE_INVALID_HANDLE, pdu, len); + + a = l->data; + + status = att_check_reqs(channel, ATT_OP_READ_REQ, a->read_reqs); + + client_attr = client_cfg_attribute(channel, a, a->data, a->len); + if (client_attr) + a = client_attr; + + if (status == 0x00 && a->read_cb) + status = a->read_cb(a, a->cb_user_data); + + if (status) + return enc_error_resp(ATT_OP_READ_REQ, handle, status, pdu, + len); + + return enc_read_resp(a->data, a->len, pdu, len); +} + +static uint16_t read_blob(struct gatt_channel *channel, uint16_t handle, + uint16_t offset, uint8_t *pdu, int len) +{ + struct attribute *a, *client_attr; + uint8_t status; + GSList *l; + guint h = handle; + + l = g_slist_find_custom(database, GUINT_TO_POINTER(h), handle_cmp); + if (!l) + return enc_error_resp(ATT_OP_READ_BLOB_REQ, handle, + ATT_ECODE_INVALID_HANDLE, pdu, len); + + a = l->data; + + if (a->len <= offset) + return enc_error_resp(ATT_OP_READ_BLOB_REQ, handle, + ATT_ECODE_INVALID_OFFSET, pdu, len); + + status = att_check_reqs(channel, ATT_OP_READ_BLOB_REQ, a->read_reqs); + + client_attr = client_cfg_attribute(channel, a, a->data, a->len); + if (client_attr) + a = client_attr; + + if (status == 0x00 && a->read_cb) + status = a->read_cb(a, a->cb_user_data); + + if (status) + return enc_error_resp(ATT_OP_READ_BLOB_REQ, handle, status, + pdu, len); + + return enc_read_blob_resp(a->data, a->len, offset, pdu, len); +} + +static uint16_t write_value(struct gatt_channel *channel, uint16_t handle, + const uint8_t *value, int vlen, + uint8_t *pdu, int len) +{ + struct attribute *a, *client_attr; + uint8_t status; + GSList *l; + guint h = handle; + + l = g_slist_find_custom(database, GUINT_TO_POINTER(h), handle_cmp); + if (!l) + return enc_error_resp(ATT_OP_WRITE_REQ, handle, + ATT_ECODE_INVALID_HANDLE, pdu, len); + + a = l->data; + + status = att_check_reqs(channel, ATT_OP_WRITE_REQ, a->write_reqs); + if (status) + return enc_error_resp(ATT_OP_WRITE_REQ, handle, status, pdu, + len); + + client_attr = client_cfg_attribute(channel, a, value, vlen); + if (client_attr) + a = client_attr; + else + attrib_db_update(a->handle, NULL, value, vlen, &a); + + if (a->write_cb) { + status = a->write_cb(a, a->cb_user_data); + if (status) + return enc_error_resp(ATT_OP_WRITE_REQ, handle, status, + pdu, len); + } + + DBG("Notifications: %d, indications: %d", + g_slist_length(channel->notify), + g_slist_length(channel->indicate)); + + return enc_write_resp(pdu, len); +} + +static uint16_t mtu_exchange(struct gatt_channel *channel, uint16_t mtu, + uint8_t *pdu, int len) +{ + guint old_mtu = channel->mtu; + + if (mtu < ATT_DEFAULT_LE_MTU) + channel->mtu = ATT_DEFAULT_LE_MTU; + else + channel->mtu = MIN(mtu, channel->mtu); + + bt_io_set(le_io, BT_IO_L2CAP, NULL, + BT_IO_OPT_OMTU, channel->mtu, + BT_IO_OPT_INVALID); + + return enc_mtu_resp(old_mtu, pdu, len); +} + +static void channel_disconnect(void *user_data) +{ + struct gatt_channel *channel = user_data; + + g_attrib_unref(channel->attrib); + clients = g_slist_remove(clients, channel); + + g_slist_free(channel->notify); + g_slist_free(channel->indicate); + g_slist_foreach(channel->configs, (GFunc) g_free, NULL); + g_slist_free(channel->configs); + + g_free(channel); +} + +static void channel_handler(const uint8_t *ipdu, uint16_t len, + gpointer user_data) +{ + struct gatt_channel *channel = user_data; + uint8_t opdu[ATT_MAX_MTU], value[ATT_MAX_MTU]; + uint16_t length, start, end, mtu, offset; + bt_uuid_t uuid; + uint8_t status = 0; + int vlen; + + DBG("op 0x%02x", ipdu[0]); + + switch (ipdu[0]) { + case ATT_OP_READ_BY_GROUP_REQ: + length = dec_read_by_grp_req(ipdu, len, &start, &end, &uuid); + if (length == 0) { + status = ATT_ECODE_INVALID_PDU; + goto done; + } + + length = read_by_group(channel, start, end, &uuid, opdu, + channel->mtu); + break; + case ATT_OP_READ_BY_TYPE_REQ: + length = dec_read_by_type_req(ipdu, len, &start, &end, &uuid); + if (length == 0) { + status = ATT_ECODE_INVALID_PDU; + goto done; + } + + length = read_by_type(channel, start, end, &uuid, opdu, + channel->mtu); + break; + case ATT_OP_READ_REQ: + length = dec_read_req(ipdu, len, &start); + if (length == 0) { + status = ATT_ECODE_INVALID_PDU; + goto done; + } + + length = read_value(channel, start, opdu, channel->mtu); + break; + case ATT_OP_READ_BLOB_REQ: + length = dec_read_blob_req(ipdu, len, &start, &offset); + if (length == 0) { + status = ATT_ECODE_INVALID_PDU; + goto done; + } + + length = read_blob(channel, start, offset, opdu, channel->mtu); + break; + case ATT_OP_MTU_REQ: + if (!channel->le) { + status = ATT_ECODE_REQ_NOT_SUPP; + goto done; + } + + length = dec_mtu_req(ipdu, len, &mtu); + if (length == 0) { + status = ATT_ECODE_INVALID_PDU; + goto done; + } + + length = mtu_exchange(channel, mtu, opdu, channel->mtu); + break; + case ATT_OP_FIND_INFO_REQ: + length = dec_find_info_req(ipdu, len, &start, &end); + if (length == 0) { + status = ATT_ECODE_INVALID_PDU; + goto done; + } + + length = find_info(start, end, opdu, channel->mtu); + break; + case ATT_OP_WRITE_REQ: + length = dec_write_req(ipdu, len, &start, value, &vlen); + if (length == 0) { + status = ATT_ECODE_INVALID_PDU; + goto done; + } + + length = write_value(channel, start, value, vlen, opdu, + channel->mtu); + break; + case ATT_OP_WRITE_CMD: + length = dec_write_cmd(ipdu, len, &start, value, &vlen); + if (length > 0) + write_value(channel, start, value, vlen, opdu, + channel->mtu); + return; + case ATT_OP_FIND_BY_TYPE_REQ: + length = dec_find_by_type_req(ipdu, len, &start, &end, + &uuid, value, &vlen); + if (length == 0) { + status = ATT_ECODE_INVALID_PDU; + goto done; + } + + length = find_by_type(start, end, &uuid, value, vlen, + opdu, channel->mtu); + break; + case ATT_OP_HANDLE_CNF: + return; + case ATT_OP_READ_MULTI_REQ: + case ATT_OP_PREP_WRITE_REQ: + case ATT_OP_EXEC_WRITE_REQ: + default: + DBG("Unsupported request 0x%02x", ipdu[0]); + status = ATT_ECODE_REQ_NOT_SUPP; + goto done; + } + + if (length == 0) + status = ATT_ECODE_IO; + +done: + if (status) + length = enc_error_resp(ipdu[0], 0x0000, status, opdu, + channel->mtu); + + g_attrib_send(channel->attrib, 0, opdu[0], opdu, length, + NULL, NULL, NULL); +} + +static void connect_event(GIOChannel *io, GError *err, void *user_data) +{ + struct gatt_channel *channel; + uint16_t cid; + GError *gerr = NULL; + + if (err) { + error("%s", err->message); + return; + } + + channel = g_new0(struct gatt_channel, 1); + + bt_io_get(io, BT_IO_L2CAP, &gerr, + BT_IO_OPT_SOURCE_BDADDR, &channel->src, + BT_IO_OPT_DEST_BDADDR, &channel->dst, + BT_IO_OPT_CID, &cid, + BT_IO_OPT_OMTU, &channel->mtu, + BT_IO_OPT_INVALID); + if (gerr) { + error("bt_io_get: %s", gerr->message); + g_error_free(gerr); + g_free(channel); + g_io_channel_shutdown(io, TRUE, NULL); + return; + } + + if (channel->mtu > ATT_MAX_MTU) + channel->mtu = ATT_MAX_MTU; + + if (cid != GATT_CID) + channel->le = FALSE; + else + channel->le = TRUE; + + channel->attrib = g_attrib_new(io); + g_io_channel_unref(io); + + channel->id = g_attrib_register(channel->attrib, GATTRIB_ALL_EVENTS, + channel_handler, channel, NULL); + + g_attrib_set_disconnect_function(channel->attrib, channel_disconnect, + channel); + + clients = g_slist_append(clients, channel); +} + +static void confirm_event(GIOChannel *io, void *user_data) +{ + GError *gerr = NULL; + + if (bt_io_accept(io, connect_event, user_data, NULL, &gerr) == FALSE) { + error("bt_io_accept: %s", gerr->message); + g_error_free(gerr); + g_io_channel_unref(io); + } + + return; +} + +static void attrib_notify_clients(struct attribute *attr) +{ + guint handle = attr->handle; + GSList *l; + + for (l = clients; l; l = l->next) { + struct gatt_channel *channel = l->data; + + /* Notification */ + if (g_slist_find_custom(channel->notify, + GUINT_TO_POINTER(handle), handle_cmp)) { + uint8_t pdu[ATT_MAX_MTU]; + uint16_t len; + + len = enc_notification(attr, pdu, channel->mtu); + if (len == 0) + continue; + + g_attrib_send(channel->attrib, 0, pdu[0], pdu, len, + NULL, NULL, NULL); + } + + /* Indication */ + if (g_slist_find_custom(channel->indicate, + GUINT_TO_POINTER(handle), handle_cmp)) { + uint8_t pdu[ATT_MAX_MTU]; + uint16_t len; + + len = enc_indication(attr, pdu, channel->mtu); + if (len == 0) + return; + + g_attrib_send(channel->attrib, 0, pdu[0], pdu, len, + NULL, NULL, NULL); + } + } +} + +static gboolean register_core_services(void) +{ + uint8_t atval[256]; + bt_uuid_t uuid; + uint16_t appearance = 0x0000; + + /* GAP service: primary service definition */ + bt_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); + att_put_u16(GENERIC_ACCESS_PROFILE_ID, &atval[0]); + attrib_db_add(0x0001, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2); + + /* GAP service: device name characteristic */ + name_handle = 0x0006; + bt_uuid16_create(&uuid, GATT_CHARAC_UUID); + atval[0] = ATT_CHAR_PROPER_READ; + att_put_u16(name_handle, &atval[1]); + att_put_u16(GATT_CHARAC_DEVICE_NAME, &atval[3]); + attrib_db_add(0x0004, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5); + + /* GAP service: device name attribute */ + bt_uuid16_create(&uuid, GATT_CHARAC_DEVICE_NAME); + attrib_db_add(name_handle, &uuid, ATT_NONE, ATT_NOT_PERMITTED, + NULL, 0); + + /* GAP service: device appearance characteristic */ + appearance_handle = 0x0008; + bt_uuid16_create(&uuid, GATT_CHARAC_UUID); + atval[0] = ATT_CHAR_PROPER_READ; + att_put_u16(appearance_handle, &atval[1]); + att_put_u16(GATT_CHARAC_APPEARANCE, &atval[3]); + attrib_db_add(0x0007, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5); + + /* GAP service: device appearance attribute */ + bt_uuid16_create(&uuid, GATT_CHARAC_APPEARANCE); + att_put_u16(appearance, &atval[0]); + attrib_db_add(appearance_handle, &uuid, ATT_NONE, ATT_NOT_PERMITTED, + atval, 2); + gap_sdp_handle = attrib_create_sdp(0x0001, "Generic Access Profile"); + if (gap_sdp_handle == 0) { + error("Failed to register GAP service record"); + goto failed; + } + + /* GATT service: primary service definition */ + bt_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); + att_put_u16(GENERIC_ATTRIB_PROFILE_ID, &atval[0]); + attrib_db_add(0x0010, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2); + + gatt_sdp_handle = attrib_create_sdp(0x0010, + "Generic Attribute Profile"); + if (gatt_sdp_handle == 0) { + error("Failed to register GATT service record"); + goto failed; + } + + return TRUE; + +failed: + if (gap_sdp_handle) + remove_record_from_server(gap_sdp_handle); + + return FALSE; +} + +int attrib_server_init(void) +{ + GError *gerr = NULL; + + /* BR/EDR socket */ + l2cap_io = bt_io_listen(BT_IO_L2CAP, NULL, confirm_event, + NULL, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, BDADDR_ANY, + BT_IO_OPT_PSM, GATT_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + if (l2cap_io == NULL) { + error("%s", gerr->message); + g_error_free(gerr); + return -1; + } + + if (!register_core_services()) + goto failed; + + if (!main_opts.le) + return 0; + + /* LE socket */ + le_io = bt_io_listen(BT_IO_L2CAP, NULL, confirm_event, + &le_io, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, BDADDR_ANY, + BT_IO_OPT_CID, GATT_CID, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + if (le_io == NULL) { + error("%s", gerr->message); + g_error_free(gerr); + /* Doesn't have LE support, continue */ + } + + return 0; + +failed: + g_io_channel_unref(l2cap_io); + l2cap_io = NULL; + + if (le_io) { + g_io_channel_unref(le_io); + le_io = NULL; + } + + return -1; +} + +void attrib_server_exit(void) +{ + GSList *l; + + g_slist_foreach(database, (GFunc) g_free, NULL); + g_slist_free(database); + + if (l2cap_io) { + g_io_channel_unref(l2cap_io); + g_io_channel_shutdown(l2cap_io, FALSE, NULL); + } + + if (le_io) { + g_io_channel_unref(le_io); + g_io_channel_shutdown(le_io, FALSE, NULL); + } + + for (l = clients; l; l = l->next) { + struct gatt_channel *channel = l->data; + + g_slist_free(channel->notify); + g_slist_free(channel->indicate); + g_slist_foreach(channel->configs, (GFunc) g_free, NULL); + g_slist_free(channel->configs); + + g_attrib_unref(channel->attrib); + g_free(channel); + } + + g_slist_free(clients); + + if (gatt_sdp_handle) + remove_record_from_server(gatt_sdp_handle); + + if (gap_sdp_handle) + remove_record_from_server(gap_sdp_handle); +} + +uint32_t attrib_create_sdp(uint16_t handle, const char *name) +{ + sdp_record_t *record; + struct attribute *a; + uint16_t end = 0; + uuid_t svc, gap_uuid; + + a = find_primary_range(handle, &end); + + if (a == NULL) + return 0; + + if (a->len == 2) + sdp_uuid16_create(&svc, att_get_u16(a->data)); + else if (a->len == 16) + sdp_uuid128_create(&svc, a->data); + else + return 0; + + record = server_record_new(&svc, handle, end); + if (record == NULL) + return 0; + + if (name) + sdp_set_info_attr(record, name, "BlueZ", NULL); + + sdp_uuid16_create(&gap_uuid, GENERIC_ACCESS_PROFILE_ID); + 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 (add_record_to_server(BDADDR_ANY, record) < 0) + sdp_record_free(record); + else + return record->handle; + + return 0; +} + +void attrib_free_sdp(uint32_t sdp_handle) +{ + remove_record_from_server(sdp_handle); +} + +struct attribute *attrib_db_add(uint16_t handle, bt_uuid_t *uuid, int read_reqs, + int write_reqs, const uint8_t *value, int len) +{ + struct attribute *a; + guint h = handle; + + DBG("handle=0x%04x", handle); + + if (g_slist_find_custom(database, GUINT_TO_POINTER(h), handle_cmp)) + return NULL; + + a = g_malloc0(sizeof(struct attribute) + len); + a->handle = handle; + memcpy(&a->uuid, uuid, sizeof(bt_uuid_t)); + a->read_reqs = read_reqs; + a->write_reqs = write_reqs; + a->len = len; + memcpy(a->data, value, len); + + database = g_slist_insert_sorted(database, a, attribute_cmp); + + return a; +} + +int attrib_db_update(uint16_t handle, bt_uuid_t *uuid, const uint8_t *value, + int len, struct attribute **attr) +{ + struct attribute *a; + GSList *l; + guint h = handle; + + DBG("handle=0x%04x", handle); + + l = g_slist_find_custom(database, GUINT_TO_POINTER(h), handle_cmp); + if (!l) + return -ENOENT; + + a = g_try_realloc(l->data, sizeof(struct attribute) + len); + if (a == NULL) + return -ENOMEM; + + l->data = a; + if (uuid != NULL) + memcpy(&a->uuid, uuid, sizeof(bt_uuid_t)); + a->len = len; + memcpy(a->data, value, len); + + attrib_notify_clients(a); + + if (attr) + *attr = a; + + return 0; +} + +int attrib_db_del(uint16_t handle) +{ + struct attribute *a; + GSList *l; + guint h = handle; + + DBG("handle=0x%04x", handle); + + l = g_slist_find_custom(database, GUINT_TO_POINTER(h), handle_cmp); + if (!l) + return -ENOENT; + + a = l->data; + database = g_slist_remove(database, a); + g_free(a); + + return 0; +} + +int attrib_gap_set(uint16_t uuid, const uint8_t *value, int len) +{ + uint16_t handle; + + /* FIXME: Missing Privacy and Reconnection Address */ + + switch (uuid) { + case GATT_CHARAC_DEVICE_NAME: + handle = name_handle; + break; + case GATT_CHARAC_APPEARANCE: + handle = appearance_handle; + break; + default: + return -ENOSYS; + } + + return attrib_db_update(handle, NULL, value, len, NULL); +} diff --git a/src/attrib-server.h b/src/attrib-server.h new file mode 100644 index 0000000..38a1f05 --- /dev/null +++ b/src/attrib-server.h @@ -0,0 +1,35 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * 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 + * + */ + +int attrib_server_init(void); +void attrib_server_exit(void); + +struct attribute *attrib_db_add(uint16_t handle, bt_uuid_t *uuid, int read_reqs, + int write_reqs, const uint8_t *value, int len); +int attrib_db_update(uint16_t handle, bt_uuid_t *uuid, const uint8_t *value, + int len, struct attribute **attr); +int attrib_db_del(uint16_t handle); +int attrib_gap_set(uint16_t uuid, const uint8_t *value, int len); +uint32_t attrib_create_sdp(uint16_t handle, const char *name); +void attrib_free_sdp(uint32_t sdp_handle); diff --git a/src/bluetooth.conf b/src/bluetooth.conf new file mode 100644 index 0000000..853f926 --- /dev/null +++ b/src/bluetooth.conf @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/bluetooth.ver b/src/bluetooth.ver new file mode 100644 index 0000000..b71c70d --- /dev/null +++ b/src/bluetooth.ver @@ -0,0 +1,10 @@ +{ + global: + btd_*; + g_dbus_*; + info; + error; + debug; + local: + *; +}; diff --git a/src/bluetoothd.8.in b/src/bluetoothd.8.in new file mode 100644 index 0000000..a7ae77b --- /dev/null +++ b/src/bluetoothd.8.in @@ -0,0 +1,91 @@ +.\" +.TH "BLUETOOTHD" "8" "March 2004" "Bluetooth daemon" "System management commands" +.SH "NAME" +bluetoothd \- Bluetooth daemon + +.SH "SYNOPSIS" +.B bluetoothd +[ +.B \-n +] + +.SH "DESCRIPTION" +This manual page documents briefly the +.B bluetoothd +daemon, which manages all the Bluetooth devices. +.B bluetoothd +itself does not accept many command\-line options, as most of its +configuration is done in the +.B @CONFIGDIR@/main.conf +file, which has its own man page. +.B bluetoothd +can also provide a number of services via the D-Bus message bus +system. +.SH "OPTIONS" +.TP +.BI \-n +Don't run as daemon in background. +.TP +.BI \-d +Enable debug information output. +.TP +.BI \-m\ mtu\-size +Use specific MTU size for SDP server. + +.SH "FILES" +.TP +.I @CONFIGDIR@/main.conf +Default location of the global configuration file. + +.TP +.I @STORAGEDIR@/nn:nn:nn:nn:nn:nn/linkkeys +Default location for link keys of paired devices. The directory +\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP +is the address of the local device. The file is line separated, with +the following columns separated by whitespace: + +\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP Remote device address. + +\fInnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn\fP Link key. + +\fIn\fP Link type integer. + +.TP +.I @STORAGEDIR@/nn:nn:nn:nn:nn:nn/names +Default location for the device name cache. The directory +\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP +is the address of the local device. The file is line separated, with +the following columns separated by whitespace: + +\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP Remote device address. + +\fIname\fP Remote device name, terminated with newline. + +.TP +.I @STORAGEDIR@/nn:nn:nn:nn:nn:nn/features +Default location for the features cache. The directory +\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP +is the address of the local device. The file is line separated, with +the following columns separated by whitespace: + +\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP Remote device address. + +\fInnnnnnnnnnnnnnnn\fP Remote device LMP features coded as an 8 byte bitfield. + +.TP +.I @STORAGEDIR@/nn:nn:nn:nn:nn:nn/manufacturers +Default location for the manufacturers cache. The directory +\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP +is the address of the local device. The file is line separated, with +the following columns separated by whitespace: + +\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP Remote device address. + +\fIn\fP Remote device manufacturer integer. + +\fIn\fP Remote device LMP version integer. + +\fIn\fP Remote device LMP sub-version integer. + +.SH "AUTHOR" +This manual page was written by Marcel Holtmann, Philipp Matthias Hahn and Fredrik Noring. diff --git a/src/dbus-common.c b/src/dbus-common.c new file mode 100644 index 0000000..8436100 --- /dev/null +++ b/src/dbus-common.c @@ -0,0 +1,254 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * Copyright (C) 2005-2007 Johan Hedberg + * + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "log.h" + +#include "adapter.h" +#include "manager.h" +#include "event.h" +#include "dbus-common.h" + +static DBusConnection *connection = NULL; + +static void append_variant(DBusMessageIter *iter, int type, void *val) +{ + DBusMessageIter value; + char sig[2] = { type, '\0' }; + + dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, sig, &value); + + dbus_message_iter_append_basic(&value, type, val); + + dbus_message_iter_close_container(iter, &value); +} + +static void append_array_variant(DBusMessageIter *iter, int type, void *val, + int n_elements) +{ + DBusMessageIter variant, array; + char type_sig[2] = { type, '\0' }; + char array_sig[3] = { DBUS_TYPE_ARRAY, type, '\0' }; + + dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, + array_sig, &variant); + + dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, + type_sig, &array); + + if (dbus_type_is_fixed(type) == TRUE) { + dbus_message_iter_append_fixed_array(&array, type, val, + n_elements); + } else if (type == DBUS_TYPE_STRING || type == DBUS_TYPE_OBJECT_PATH) { + const char ***str_array = val; + int i; + + for (i = 0; i < n_elements; i++) + dbus_message_iter_append_basic(&array, type, + &((*str_array)[i])); + } + + dbus_message_iter_close_container(&variant, &array); + + dbus_message_iter_close_container(iter, &variant); +} + +void dict_append_entry(DBusMessageIter *dict, + const char *key, int type, void *val) +{ + DBusMessageIter entry; + + if (type == DBUS_TYPE_STRING) { + const char *str = *((const char **) val); + if (str == NULL) + return; + } + + dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, + NULL, &entry); + + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key); + + append_variant(&entry, type, val); + + dbus_message_iter_close_container(dict, &entry); +} + +void dict_append_array(DBusMessageIter *dict, const char *key, int type, + void *val, int n_elements) +{ + DBusMessageIter entry; + + dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, + NULL, &entry); + + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key); + + append_array_variant(&entry, type, val, n_elements); + + dbus_message_iter_close_container(dict, &entry); +} + +dbus_bool_t emit_property_changed(DBusConnection *conn, + const char *path, + const char *interface, + const char *name, + int type, void *value) +{ + DBusMessage *signal; + DBusMessageIter iter; + + signal = dbus_message_new_signal(path, interface, "PropertyChanged"); + + if (!signal) { + error("Unable to allocate new %s.PropertyChanged signal", + interface); + return FALSE; + } + + dbus_message_iter_init_append(signal, &iter); + + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &name); + + append_variant(&iter, type, value); + + return g_dbus_send_message(conn, signal); +} + +dbus_bool_t emit_array_property_changed(DBusConnection *conn, + const char *path, + const char *interface, + const char *name, + int type, void *value, int num) +{ + DBusMessage *signal; + DBusMessageIter iter; + + signal = dbus_message_new_signal(path, interface, "PropertyChanged"); + + if (!signal) { + error("Unable to allocate new %s.PropertyChanged signal", + interface); + return FALSE; + } + + dbus_message_iter_init_append(signal, &iter); + + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &name); + + append_array_variant(&iter, type, value, num); + + return g_dbus_send_message(conn, signal); +} + +void set_dbus_connection(DBusConnection *conn) +{ + connection = conn; +} + +DBusConnection *get_dbus_connection(void) +{ + return connection; +} + +const char *class_to_icon(uint32_t class) +{ + switch ((class & 0x1f00) >> 8) { + case 0x01: + return "computer"; + case 0x02: + switch ((class & 0xfc) >> 2) { + case 0x01: + case 0x02: + case 0x03: + case 0x05: + return "phone"; + case 0x04: + return "modem"; + } + break; + case 0x03: + return "network-wireless"; + case 0x04: + switch ((class & 0xfc) >> 2) { + case 0x01: + case 0x02: + return "audio-card"; /* Headset */ + case 0x06: + return "audio-card"; /* Headphone */ + case 0x0b: /* VCR */ + case 0x0c: /* Video Camera */ + case 0x0d: /* Camcorder */ + return "camera-video"; + default: + return "audio-card"; /* Other audio device */ + } + break; + case 0x05: + switch ((class & 0xc0) >> 6) { + case 0x00: + switch ((class & 0x1e) >> 2) { + case 0x01: + case 0x02: + return "input-gaming"; + } + break; + case 0x01: + return "input-keyboard"; + case 0x02: + switch ((class & 0x1e) >> 2) { + case 0x05: + return "input-tablet"; + default: + return "input-mouse"; + } + } + break; + case 0x06: + if (class & 0x80) + return "printer"; + if (class & 0x20) + return "camera-photo"; + break; + } + + return NULL; +} diff --git a/src/dbus-common.h b/src/dbus-common.h new file mode 100644 index 0000000..b196a1b --- /dev/null +++ b/src/dbus-common.h @@ -0,0 +1,47 @@ +/* * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 + * + */ + +#define MAX_PATH_LENGTH 64 + +void dict_append_entry(DBusMessageIter *dict, + const char *key, int type, void *val); + +void dict_append_array(DBusMessageIter *dict, const char *key, int type, + void *val, int n_elements); + +dbus_bool_t emit_property_changed(DBusConnection *conn, + const char *path, + const char *interface, + const char *name, + int type, void *value); + +dbus_bool_t emit_array_property_changed(DBusConnection *conn, + const char *path, + const char *interface, + const char *name, + int type, void *value, int num); + +void set_dbus_connection(DBusConnection *conn); +DBusConnection *get_dbus_connection(void); + +const char *class_to_icon(uint32_t class); diff --git a/src/device.c b/src/device.c new file mode 100644 index 0000000..771a908 --- /dev/null +++ b/src/device.c @@ -0,0 +1,2388 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "log.h" +#include "textfile.h" + +#include "att.h" +#include "hcid.h" +#include "adapter.h" +#include "device.h" +#include "dbus-common.h" +#include "event.h" +#include "error.h" +#include "glib-helper.h" +#include "gattrib.h" +#include "gatt.h" +#include "agent.h" +#include "sdp-xml.h" +#include "storage.h" +#include "btio.h" + +#define DISCONNECT_TIMER 2 +#define DISCOVERY_TIMER 2 + +/* When all services should trust a remote device */ +#define GLOBAL_TRUST "[all]" + +struct btd_disconnect_data { + guint id; + disconnect_watch watch; + void *user_data; + GDestroyNotify destroy; +}; + +struct bonding_req { + DBusConnection *conn; + DBusMessage *msg; + GIOChannel *io; + guint listener_id; + struct btd_device *device; +}; + +struct authentication_req { + auth_type_t type; + void *cb; + struct agent *agent; + struct btd_device *device; +}; + +struct browse_req { + DBusConnection *conn; + DBusMessage *msg; + GAttrib *attrib; + GIOChannel *io; + struct btd_device *device; + GSList *match_uuids; + GSList *profiles_added; + GSList *profiles_removed; + sdp_list_t *records; + int search_uuid; + int reconnect_attempt; + guint listener_id; +}; + +struct btd_device { + bdaddr_t bdaddr; + device_type_t type; + gchar *path; + char name[MAX_NAME_LENGTH + 1]; + char *alias; + struct btd_adapter *adapter; + GSList *uuids; + GSList *services; /* Primary services path */ + GSList *primaries; /* List of primary services */ + GSList *drivers; /* List of device drivers */ + GSList *watches; /* List of disconnect_data */ + gboolean temporary; + struct agent *agent; + guint disconn_timer; + guint discov_timer; + struct browse_req *browse; /* service discover request */ + struct bonding_req *bonding; + struct authentication_req *authr; /* authentication request */ + GSList *disconnects; /* disconnects message */ + + gboolean connected; + + /* Whether were creating a security mode 3 connection */ + gboolean secmode3; + + sdp_list_t *tmp_records; + + gboolean trusted; + gboolean paired; + gboolean blocked; + + gboolean authorizing; + gint ref; +}; + +static uint16_t uuid_list[] = { + L2CAP_UUID, + PNP_INFO_SVCLASS_ID, + PUBLIC_BROWSE_GROUP, + 0 +}; + +static GSList *device_drivers = NULL; + +static void browse_request_free(struct browse_req *req) +{ + if (req->listener_id) + g_dbus_remove_watch(req->conn, req->listener_id); + if (req->msg) + dbus_message_unref(req->msg); + if (req->conn) + dbus_connection_unref(req->conn); + if (req->device) + btd_device_unref(req->device); + g_slist_foreach(req->profiles_added, (GFunc) g_free, NULL); + g_slist_free(req->profiles_added); + g_slist_free(req->profiles_removed); + if (req->records) + sdp_list_free(req->records, (sdp_free_func_t) sdp_record_free); + + if (req->io) { + g_attrib_unref(req->attrib); + g_io_channel_unref(req->io); + g_io_channel_shutdown(req->io, FALSE, NULL); + } + + g_free(req); +} + +static void browse_request_cancel(struct browse_req *req) +{ + struct btd_device *device = req->device; + struct btd_adapter *adapter = device->adapter; + bdaddr_t src; + + if (device_is_creating(device, NULL)) + device_set_temporary(device, TRUE); + + adapter_get_address(adapter, &src); + + bt_cancel_discovery(&src, &device->bdaddr); + + device->browse = NULL; + browse_request_free(req); +} + +static void device_free(gpointer user_data) +{ + struct btd_device *device = user_data; + struct btd_adapter *adapter = device->adapter; + struct agent *agent = adapter_get_agent(adapter); + + if (device->agent) + agent_free(device->agent); + + if (agent && (agent_is_busy(agent, device) || + agent_is_busy(agent, device->authr))) + agent_cancel(agent); + + g_slist_foreach(device->services, (GFunc) g_free, NULL); + g_slist_free(device->services); + + g_slist_foreach(device->uuids, (GFunc) g_free, NULL); + g_slist_free(device->uuids); + + g_slist_foreach(device->primaries, (GFunc) g_free, NULL); + g_slist_free(device->primaries); + + if (device->tmp_records) + sdp_list_free(device->tmp_records, + (sdp_free_func_t) sdp_record_free); + + if (device->disconn_timer) + g_source_remove(device->disconn_timer); + + if (device->discov_timer) + g_source_remove(device->discov_timer); + + DBG("%p", device); + + g_free(device->authr); + g_free(device->path); + g_free(device->alias); + g_free(device); +} + +gboolean device_is_paired(struct btd_device *device) +{ + return device->paired; +} + +gboolean device_is_trusted(struct btd_device *device) +{ + return device->trusted; +} + +static DBusMessage *get_properties(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct btd_device *device = user_data; + struct btd_adapter *adapter = device->adapter; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + bdaddr_t src; + char name[MAX_NAME_LENGTH + 1], srcaddr[18], dstaddr[18]; + char **str; + const char *ptr; + dbus_bool_t boolean; + uint32_t class; + int i; + GSList *l; + + ba2str(&device->bdaddr, dstaddr); + + 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); + + /* Address */ + ptr = dstaddr; + dict_append_entry(&dict, "Address", DBUS_TYPE_STRING, &ptr); + + /* Name */ + ptr = NULL; + memset(name, 0, sizeof(name)); + adapter_get_address(adapter, &src); + ba2str(&src, srcaddr); + + ptr = device->name; + dict_append_entry(&dict, "Name", DBUS_TYPE_STRING, &ptr); + + /* Alias (fallback to name or address) */ + if (device->alias != NULL) + ptr = device->alias; + else if (strlen(ptr) == 0) { + g_strdelimit(dstaddr, ":", '-'); + ptr = dstaddr; + } + + dict_append_entry(&dict, "Alias", DBUS_TYPE_STRING, &ptr); + + /* Class */ + if (read_remote_class(&src, &device->bdaddr, &class) == 0) { + const char *icon = class_to_icon(class); + + dict_append_entry(&dict, "Class", DBUS_TYPE_UINT32, &class); + + if (icon) + dict_append_entry(&dict, "Icon", + DBUS_TYPE_STRING, &icon); + } + + /* Paired */ + boolean = device_is_paired(device); + dict_append_entry(&dict, "Paired", DBUS_TYPE_BOOLEAN, &boolean); + + /* Trusted */ + boolean = device_is_trusted(device); + dict_append_entry(&dict, "Trusted", DBUS_TYPE_BOOLEAN, &boolean); + + /* Blocked */ + boolean = device->blocked; + dict_append_entry(&dict, "Blocked", DBUS_TYPE_BOOLEAN, &boolean); + + /* Connected */ + dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, + &device->connected); + + /* UUIDs */ + str = g_new0(char *, g_slist_length(device->uuids) + 1); + for (i = 0, l = device->uuids; l; l = l->next, i++) + str[i] = l->data; + dict_append_array(&dict, "UUIDs", DBUS_TYPE_STRING, &str, i); + g_free(str); + + /* Services */ + str = g_new0(char *, g_slist_length(device->services) + 1); + for (i = 0, l = device->services; l; l = l->next, i++) + str[i] = l->data; + dict_append_array(&dict, "Services", DBUS_TYPE_OBJECT_PATH, &str, i); + g_free(str); + + /* Adapter */ + ptr = adapter_get_path(adapter); + dict_append_entry(&dict, "Adapter", DBUS_TYPE_OBJECT_PATH, &ptr); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static DBusMessage *set_alias(DBusConnection *conn, DBusMessage *msg, + const char *alias, void *data) +{ + struct btd_device *device = data; + struct btd_adapter *adapter = device->adapter; + char srcaddr[18], dstaddr[18]; + bdaddr_t src; + int err; + + /* No change */ + if ((device->alias == NULL && g_str_equal(alias, "")) || + g_strcmp0(device->alias, alias) == 0) + return dbus_message_new_method_return(msg); + + adapter_get_address(adapter, &src); + ba2str(&src, srcaddr); + ba2str(&device->bdaddr, dstaddr); + + /* Remove alias if empty string */ + err = write_device_alias(srcaddr, dstaddr, + g_str_equal(alias, "") ? NULL : alias); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + g_free(device->alias); + device->alias = g_str_equal(alias, "") ? NULL : g_strdup(alias); + + emit_property_changed(conn, dbus_message_get_path(msg), + DEVICE_INTERFACE, "Alias", + DBUS_TYPE_STRING, &alias); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *set_trust(DBusConnection *conn, DBusMessage *msg, + gboolean value, void *data) +{ + struct btd_device *device = data; + struct btd_adapter *adapter = device->adapter; + char srcaddr[18], dstaddr[18]; + bdaddr_t src; + int err; + + if (device->trusted == value) + return dbus_message_new_method_return(msg); + + adapter_get_address(adapter, &src); + ba2str(&src, srcaddr); + ba2str(&device->bdaddr, dstaddr); + + err = write_trust(srcaddr, dstaddr, GLOBAL_TRUST, value); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + device->trusted = value; + + emit_property_changed(conn, dbus_message_get_path(msg), + DEVICE_INTERFACE, "Trusted", + DBUS_TYPE_BOOLEAN, &value); + + return dbus_message_new_method_return(msg); +} + +static void driver_remove(struct btd_device_driver *driver, + struct btd_device *device) +{ + driver->remove(device); + + device->drivers = g_slist_remove(device->drivers, driver); +} + +static gboolean do_disconnect(gpointer user_data) +{ + struct btd_device *device = user_data; + + device->disconn_timer = 0; + + btd_adapter_disconnect_device(device->adapter, &device->bdaddr); + + return FALSE; +} + +static int device_block(DBusConnection *conn, struct btd_device *device) +{ + int err; + bdaddr_t src; + + if (device->blocked) + return 0; + + if (device->connected) + do_disconnect(device); + + g_slist_foreach(device->drivers, (GFunc) driver_remove, device); + + err = btd_adapter_block_address(device->adapter, &device->bdaddr); + if (err < 0) + return err; + + device->blocked = TRUE; + + adapter_get_address(device->adapter, &src); + + err = write_blocked(&src, &device->bdaddr, TRUE); + if (err < 0) + error("write_blocked(): %s (%d)", strerror(-err), -err); + + device_set_temporary(device, FALSE); + + emit_property_changed(conn, device->path, DEVICE_INTERFACE, "Blocked", + DBUS_TYPE_BOOLEAN, &device->blocked); + + return 0; +} + +static int device_unblock(DBusConnection *conn, struct btd_device *device, + gboolean silent) +{ + int err; + bdaddr_t src; + + if (!device->blocked) + return 0; + + err = btd_adapter_unblock_address(device->adapter, &device->bdaddr); + if (err < 0) + return err; + + device->blocked = FALSE; + + adapter_get_address(device->adapter, &src); + + err = write_blocked(&src, &device->bdaddr, FALSE); + if (err < 0) + error("write_blocked(): %s (%d)", strerror(-err), -err); + + if (!silent) { + emit_property_changed(conn, device->path, + DEVICE_INTERFACE, "Blocked", + DBUS_TYPE_BOOLEAN, &device->blocked); + device_probe_drivers(device, device->uuids); + } + + return 0; +} + +static DBusMessage *set_blocked(DBusConnection *conn, DBusMessage *msg, + gboolean value, void *data) +{ + struct btd_device *device = data; + int err; + + if (value) + err = device_block(conn, device); + else + err = device_unblock(conn, device, FALSE); + + switch (-err) { + case 0: + return dbus_message_new_method_return(msg); + case EINVAL: + return btd_error_failed(msg, "Kernel lacks blacklist support"); + default: + return btd_error_failed(msg, strerror(-err)); + } +} + +static DBusMessage *set_property(DBusConnection *conn, + DBusMessage *msg, void *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("Trusted", property)) { + dbus_bool_t value; + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN) + return btd_error_invalid_args(msg); + dbus_message_iter_get_basic(&sub, &value); + + return set_trust(conn, msg, value, data); + } else if (g_str_equal("Alias", property)) { + const char *alias; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) + return btd_error_invalid_args(msg); + dbus_message_iter_get_basic(&sub, &alias); + + return set_alias(conn, msg, alias, data); + } else if (g_str_equal("Blocked", property)) { + dbus_bool_t value; + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&sub, &value); + + return set_blocked(conn, msg, value, data); + } + + return btd_error_invalid_args(msg); +} + +static void discover_services_req_exit(DBusConnection *conn, void *user_data) +{ + struct browse_req *req = user_data; + + DBG("DiscoverServices requestor exited"); + + browse_request_cancel(req); +} + +static DBusMessage *discover_services(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct btd_device *device = user_data; + const char *pattern; + int err; + + if (device->browse) + return btd_error_in_progress(msg); + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern, + DBUS_TYPE_INVALID) == FALSE) + return btd_error_invalid_args(msg); + + if (strlen(pattern) == 0) { + err = device_browse_sdp(device, conn, msg, NULL, FALSE); + if (err < 0) + goto fail; + } else { + uuid_t uuid; + + if (bt_string2uuid(&uuid, pattern) < 0) + return btd_error_invalid_args(msg); + + sdp_uuid128_to_uuid(&uuid); + + err = device_browse_sdp(device, conn, msg, &uuid, FALSE); + if (err < 0) + goto fail; + } + + return NULL; + +fail: + return btd_error_failed(msg, strerror(-err)); +} + +static const char *browse_request_get_requestor(struct browse_req *req) +{ + if (!req->msg) + return NULL; + + return dbus_message_get_sender(req->msg); +} + +static void iter_append_record(DBusMessageIter *dict, uint32_t handle, + const char *record) +{ + DBusMessageIter entry; + + dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, + NULL, &entry); + + dbus_message_iter_append_basic(&entry, DBUS_TYPE_UINT32, &handle); + + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &record); + + dbus_message_iter_close_container(dict, &entry); +} + +static void discover_services_reply(struct browse_req *req, int err, + sdp_list_t *recs) +{ + DBusMessage *reply; + DBusMessageIter iter, dict; + sdp_list_t *seq; + + if (err) { + const char *err_if; + + if (err == -EHOSTDOWN) + err_if = ERROR_INTERFACE ".ConnectionAttemptFailed"; + else + err_if = ERROR_INTERFACE ".Failed"; + + reply = dbus_message_new_error(req->msg, err_if, + strerror(-err)); + g_dbus_send_message(req->conn, reply); + return; + } + + reply = dbus_message_new_method_return(req->msg); + if (!reply) + return; + + 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_UINT32_AS_STRING DBUS_TYPE_STRING_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + for (seq = recs; seq; seq = seq->next) { + sdp_record_t *rec = (sdp_record_t *) seq->data; + GString *result; + + if (!rec) + break; + + result = g_string_new(NULL); + + convert_sdp_record_to_xml(rec, result, + (void *) g_string_append); + + if (result->len) + iter_append_record(&dict, rec->handle, result->str); + + g_string_free(result, TRUE); + } + + dbus_message_iter_close_container(&iter, &dict); + + g_dbus_send_message(req->conn, reply); +} + +static DBusMessage *cancel_discover(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct btd_device *device = user_data; + const char *sender = dbus_message_get_sender(msg); + const char *requestor; + + if (!device->browse) + return btd_error_does_not_exist(msg); + + if (!dbus_message_is_method_call(device->browse->msg, DEVICE_INTERFACE, + "DiscoverServices")) + return btd_error_not_authorized(msg); + + requestor = browse_request_get_requestor(device->browse); + + /* only the discover requestor can cancel the inquiry process */ + if (!requestor || !g_str_equal(requestor, sender)) + return btd_error_not_authorized(msg); + + discover_services_reply(device->browse, -ECANCELED, NULL); + + browse_request_cancel(device->browse); + + return dbus_message_new_method_return(msg); +} + +static void bonding_request_cancel(struct bonding_req *bonding) +{ + struct btd_device *device = bonding->device; + struct btd_adapter *adapter = device->adapter; + + adapter_cancel_bonding(adapter, &device->bdaddr); +} + +void device_request_disconnect(struct btd_device *device, DBusMessage *msg) +{ + DBusConnection *conn = get_dbus_connection(); + + if (device->bonding) + bonding_request_cancel(device->bonding); + + if (device->browse) { + discover_services_reply(device->browse, -ECANCELED, NULL); + browse_request_cancel(device->browse); + } + + if (msg) + device->disconnects = g_slist_append(device->disconnects, + dbus_message_ref(msg)); + + if (device->disconn_timer) + return; + + while (device->watches) { + struct btd_disconnect_data *data = device->watches->data; + + if (data->watch) + /* temporary is set if device is going to be removed */ + data->watch(device, device->temporary, + data->user_data); + + /* Check if the watch has been removed by callback function */ + if (!g_slist_find(device->watches, data)) + continue; + + device->watches = g_slist_remove(device->watches, data); + g_free(data); + } + + device->disconn_timer = g_timeout_add_seconds(DISCONNECT_TIMER, + do_disconnect, device); + + g_dbus_emit_signal(conn, device->path, + DEVICE_INTERFACE, "DisconnectRequested", + DBUS_TYPE_INVALID); +} + +static DBusMessage *disconnect(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct btd_device *device = user_data; + + if (!device->connected) + return btd_error_not_connected(msg); + + device_request_disconnect(device, msg); + + return NULL; +} + +static GDBusMethodTable device_methods[] = { + { "GetProperties", "", "a{sv}", get_properties }, + { "SetProperty", "sv", "", set_property }, + { "DiscoverServices", "s", "a{us}", discover_services, + G_DBUS_METHOD_FLAG_ASYNC}, + { "CancelDiscovery", "", "", cancel_discover }, + { "Disconnect", "", "", disconnect, + G_DBUS_METHOD_FLAG_ASYNC}, + { } +}; + +static GDBusSignalTable device_signals[] = { + { "PropertyChanged", "sv" }, + { "DisconnectRequested", "" }, + { } +}; + +gboolean device_is_connected(struct btd_device *device) +{ + return device->connected; +} + +void device_add_connection(struct btd_device *device, DBusConnection *conn) +{ + if (device->connected) { + char addr[18]; + ba2str(&device->bdaddr, addr); + error("Device %s is already connected", addr); + return; + } + + device->connected = TRUE; + + emit_property_changed(conn, device->path, + DEVICE_INTERFACE, "Connected", + DBUS_TYPE_BOOLEAN, &device->connected); +} + +void device_remove_connection(struct btd_device *device, DBusConnection *conn) +{ + if (!device->connected) { + char addr[18]; + ba2str(&device->bdaddr, addr); + error("Device %s isn't connected", addr); + return; + } + + device->connected = FALSE; + + if (device->disconn_timer > 0) { + g_source_remove(device->disconn_timer); + device->disconn_timer = 0; + } + + while (device->disconnects) { + DBusMessage *msg = device->disconnects->data; + + g_dbus_send_reply(conn, msg, DBUS_TYPE_INVALID); + device->disconnects = g_slist_remove(device->disconnects, msg); + } + + emit_property_changed(conn, device->path, + DEVICE_INTERFACE, "Connected", + DBUS_TYPE_BOOLEAN, &device->connected); +} + +guint device_add_disconnect_watch(struct btd_device *device, + disconnect_watch watch, void *user_data, + GDestroyNotify destroy) +{ + struct btd_disconnect_data *data; + static guint id = 0; + + data = g_new0(struct btd_disconnect_data, 1); + data->id = ++id; + data->watch = watch; + data->user_data = user_data; + data->destroy = destroy; + + device->watches = g_slist_append(device->watches, data); + + return data->id; +} + +void device_remove_disconnect_watch(struct btd_device *device, guint id) +{ + GSList *l; + + for (l = device->watches; l; l = l->next) { + struct btd_disconnect_data *data = l->data; + + if (data->id == id) { + device->watches = g_slist_remove(device->watches, + data); + if (data->destroy) + data->destroy(data->user_data); + g_free(data); + return; + } + } +} + +struct btd_device *device_create(DBusConnection *conn, + struct btd_adapter *adapter, + const gchar *address, device_type_t type) +{ + gchar *address_up; + struct btd_device *device; + const gchar *adapter_path = adapter_get_path(adapter); + bdaddr_t src; + char srcaddr[18], alias[MAX_NAME_LENGTH + 1]; + + device = g_try_malloc0(sizeof(struct btd_device)); + if (device == NULL) + return NULL; + + address_up = g_ascii_strup(address, -1); + device->path = g_strdup_printf("%s/dev_%s", adapter_path, address_up); + g_strdelimit(device->path, ":", '_'); + g_free(address_up); + + DBG("Creating device %s", device->path); + + if (g_dbus_register_interface(conn, device->path, DEVICE_INTERFACE, + device_methods, device_signals, NULL, + device, device_free) == FALSE) { + device_free(device); + return NULL; + } + + str2ba(address, &device->bdaddr); + device->adapter = adapter; + device->type = type; + adapter_get_address(adapter, &src); + ba2str(&src, srcaddr); + read_device_name(srcaddr, address, device->name); + if (read_device_alias(srcaddr, address, alias, sizeof(alias)) == 0) + device->alias = g_strdup(alias); + device->trusted = read_trust(&src, address, GLOBAL_TRUST); + + if (read_blocked(&src, &device->bdaddr)) + device_block(conn, device); + + if (read_link_key(&src, &device->bdaddr, NULL, NULL) == 0) + device->paired = TRUE; + + return btd_device_ref(device); +} + +void device_set_name(struct btd_device *device, const char *name) +{ + DBusConnection *conn = get_dbus_connection(); + + if (strncmp(name, device->name, MAX_NAME_LENGTH) == 0) + return; + + strncpy(device->name, name, MAX_NAME_LENGTH); + + emit_property_changed(conn, device->path, + DEVICE_INTERFACE, "Name", + DBUS_TYPE_STRING, &name); + + if (device->alias != NULL) + return; + + emit_property_changed(conn, device->path, + DEVICE_INTERFACE, "Alias", + DBUS_TYPE_STRING, &name); +} + +void device_get_name(struct btd_device *device, char *name, size_t len) +{ + strncpy(name, device->name, len); +} + +device_type_t device_get_type(struct btd_device *device) +{ + return device->type; +} + +void device_remove_bonding(struct btd_device *device) +{ + char filename[PATH_MAX + 1]; + char srcaddr[18], dstaddr[18]; + bdaddr_t bdaddr; + + adapter_get_address(device->adapter, &bdaddr); + ba2str(&bdaddr, srcaddr); + ba2str(&device->bdaddr, dstaddr); + + create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, + "linkkeys"); + + /* Delete the link key from storage */ + textfile_casedel(filename, dstaddr); + + btd_adapter_remove_bonding(device->adapter, &device->bdaddr); +} + +static void device_remove_stored(struct btd_device *device) +{ + bdaddr_t src; + char addr[18]; + DBusConnection *conn = get_dbus_connection(); + + adapter_get_address(device->adapter, &src); + ba2str(&device->bdaddr, addr); + + if (device->paired) + device_remove_bonding(device); + delete_entry(&src, "profiles", addr); + delete_entry(&src, "trusts", addr); + delete_entry(&src, "types", addr); + delete_entry(&src, "primary", addr); + delete_all_records(&src, &device->bdaddr); + delete_device_service(&src, &device->bdaddr); + + if (device->blocked) + device_unblock(conn, device, TRUE); +} + +void device_remove(struct btd_device *device, gboolean remove_stored) +{ + + DBG("Removing device %s", device->path); + + if (device->agent) + agent_free(device->agent); + + if (device->bonding) { + uint8_t status; + + if (device->connected) + status = HCI_OE_USER_ENDED_CONNECTION; + else + status = HCI_PAGE_TIMEOUT; + + device_cancel_bonding(device, status); + } + + if (device->browse) { + discover_services_reply(device->browse, -ECANCELED, NULL); + browse_request_cancel(device->browse); + } + + if (device->connected) + do_disconnect(device); + + if (remove_stored) + device_remove_stored(device); + + g_slist_foreach(device->drivers, (GFunc) driver_remove, device); + g_slist_free(device->drivers); + device->drivers = NULL; + + btd_device_unref(device); +} + +gint device_address_cmp(struct btd_device *device, const gchar *address) +{ + char addr[18]; + + ba2str(&device->bdaddr, addr); + return strcasecmp(addr, address); +} + +static gboolean record_has_uuid(const sdp_record_t *rec, + const char *profile_uuid) +{ + sdp_list_t *pat; + + for (pat = rec->pattern; pat != NULL; pat = pat->next) { + char *uuid; + int ret; + + uuid = bt_uuid2string(pat->data); + if (!uuid) + continue; + + ret = strcasecmp(uuid, profile_uuid); + + g_free(uuid); + + if (ret == 0) + return TRUE; + } + + return FALSE; +} + +static GSList *device_match_pattern(struct btd_device *device, + const char *match_uuid, + GSList *profiles) +{ + GSList *l, *uuids = NULL; + + for (l = profiles; l; l = l->next) { + char *profile_uuid = l->data; + const sdp_record_t *rec; + + rec = btd_device_get_record(device, profile_uuid); + if (!rec) + continue; + + if (record_has_uuid(rec, match_uuid)) + uuids = g_slist_append(uuids, profile_uuid); + } + + return uuids; +} + +static GSList *device_match_driver(struct btd_device *device, + struct btd_device_driver *driver, + GSList *profiles) +{ + const char **uuid; + GSList *uuids = NULL; + + for (uuid = driver->uuids; *uuid; uuid++) { + GSList *match; + + /* skip duplicated uuids */ + if (g_slist_find_custom(uuids, *uuid, + (GCompareFunc) strcasecmp)) + continue; + + /* match profile driver */ + match = g_slist_find_custom(profiles, *uuid, + (GCompareFunc) strcasecmp); + if (match) { + uuids = g_slist_append(uuids, match->data); + continue; + } + + /* match pattern driver */ + match = device_match_pattern(device, *uuid, profiles); + uuids = g_slist_concat(uuids, match); + } + + return uuids; +} + +void device_probe_drivers(struct btd_device *device, GSList *profiles) +{ + GSList *list; + char addr[18]; + int err; + + ba2str(&device->bdaddr, addr); + + if (device->blocked) { + DBG("Skipping drivers for blocked device %s", addr); + goto add_uuids; + } + + DBG("Probing drivers for %s", addr); + + for (list = device_drivers; list; list = list->next) { + struct btd_device_driver *driver = list->data; + GSList *probe_uuids; + + probe_uuids = device_match_driver(device, driver, profiles); + + if (!probe_uuids) + continue; + + err = driver->probe(device, probe_uuids); + if (err < 0) { + error("%s driver probe failed for device %s", + driver->name, addr); + g_slist_free(probe_uuids); + continue; + } + + device->drivers = g_slist_append(device->drivers, driver); + g_slist_free(probe_uuids); + } + +add_uuids: + for (list = profiles; list; list = list->next) { + GSList *l = g_slist_find_custom(device->uuids, list->data, + (GCompareFunc) strcasecmp); + if (l) + continue; + + device->uuids = g_slist_insert_sorted(device->uuids, + g_strdup(list->data), + (GCompareFunc) strcasecmp); + } +} + +static void device_remove_drivers(struct btd_device *device, GSList *uuids) +{ + struct btd_adapter *adapter = device_get_adapter(device); + GSList *list, *next; + char srcaddr[18], dstaddr[18]; + bdaddr_t src; + sdp_list_t *records; + + adapter_get_address(adapter, &src); + ba2str(&src, srcaddr); + ba2str(&device->bdaddr, dstaddr); + + records = read_records(&src, &device->bdaddr); + + DBG("Removing drivers for %s", dstaddr); + + for (list = device->drivers; list; list = next) { + struct btd_device_driver *driver = list->data; + const char **uuid; + + next = list->next; + + for (uuid = driver->uuids; *uuid; uuid++) { + if (!g_slist_find_custom(uuids, *uuid, + (GCompareFunc) strcasecmp)) + continue; + + DBG("UUID %s was removed from device %s", + *uuid, dstaddr); + + driver->remove(device); + device->drivers = g_slist_remove(device->drivers, + driver); + break; + } + } + + for (list = uuids; list; list = list->next) { + sdp_record_t *rec; + + device->uuids = g_slist_remove(device->uuids, list->data); + + rec = find_record_in_list(records, list->data); + if (!rec) + continue; + + delete_record(srcaddr, dstaddr, rec->handle); + + records = sdp_list_remove(records, rec); + sdp_record_free(rec); + + } + + if (records) + sdp_list_free(records, (sdp_free_func_t) sdp_record_free); +} + +static void services_changed(struct btd_device *device) +{ + DBusConnection *conn = get_dbus_connection(); + char **uuids; + GSList *l; + int i; + + uuids = g_new0(char *, g_slist_length(device->uuids) + 1); + for (i = 0, l = device->uuids; l; l = l->next, i++) + uuids[i] = l->data; + + emit_array_property_changed(conn, device->path, DEVICE_INTERFACE, + "UUIDs", DBUS_TYPE_STRING, &uuids, i); + + g_free(uuids); +} + +static int rec_cmp(const void *a, const void *b) +{ + const sdp_record_t *r1 = a; + const sdp_record_t *r2 = b; + + return r1->handle - r2->handle; +} + +static void update_services(struct browse_req *req, sdp_list_t *recs) +{ + struct btd_device *device = req->device; + struct btd_adapter *adapter = device_get_adapter(device); + sdp_list_t *seq; + char srcaddr[18], dstaddr[18]; + bdaddr_t src; + + adapter_get_address(adapter, &src); + ba2str(&src, srcaddr); + ba2str(&device->bdaddr, dstaddr); + + for (seq = recs; seq; seq = seq->next) { + sdp_record_t *rec = (sdp_record_t *) seq->data; + sdp_list_t *svcclass = NULL; + gchar *profile_uuid; + GSList *l; + + if (!rec) + break; + + if (sdp_get_service_classes(rec, &svcclass) < 0) + continue; + + /* Check for empty service classes list */ + if (svcclass == NULL) { + DBG("Skipping record with no service classes"); + continue; + } + + /* Extract the first element and skip the remainning */ + profile_uuid = bt_uuid2string(svcclass->data); + if (!profile_uuid) { + sdp_list_free(svcclass, free); + continue; + } + + if (!strcasecmp(profile_uuid, PNP_UUID)) { + uint16_t source, vendor, product, version; + sdp_data_t *pdlist; + + pdlist = sdp_data_get(rec, SDP_ATTR_VENDOR_ID_SOURCE); + source = pdlist ? pdlist->val.uint16 : 0x0000; + + pdlist = sdp_data_get(rec, SDP_ATTR_VENDOR_ID); + vendor = pdlist ? pdlist->val.uint16 : 0x0000; + + pdlist = sdp_data_get(rec, SDP_ATTR_PRODUCT_ID); + product = pdlist ? pdlist->val.uint16 : 0x0000; + + pdlist = sdp_data_get(rec, SDP_ATTR_VERSION); + version = pdlist ? pdlist->val.uint16 : 0x0000; + + if (source || vendor || product || version) + store_device_id(srcaddr, dstaddr, source, + vendor, product, version); + } + + /* Check for duplicates */ + if (sdp_list_find(req->records, rec, rec_cmp)) { + g_free(profile_uuid); + sdp_list_free(svcclass, free); + continue; + } + + store_record(srcaddr, dstaddr, rec); + + /* Copy record */ + req->records = sdp_list_append(req->records, + sdp_copy_record(rec)); + + l = g_slist_find_custom(device->uuids, profile_uuid, + (GCompareFunc) strcmp); + if (!l) + req->profiles_added = + g_slist_append(req->profiles_added, + profile_uuid); + else { + req->profiles_removed = + g_slist_remove(req->profiles_removed, + l->data); + g_free(profile_uuid); + } + + sdp_list_free(svcclass, free); + } +} + +static void store_profiles(struct btd_device *device) +{ + struct btd_adapter *adapter = device->adapter; + bdaddr_t src; + char *str; + + adapter_get_address(adapter, &src); + + if (!device->uuids) { + write_device_profiles(&src, &device->bdaddr, ""); + return; + } + + str = bt_list2string(device->uuids); + write_device_profiles(&src, &device->bdaddr, str); + g_free(str); +} + +static void create_device_reply(struct btd_device *device, struct browse_req *req) +{ + DBusMessage *reply; + + reply = dbus_message_new_method_return(req->msg); + if (!reply) + return; + + dbus_message_append_args(reply, DBUS_TYPE_OBJECT_PATH, &device->path, + DBUS_TYPE_INVALID); + + g_dbus_send_message(req->conn, reply); +} + +static void search_cb(sdp_list_t *recs, int err, gpointer user_data) +{ + struct browse_req *req = user_data; + struct btd_device *device = req->device; + char addr[18]; + + ba2str(&device->bdaddr, addr); + + if (err < 0) { + error("%s: error updating services: %s (%d)", + addr, strerror(-err), -err); + goto send_reply; + } + + update_services(req, recs); + + if (device->tmp_records) + sdp_list_free(device->tmp_records, + (sdp_free_func_t) sdp_record_free); + + device->tmp_records = req->records; + req->records = NULL; + + if (!req->profiles_added && !req->profiles_removed) { + DBG("%s: No service update", addr); + goto send_reply; + } + + /* Probe matching drivers for services added */ + if (req->profiles_added) + device_probe_drivers(device, req->profiles_added); + + /* Remove drivers for services removed */ + if (req->profiles_removed) + device_remove_drivers(device, req->profiles_removed); + + /* Propagate services changes */ + services_changed(req->device); + +send_reply: + if (!req->msg) + goto cleanup; + + if (dbus_message_is_method_call(req->msg, DEVICE_INTERFACE, + "DiscoverServices")) + discover_services_reply(req, err, device->tmp_records); + else if (dbus_message_is_method_call(req->msg, ADAPTER_INTERFACE, + "CreatePairedDevice")) + create_device_reply(device, req); + else if (dbus_message_is_method_call(req->msg, ADAPTER_INTERFACE, + "CreateDevice")) { + if (err < 0) { + DBusMessage *reply; + reply = btd_error_failed(req->msg, strerror(-err)); + g_dbus_send_message(req->conn, reply); + goto cleanup; + } + + create_device_reply(device, req); + device_set_temporary(device, FALSE); + } + +cleanup: + if (!device->temporary) { + bdaddr_t sba, dba; + + adapter_get_address(device->adapter, &sba); + device_get_address(device, &dba); + + store_profiles(device); + write_device_type(&sba, &dba, device->type); + } + + device->browse = NULL; + browse_request_free(req); +} + +static void browse_cb(sdp_list_t *recs, int err, gpointer user_data) +{ + struct browse_req *req = user_data; + struct btd_device *device = req->device; + struct btd_adapter *adapter = device->adapter; + bdaddr_t src; + uuid_t uuid; + + /* If we have a valid response and req->search_uuid == 2, then L2CAP + * UUID & PNP searching was successful -- we are done */ + if (err < 0 || (req->search_uuid == 2 && req->records)) { + if (err == -ECONNRESET && req->reconnect_attempt < 1) { + req->search_uuid--; + req->reconnect_attempt++; + } else + goto done; + } + + update_services(req, recs); + + adapter_get_address(adapter, &src); + + /* Search for mandatory uuids */ + if (uuid_list[req->search_uuid]) { + sdp_uuid16_create(&uuid, uuid_list[req->search_uuid++]); + bt_search_service(&src, &device->bdaddr, &uuid, + browse_cb, user_data, NULL); + return; + } + +done: + search_cb(recs, err, user_data); +} + +static void init_browse(struct browse_req *req, gboolean reverse) +{ + GSList *l; + + /* If we are doing reverse-SDP don't try to detect removed profiles + * since some devices hide their service records while they are + * connected + */ + if (reverse) + return; + + for (l = req->device->uuids; l; l = l->next) + req->profiles_removed = g_slist_append(req->profiles_removed, + l->data); +} + +static char *primary_list_to_string(GSList *primary_list) +{ + GString *services; + GSList *l; + + services = g_string_new(NULL); + + for (l = primary_list; l; l = l->next) { + struct att_primary *primary = l->data; + char service[64]; + + memset(service, 0, sizeof(service)); + + snprintf(service, sizeof(service), "%04X#%04X#%s ", + primary->start, primary->end, primary->uuid); + + services = g_string_append(services, service); + } + + return g_string_free(services, FALSE); +} + +static void primary_cb(GSList *services, guint8 status, gpointer user_data) +{ + struct browse_req *req = user_data; + struct btd_device *device = req->device; + struct btd_adapter *adapter = device->adapter; + GSList *l, *uuids = NULL; + bdaddr_t dba, sba; + char *str; + + if (status) { + DBusMessage *reply; + reply = btd_error_failed(req->msg, att_ecode2str(status)); + g_dbus_send_message(req->conn, reply); + goto done; + } + + services_changed(device); + device_set_temporary(device, FALSE); + + for (l = services; l; l = l->next) { + struct att_primary *prim = l->data; + uuids = g_slist_append(uuids, prim->uuid); + device_add_primary(device, prim); + } + + device_probe_drivers(device, uuids); + g_slist_free(uuids); + + create_device_reply(device, req); + + str = primary_list_to_string(services); + + adapter_get_address(adapter, &sba); + device_get_address(device, &dba); + + write_device_type(&sba, &dba, device->type); + write_device_services(&sba, &dba, str); + g_free(str); + +done: + device->browse = NULL; + browse_request_free(req); +} + +static void gatt_connect_cb(GIOChannel *io, GError *gerr, gpointer user_data) +{ + struct browse_req *req = user_data; + struct btd_device *device = req->device; + + if (gerr) { + DBusMessage *reply; + + DBG("%s", gerr->message); + + reply = btd_error_failed(req->msg, gerr->message); + g_dbus_send_message(req->conn, reply); + + device->browse = NULL; + browse_request_free(req); + + return; + } + + req->attrib = g_attrib_new(io); + gatt_discover_primary(req->attrib, NULL, primary_cb, req); +} + +int device_browse_primary(struct btd_device *device, DBusConnection *conn, + DBusMessage *msg, gboolean secure) +{ + struct btd_adapter *adapter = device->adapter; + struct browse_req *req; + BtIOSecLevel sec_level; + bdaddr_t src; + + if (device->browse) + return -EBUSY; + + req = g_new0(struct browse_req, 1); + req->device = btd_device_ref(device); + + adapter_get_address(adapter, &src); + + sec_level = secure ? BT_IO_SEC_HIGH : BT_IO_SEC_LOW; + + req->io = bt_io_connect(BT_IO_L2CAP, gatt_connect_cb, req, NULL, NULL, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &device->bdaddr, + BT_IO_OPT_CID, GATT_CID, + BT_IO_OPT_SEC_LEVEL, sec_level, + BT_IO_OPT_INVALID); + + if (req->io == NULL ) { + browse_request_free(req); + return -EIO; + } + + if (conn == NULL) + conn = get_dbus_connection(); + + req->conn = dbus_connection_ref(conn); + device->browse = req; + + if (msg) { + const char *sender = dbus_message_get_sender(msg); + + req->msg = dbus_message_ref(msg); + /* Track the request owner to cancel it + * automatically if the owner exits */ + req->listener_id = g_dbus_add_disconnect_watch(conn, + sender, + discover_services_req_exit, + req, NULL); + } + + return 0; +} + +int device_browse_sdp(struct btd_device *device, DBusConnection *conn, + DBusMessage *msg, uuid_t *search, gboolean reverse) +{ + struct btd_adapter *adapter = device->adapter; + struct browse_req *req; + bt_callback_t cb; + bdaddr_t src; + uuid_t uuid; + int err; + + if (device->browse) + return -EBUSY; + + adapter_get_address(adapter, &src); + + req = g_new0(struct browse_req, 1); + req->device = btd_device_ref(device); + if (search) { + memcpy(&uuid, search, sizeof(uuid_t)); + cb = search_cb; + } else { + sdp_uuid16_create(&uuid, uuid_list[req->search_uuid++]); + init_browse(req, reverse); + cb = browse_cb; + } + + err = bt_search_service(&src, &device->bdaddr, &uuid, cb, req, NULL); + if (err < 0) { + browse_request_free(req); + return err; + } + + if (conn == NULL) + conn = get_dbus_connection(); + + req->conn = dbus_connection_ref(conn); + device->browse = req; + + if (msg) { + const char *sender = dbus_message_get_sender(msg); + + req->msg = dbus_message_ref(msg); + /* Track the request owner to cancel it + * automatically if the owner exits */ + req->listener_id = g_dbus_add_disconnect_watch(conn, + sender, + discover_services_req_exit, + req, NULL); + } + + return err; +} + +struct btd_adapter *device_get_adapter(struct btd_device *device) +{ + if (!device) + return NULL; + + return device->adapter; +} + +void device_get_address(struct btd_device *device, bdaddr_t *bdaddr) +{ + bacpy(bdaddr, &device->bdaddr); +} + +const gchar *device_get_path(struct btd_device *device) +{ + if (!device) + return NULL; + + return device->path; +} + +struct agent *device_get_agent(struct btd_device *device) +{ + if (!device) + return NULL; + + if (device->agent) + return device->agent; + + return adapter_get_agent(device->adapter); +} + +gboolean device_is_busy(struct btd_device *device) +{ + return device->browse ? TRUE : FALSE; +} + +gboolean device_is_temporary(struct btd_device *device) +{ + return device->temporary; +} + +void device_set_temporary(struct btd_device *device, gboolean temporary) +{ + if (!device) + return; + + DBG("temporary %d", temporary); + + device->temporary = temporary; +} + +void device_set_type(struct btd_device *device, device_type_t type) +{ + if (!device) + return; + + device->type = type; +} + +static gboolean start_discovery(gpointer user_data) +{ + struct btd_device *device = user_data; + + device_browse_sdp(device, NULL, NULL, NULL, TRUE); + + device->discov_timer = 0; + + return FALSE; +} + +static DBusMessage *new_authentication_return(DBusMessage *msg, int status) +{ + switch (status) { + case 0x00: /* success */ + return dbus_message_new_method_return(msg); + + case 0x04: /* page timeout */ + return dbus_message_new_error(msg, + ERROR_INTERFACE ".ConnectionAttemptFailed", + "Page Timeout"); + case 0x08: /* connection timeout */ + return dbus_message_new_error(msg, + ERROR_INTERFACE ".ConnectionAttemptFailed", + "Connection Timeout"); + case 0x10: /* connection accept timeout */ + case 0x22: /* LMP response timeout */ + case 0x28: /* instant passed - is this a timeout? */ + return dbus_message_new_error(msg, + ERROR_INTERFACE ".AuthenticationTimeout", + "Authentication Timeout"); + case 0x17: /* too frequent pairing attempts */ + return dbus_message_new_error(msg, + ERROR_INTERFACE ".RepeatedAttempts", + "Repeated Attempts"); + + case 0x06: + case 0x18: /* pairing not allowed (e.g. gw rejected attempt) */ + return dbus_message_new_error(msg, + ERROR_INTERFACE ".AuthenticationRejected", + "Authentication Rejected"); + + case 0x07: /* memory capacity */ + case 0x09: /* connection limit */ + case 0x0a: /* synchronous connection limit */ + case 0x0d: /* limited resources */ + case 0x13: /* user ended the connection */ + case 0x14: /* terminated due to low resources */ + case 0x16: /* connection terminated */ + return dbus_message_new_error(msg, + ERROR_INTERFACE ".AuthenticationCanceled", + "Authentication Canceled"); + + case 0x05: /* authentication failure */ + case 0x0E: /* rejected due to security reasons - is this auth failure? */ + case 0x25: /* encryption mode not acceptable - is this auth failure? */ + case 0x26: /* link key cannot be changed - is this auth failure? */ + case 0x29: /* pairing with unit key unsupported - is this auth failure? */ + case 0x2f: /* insufficient security - is this auth failure? */ + default: + return dbus_message_new_error(msg, + ERROR_INTERFACE ".AuthenticationFailed", + "Authentication Failed"); + } +} + +static void bonding_request_free(struct bonding_req *bonding) +{ + struct btd_device *device; + + if (!bonding) + return; + + if (bonding->listener_id) + g_dbus_remove_watch(bonding->conn, bonding->listener_id); + + if (bonding->msg) + dbus_message_unref(bonding->msg); + + if (bonding->conn) + dbus_connection_unref(bonding->conn); + + if (bonding->io) + g_io_channel_unref(bonding->io); + + device = bonding->device; + g_free(bonding); + + if (!device) + return; + + device->bonding = NULL; + + adapter_resume_discovery(device->adapter); + + if (!device->agent) + return; + + agent_cancel(device->agent); + agent_free(device->agent); + device->agent = NULL; +} + +void device_set_paired(struct btd_device *device, gboolean value) +{ + DBusConnection *conn = get_dbus_connection(); + + if (device->paired == value) + return; + + device->paired = value; + + emit_property_changed(conn, device->path, DEVICE_INTERFACE, "Paired", + DBUS_TYPE_BOOLEAN, &value); +} + +static void device_agent_removed(struct agent *agent, void *user_data) +{ + struct btd_device *device = user_data; + + device->agent = NULL; + + if (device->authr) + device->authr->agent = NULL; +} + +static struct bonding_req *bonding_request_new(DBusConnection *conn, + DBusMessage *msg, + struct btd_device *device, + const char *agent_path, + uint8_t capability) +{ + struct bonding_req *bonding; + const char *name = dbus_message_get_sender(msg); + struct agent *agent; + char addr[18]; + + ba2str(&device->bdaddr, addr); + DBG("Requesting bonding for %s", addr); + + if (!agent_path) + goto proceed; + + agent = agent_create(device->adapter, name, agent_path, + capability, + device_agent_removed, + device); + if (!agent) { + error("Unable to create a new agent"); + return NULL; + } + + device->agent = agent; + + DBG("Temporary agent registered for %s at %s:%s", + addr, name, agent_path); + +proceed: + bonding = g_new0(struct bonding_req, 1); + + bonding->conn = dbus_connection_ref(conn); + bonding->msg = dbus_message_ref(msg); + + adapter_suspend_discovery(device->adapter); + + return bonding; +} + +static void create_bond_req_exit(DBusConnection *conn, void *user_data) +{ + struct btd_device *device = user_data; + char addr[18]; + + ba2str(&device->bdaddr, addr); + DBG("%s: requestor exited before bonding was completed", addr); + + if (device->authr) + device_cancel_authentication(device, FALSE); + + if (device->bonding) { + device->bonding->listener_id = 0; + device_request_disconnect(device, NULL); + } +} + +DBusMessage *device_create_bonding(struct btd_device *device, + DBusConnection *conn, + DBusMessage *msg, + const char *agent_path, + uint8_t capability) +{ + char filename[PATH_MAX + 1]; + char *str, srcaddr[18], dstaddr[18]; + struct btd_adapter *adapter = device->adapter; + struct bonding_req *bonding; + bdaddr_t src; + int err; + + adapter_get_address(adapter, &src); + ba2str(&src, srcaddr); + ba2str(&device->bdaddr, dstaddr); + + if (device->bonding) + return btd_error_in_progress(msg); + + /* check if a link key already exists */ + create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, + "linkkeys"); + + str = textfile_caseget(filename, dstaddr); + if (str) { + free(str); + return btd_error_already_exists(msg); + } + + err = adapter_create_bonding(adapter, &device->bdaddr, capability); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + bonding = bonding_request_new(conn, msg, device, agent_path, + capability); + if (!bonding) { + adapter_cancel_bonding(adapter, &device->bdaddr); + return NULL; + } + + bonding->listener_id = g_dbus_add_disconnect_watch(conn, + dbus_message_get_sender(msg), + create_bond_req_exit, device, + NULL); + + device->bonding = bonding; + bonding->device = device; + + return NULL; +} + +void device_simple_pairing_complete(struct btd_device *device, uint8_t status) +{ + struct authentication_req *auth = device->authr; + + if (auth && auth->type == AUTH_TYPE_NOTIFY && auth->agent) + agent_cancel(auth->agent); +} + +static void device_auth_req_free(struct btd_device *device) +{ + g_free(device->authr); + device->authr = NULL; +} + +void device_bonding_complete(struct btd_device *device, uint8_t status) +{ + struct bonding_req *bonding = device->bonding; + struct authentication_req *auth = device->authr; + + DBG("bonding %p status 0x%02x", bonding, status); + + if (auth && auth->type == AUTH_TYPE_NOTIFY && auth->agent) + agent_cancel(auth->agent); + + if (status) { + device_cancel_authentication(device, TRUE); + device_cancel_bonding(device, status); + return; + } + + device_auth_req_free(device); + + /* If we're already paired nothing more is needed */ + if (device->paired) + return; + + device_set_paired(device, TRUE); + + /* If we were initiators start service discovery immediately. + * However if the other end was the initator wait a few seconds + * before SDP. This is due to potential IOP issues if the other + * end starts doing SDP at the same time as us */ + if (bonding) { + DBG("Proceeding with service discovery"); + /* If we are initiators remove any discovery timer and just + * start discovering services directly */ + if (device->discov_timer) { + g_source_remove(device->discov_timer); + device->discov_timer = 0; + } + + device_browse_sdp(device, bonding->conn, bonding->msg, + NULL, FALSE); + + bonding_request_free(bonding); + } else { + if (!device->browse && !device->discov_timer && + main_opts.reverse_sdp) { + /* If we are not initiators and there is no currently + * active discovery or discovery timer, set discovery + * timer */ + DBG("setting timer for reverse service discovery"); + device->discov_timer = g_timeout_add_seconds( + DISCOVERY_TIMER, + start_discovery, + device); + } + } +} + +gboolean device_is_creating(struct btd_device *device, const char *sender) +{ + DBusMessage *msg; + + if (device->bonding && device->bonding->msg) + msg = device->bonding->msg; + else if (device->browse && device->browse->msg) + msg = device->browse->msg; + else + return FALSE; + + if (!dbus_message_is_method_call(msg, ADAPTER_INTERFACE, + "CreatePairedDevice") && + !dbus_message_is_method_call(msg, ADAPTER_INTERFACE, + "CreateDevice")) + return FALSE; + + if (sender == NULL) + return TRUE; + + return g_str_equal(sender, dbus_message_get_sender(msg)); +} + +gboolean device_is_bonding(struct btd_device *device, const char *sender) +{ + struct bonding_req *bonding = device->bonding; + + if (!device->bonding) + return FALSE; + + if (!sender) + return TRUE; + + return g_str_equal(sender, dbus_message_get_sender(bonding->msg)); +} + +void device_cancel_bonding(struct btd_device *device, uint8_t status) +{ + struct bonding_req *bonding = device->bonding; + DBusMessage *reply; + char addr[18]; + + if (!bonding) + return; + + ba2str(&device->bdaddr, addr); + DBG("Canceling bonding request for %s", addr); + + if (device->authr) + device_cancel_authentication(device, FALSE); + + reply = new_authentication_return(bonding->msg, status); + g_dbus_send_message(bonding->conn, reply); + + bonding_request_cancel(bonding); + bonding_request_free(bonding); +} + +static void pincode_cb(struct agent *agent, DBusError *err, + const char *pincode, void *data) +{ + struct authentication_req *auth = data; + struct btd_device *device = auth->device; + + /* No need to reply anything if the authentication already failed */ + if (auth->cb == NULL) + return; + + ((agent_pincode_cb) auth->cb)(agent, err, pincode, device); + + device->authr->cb = NULL; + device->authr->agent = NULL; +} + +static void confirm_cb(struct agent *agent, DBusError *err, void *data) +{ + struct authentication_req *auth = data; + struct btd_device *device = auth->device; + + /* No need to reply anything if the authentication already failed */ + if (auth->cb == NULL) + return; + + ((agent_cb) auth->cb)(agent, err, device); + + device->authr->cb = NULL; + device->authr->agent = NULL; +} + +static void passkey_cb(struct agent *agent, DBusError *err, + uint32_t passkey, void *data) +{ + struct authentication_req *auth = data; + struct btd_device *device = auth->device; + + /* No need to reply anything if the authentication already failed */ + if (auth->cb == NULL) + return; + + ((agent_passkey_cb) auth->cb)(agent, err, passkey, device); + + device->authr->cb = NULL; + device->authr->agent = NULL; +} + +int device_request_authentication(struct btd_device *device, auth_type_t type, + uint32_t passkey, void *cb) +{ + struct authentication_req *auth; + struct agent *agent; + char addr[18]; + int err; + + ba2str(&device->bdaddr, addr); + DBG("Requesting agent authentication for %s", addr); + + if (device->authr) { + error("Authentication already requested for %s", addr); + return -EALREADY; + } + + agent = device_get_agent(device); + if (!agent) { + error("No agent available for request type %d", type); + return -EPERM; + } + + auth = g_new0(struct authentication_req, 1); + auth->agent = agent; + auth->device = device; + auth->cb = cb; + auth->type = type; + device->authr = auth; + + switch (type) { + case AUTH_TYPE_PINCODE: + err = agent_request_pincode(agent, device, pincode_cb, + auth, NULL); + break; + case AUTH_TYPE_PASSKEY: + err = agent_request_passkey(agent, device, passkey_cb, + auth, NULL); + break; + case AUTH_TYPE_CONFIRM: + err = agent_request_confirmation(agent, device, passkey, + confirm_cb, auth, NULL); + break; + case AUTH_TYPE_NOTIFY: + err = agent_display_passkey(agent, device, passkey); + break; + default: + err = -EINVAL; + } + + if (err < 0) { + error("Failed requesting authentication"); + device_auth_req_free(device); + } + + return err; +} + +static void cancel_authentication(struct authentication_req *auth) +{ + struct btd_device *device; + struct agent *agent; + DBusError err; + + if (!auth || !auth->cb) + return; + + device = auth->device; + agent = auth->agent; + + dbus_error_init(&err); + dbus_set_error_const(&err, "org.bluez.Error.Canceled", NULL); + + switch (auth->type) { + case AUTH_TYPE_PINCODE: + ((agent_pincode_cb) auth->cb)(agent, &err, NULL, device); + break; + case AUTH_TYPE_CONFIRM: + ((agent_cb) auth->cb)(agent, &err, device); + break; + case AUTH_TYPE_PASSKEY: + ((agent_passkey_cb) auth->cb)(agent, &err, 0, device); + break; + case AUTH_TYPE_NOTIFY: + /* User Notify doesn't require any reply */ + break; + } + + dbus_error_free(&err); + auth->cb = NULL; +} + +void device_cancel_authentication(struct btd_device *device, gboolean aborted) +{ + struct authentication_req *auth = device->authr; + char addr[18]; + + if (!auth) + return; + + ba2str(&device->bdaddr, addr); + DBG("Canceling authentication request for %s", addr); + + if (auth->agent) + agent_cancel(auth->agent); + + if (!aborted) + cancel_authentication(auth); + + device_auth_req_free(device); +} + +gboolean device_is_authenticating(struct btd_device *device) +{ + return (device->authr != NULL); +} + +gboolean device_is_authorizing(struct btd_device *device) +{ + return device->authorizing; +} + +void device_set_authorizing(struct btd_device *device, gboolean auth) +{ + device->authorizing = auth; +} + +void btd_device_add_service(struct btd_device *device, const char *path) +{ + if (g_slist_find_custom(device->services, path, (GCompareFunc) strcmp)) + return; + + device->services = g_slist_append(device->services, g_strdup(path)); +} + +void device_add_primary(struct btd_device *device, struct att_primary *prim) +{ + device->primaries = g_slist_append(device->primaries, prim); +} + +GSList *btd_device_get_primaries(struct btd_device *device) +{ + return device->primaries; +} + +void btd_device_add_uuid(struct btd_device *device, const char *uuid) +{ + GSList *uuid_list; + char *new_uuid; + + if (g_slist_find_custom(device->uuids, uuid, + (GCompareFunc) strcasecmp)) + return; + + new_uuid = g_strdup(uuid); + uuid_list = g_slist_append(NULL, new_uuid); + + device_probe_drivers(device, uuid_list); + + g_free(new_uuid); + g_slist_free(uuid_list); + + store_profiles(device); + services_changed(device); +} + +const sdp_record_t *btd_device_get_record(struct btd_device *device, + const char *uuid) +{ + bdaddr_t src; + + if (device->tmp_records) { + const sdp_record_t *record; + + record = find_record_in_list(device->tmp_records, uuid); + if (record != NULL) + return record; + } + + adapter_get_address(device->adapter, &src); + + device->tmp_records = read_records(&src, &device->bdaddr); + if (!device->tmp_records) + return NULL; + + return find_record_in_list(device->tmp_records, uuid); +} + +int btd_register_device_driver(struct btd_device_driver *driver) +{ + device_drivers = g_slist_append(device_drivers, driver); + + return 0; +} + +void btd_unregister_device_driver(struct btd_device_driver *driver) +{ + device_drivers = g_slist_remove(device_drivers, driver); +} + +struct btd_device *btd_device_ref(struct btd_device *device) +{ + device->ref++; + + DBG("%p: ref=%d", device, device->ref); + + return device; +} + +void btd_device_unref(struct btd_device *device) +{ + DBusConnection *conn = get_dbus_connection(); + gchar *path; + + device->ref--; + + DBG("%p: ref=%d", device, device->ref); + + if (device->ref > 0) + return; + + path = g_strdup(device->path); + + g_dbus_unregister_interface(conn, path, DEVICE_INTERFACE); + + g_free(path); +} diff --git a/src/device.h b/src/device.h new file mode 100644 index 0000000..3ce212b --- /dev/null +++ b/src/device.h @@ -0,0 +1,119 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 + * + */ + +#define DEVICE_INTERFACE "org.bluez.Device" + +struct btd_device; +struct att_primary; + +typedef enum { + AUTH_TYPE_PINCODE, + AUTH_TYPE_PASSKEY, + AUTH_TYPE_CONFIRM, + AUTH_TYPE_NOTIFY, +} auth_type_t; + +typedef enum { + DEVICE_TYPE_UNKNOWN, + DEVICE_TYPE_BREDR, + DEVICE_TYPE_LE, + DEVICE_TYPE_DUALMODE +} device_type_t; + +struct btd_device *device_create(DBusConnection *conn, + struct btd_adapter *adapter, + const gchar *address, device_type_t type); +void device_set_name(struct btd_device *device, const char *name); +void device_get_name(struct btd_device *device, char *name, size_t len); +device_type_t device_get_type(struct btd_device *device); +void device_remove(struct btd_device *device, gboolean remove_stored); +gint device_address_cmp(struct btd_device *device, const gchar *address); +int device_browse_primary(struct btd_device *device, DBusConnection *conn, + DBusMessage *msg, gboolean secure); +int device_browse_sdp(struct btd_device *device, DBusConnection *conn, + DBusMessage *msg, uuid_t *search, gboolean reverse); +void device_probe_drivers(struct btd_device *device, GSList *profiles); +const sdp_record_t *btd_device_get_record(struct btd_device *device, + const char *uuid); +GSList *btd_device_get_primaries(struct btd_device *device); +void btd_device_add_service(struct btd_device *device, const char *path); +void device_add_primary(struct btd_device *device, struct att_primary *prim); +void btd_device_add_uuid(struct btd_device *device, const char *uuid); +struct btd_adapter *device_get_adapter(struct btd_device *device); +void device_get_address(struct btd_device *device, bdaddr_t *bdaddr); +const gchar *device_get_path(struct btd_device *device); +struct agent *device_get_agent(struct btd_device *device); +gboolean device_is_busy(struct btd_device *device); +gboolean device_is_temporary(struct btd_device *device); +gboolean device_is_paired(struct btd_device *device); +gboolean device_is_trusted(struct btd_device *device); +void device_set_paired(struct btd_device *device, gboolean paired); +void device_set_temporary(struct btd_device *device, gboolean temporary); +void device_set_cap(struct btd_device *device, uint8_t cap); +void device_set_type(struct btd_device *device, device_type_t type); +uint8_t device_get_cap(struct btd_device *device); +void device_set_auth(struct btd_device *device, uint8_t auth); +uint8_t device_get_auth(struct btd_device *device); +gboolean device_is_connected(struct btd_device *device); +DBusMessage *device_create_bonding(struct btd_device *device, + DBusConnection *conn, DBusMessage *msg, + const char *agent_path, uint8_t capability); +void device_remove_bonding(struct btd_device *device); +void device_bonding_complete(struct btd_device *device, uint8_t status); +void device_simple_pairing_complete(struct btd_device *device, uint8_t status); +gboolean device_is_creating(struct btd_device *device, const char *sender); +gboolean device_is_bonding(struct btd_device *device, const char *sender); +void device_cancel_bonding(struct btd_device *device, uint8_t status); +int device_request_authentication(struct btd_device *device, auth_type_t type, + uint32_t passkey, void *cb); +void device_cancel_authentication(struct btd_device *device, gboolean aborted); +gboolean device_is_authenticating(struct btd_device *device); +gboolean device_is_authorizing(struct btd_device *device); +void device_set_authorizing(struct btd_device *device, gboolean auth); +void device_add_connection(struct btd_device *device, DBusConnection *conn); +void device_remove_connection(struct btd_device *device, DBusConnection *conn); +void device_request_disconnect(struct btd_device *device, DBusMessage *msg); + +typedef void (*disconnect_watch) (struct btd_device *device, gboolean removal, + void *user_data); + +guint device_add_disconnect_watch(struct btd_device *device, + disconnect_watch watch, void *user_data, + GDestroyNotify destroy); +void device_remove_disconnect_watch(struct btd_device *device, guint id); + +#define BTD_UUIDS(args...) ((const char *[]) { args, NULL } ) + +struct btd_device_driver { + const char *name; + const char **uuids; + int (*probe) (struct btd_device *device, GSList *uuids); + void (*remove) (struct btd_device *device); +}; + +int btd_register_device_driver(struct btd_device_driver *driver); +void btd_unregister_device_driver(struct btd_device_driver *driver); + +struct btd_device *btd_device_ref(struct btd_device *device); +void btd_device_unref(struct btd_device *device); diff --git a/src/error.c b/src/error.c new file mode 100644 index 0000000..c2d9baa --- /dev/null +++ b/src/error.c @@ -0,0 +1,116 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * Copyright (C) 2007-2008 Fabien Chevalier + * + * + * 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 +#endif + +#include + +#include "error.h" + +DBusMessage *btd_error_invalid_args(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); +} + +DBusMessage *btd_error_busy(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress", + "Operation already in progress"); +} + +DBusMessage *btd_error_already_exists(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".AlreadyExists", + "Already Exists"); +} + +DBusMessage *btd_error_not_supported(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".NotSupported", + "Operation is not supported"); +} + +DBusMessage *btd_error_not_connected(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".NotConnected", + "Not Connected"); +} + +DBusMessage *btd_error_already_connected(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".AlreadyConnected", + "Already Connected"); +} + +DBusMessage *btd_error_in_progress(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress", + "In Progress"); +} + +DBusMessage *btd_error_not_available(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable", + "Operation currently not available"); +} + +DBusMessage *btd_error_does_not_exist(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExist", + "Does Not Exist"); +} + +DBusMessage *btd_error_not_authorized(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAuthorized", + "Operation Not Authorized"); +} + +DBusMessage *btd_error_no_such_adapter(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".NoSuchAdapter", + "No such adapter"); +} + +DBusMessage *btd_error_agent_not_available(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".AgentNotAvailable", + "Agent Not Available"); +} + +DBusMessage *btd_error_not_ready(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".NotReady", + "Resource Not Ready"); +} + +DBusMessage *btd_error_failed(DBusMessage *msg, const char *str) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE + ".Failed", "%s", str); +} diff --git a/src/error.h b/src/error.h new file mode 100644 index 0000000..cdb8919 --- /dev/null +++ b/src/error.h @@ -0,0 +1,43 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * Copyright (C) 2007-2008 Fabien Chevalier + * + * + * 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 + * + */ + +#include + +#define ERROR_INTERFACE "org.bluez.Error" + +DBusMessage *btd_error_invalid_args(DBusMessage *msg); +DBusMessage *btd_error_busy(DBusMessage *msg); +DBusMessage *btd_error_already_exists(DBusMessage *msg); +DBusMessage *btd_error_not_supported(DBusMessage *msg); +DBusMessage *btd_error_not_connected(DBusMessage *msg); +DBusMessage *btd_error_already_connected(DBusMessage *msg); +DBusMessage *btd_error_not_available(DBusMessage *msg); +DBusMessage *btd_error_in_progress(DBusMessage *msg); +DBusMessage *btd_error_does_not_exist(DBusMessage *msg); +DBusMessage *btd_error_not_authorized(DBusMessage *msg); +DBusMessage *btd_error_no_such_adapter(DBusMessage *msg); +DBusMessage *btd_error_agent_not_available(DBusMessage *msg); +DBusMessage *btd_error_not_ready(DBusMessage *msg); +DBusMessage *btd_error_failed(DBusMessage *msg, const char *str); diff --git a/src/event.c b/src/event.c new file mode 100644 index 0000000..4ca1be5 --- /dev/null +++ b/src/event.c @@ -0,0 +1,731 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "log.h" +#include "textfile.h" + +#include "hcid.h" +#include "adapter.h" +#include "manager.h" +#include "device.h" +#include "error.h" +#include "glib-helper.h" +#include "dbus-common.h" +#include "agent.h" +#include "storage.h" +#include "event.h" +#include "sdpd.h" + +struct eir_data { + GSList *services; + int flags; + char *name; + gboolean name_complete; +}; + +static gboolean get_adapter_and_device(bdaddr_t *src, bdaddr_t *dst, + struct btd_adapter **adapter, + struct btd_device **device, + gboolean create) +{ + DBusConnection *conn = get_dbus_connection(); + char peer_addr[18]; + + *adapter = manager_find_adapter(src); + if (!*adapter) { + error("Unable to find matching adapter"); + return FALSE; + } + + ba2str(dst, peer_addr); + + if (create) + *device = adapter_get_device(conn, *adapter, peer_addr); + else + *device = adapter_find_device(*adapter, peer_addr); + + if (create && !*device) { + error("Unable to get device object!"); + return FALSE; + } + + return TRUE; +} + +/***************************************************************** + * + * Section reserved to HCI commands confirmation handling and low + * level events(eg: device attached/dettached. + * + *****************************************************************/ + +static void pincode_cb(struct agent *agent, DBusError *derr, + const char *pincode, struct btd_device *device) +{ + struct btd_adapter *adapter = device_get_adapter(device); + bdaddr_t sba, dba; + int err; + + device_get_address(device, &dba); + + if (derr) { + err = btd_adapter_pincode_reply(adapter, &dba, NULL); + if (err < 0) + goto fail; + return; + } + + err = btd_adapter_pincode_reply(adapter, &dba, pincode); + if (err < 0) + goto fail; + + adapter_get_address(adapter, &sba); + + return; + +fail: + error("Sending PIN code reply failed: %s (%d)", strerror(-err), -err); +} + +int btd_event_request_pin(bdaddr_t *sba, bdaddr_t *dba) +{ + struct btd_adapter *adapter; + struct btd_device *device; + char pin[17]; + int pinlen; + + if (!get_adapter_and_device(sba, dba, &adapter, &device, TRUE)) + return -ENODEV; + + memset(pin, 0, sizeof(pin)); + pinlen = read_pin_code(sba, dba, pin); + if (pinlen > 0) { + btd_adapter_pincode_reply(adapter, dba, pin); + return 0; + } + + return device_request_authentication(device, AUTH_TYPE_PINCODE, 0, + pincode_cb); +} + +static int confirm_reply(struct btd_adapter *adapter, + struct btd_device *device, gboolean success) +{ + bdaddr_t bdaddr; + + device_get_address(device, &bdaddr); + + return btd_adapter_confirm_reply(adapter, &bdaddr, success); +} + +static void confirm_cb(struct agent *agent, DBusError *err, void *user_data) +{ + struct btd_device *device = user_data; + struct btd_adapter *adapter = device_get_adapter(device); + gboolean success = (err == NULL) ? TRUE : FALSE; + + confirm_reply(adapter, device, success); +} + +static void passkey_cb(struct agent *agent, DBusError *err, uint32_t passkey, + void *user_data) +{ + struct btd_device *device = user_data; + struct btd_adapter *adapter = device_get_adapter(device); + bdaddr_t bdaddr; + + device_get_address(device, &bdaddr); + + if (err) + passkey = INVALID_PASSKEY; + + btd_adapter_passkey_reply(adapter, &bdaddr, passkey); +} + +int btd_event_user_confirm(bdaddr_t *sba, bdaddr_t *dba, uint32_t passkey) +{ + struct btd_adapter *adapter; + struct btd_device *device; + + if (!get_adapter_and_device(sba, dba, &adapter, &device, TRUE)) + return -ENODEV; + + return device_request_authentication(device, AUTH_TYPE_CONFIRM, + passkey, confirm_cb); +} + +int btd_event_user_passkey(bdaddr_t *sba, bdaddr_t *dba) +{ + struct btd_adapter *adapter; + struct btd_device *device; + + if (!get_adapter_and_device(sba, dba, &adapter, &device, TRUE)) + return -ENODEV; + + return device_request_authentication(device, AUTH_TYPE_PASSKEY, 0, + passkey_cb); +} + +int btd_event_user_notify(bdaddr_t *sba, bdaddr_t *dba, uint32_t passkey) +{ + struct btd_adapter *adapter; + struct btd_device *device; + + if (!get_adapter_and_device(sba, dba, &adapter, &device, TRUE)) + return -ENODEV; + + return device_request_authentication(device, AUTH_TYPE_NOTIFY, + passkey, NULL); +} + +void btd_event_bonding_complete(bdaddr_t *local, bdaddr_t *peer, + uint8_t status) +{ + struct btd_adapter *adapter; + struct btd_device *device; + gboolean create; + + DBG("status 0x%02x", status); + + create = status ? FALSE : TRUE; + + if (!get_adapter_and_device(local, peer, &adapter, &device, create)) + return; + + if (device) + device_bonding_complete(device, status); +} + +void btd_event_simple_pairing_complete(bdaddr_t *local, bdaddr_t *peer, + uint8_t status) +{ + struct btd_adapter *adapter; + struct btd_device *device; + gboolean create; + + DBG("status=%02x", status); + + create = status ? FALSE : TRUE; + + if (!get_adapter_and_device(local, peer, &adapter, &device, create)) + return; + + if (!device) + return; + + device_simple_pairing_complete(device, status); +} + +static int parse_eir_data(struct eir_data *eir, uint8_t *eir_data, + size_t eir_length) +{ + uint16_t len = 0; + size_t total; + size_t uuid16_count = 0; + size_t uuid32_count = 0; + size_t uuid128_count = 0; + uint8_t *uuid16 = NULL; + uint8_t *uuid32 = NULL; + uint8_t *uuid128 = NULL; + uuid_t service; + char *uuid_str; + unsigned int i; + + eir->flags = -1; + + /* No EIR data to parse */ + if (eir_data == NULL || eir_length == 0) + return 0; + + while (len < eir_length - 1) { + uint8_t field_len = eir_data[0]; + + /* Check for the end of EIR */ + if (field_len == 0) + break; + + switch (eir_data[1]) { + case EIR_UUID16_SOME: + case EIR_UUID16_ALL: + uuid16_count = field_len / 2; + uuid16 = &eir_data[2]; + break; + case EIR_UUID32_SOME: + case EIR_UUID32_ALL: + uuid32_count = field_len / 4; + uuid32 = &eir_data[2]; + break; + case EIR_UUID128_SOME: + case EIR_UUID128_ALL: + uuid128_count = field_len / 16; + uuid128 = &eir_data[2]; + break; + case EIR_FLAGS: + eir->flags = eir_data[2]; + break; + case EIR_NAME_SHORT: + case EIR_NAME_COMPLETE: + if (g_utf8_validate((char *) &eir_data[2], + field_len - 1, NULL)) + eir->name = g_strndup((char *) &eir_data[2], + field_len - 1); + else + eir->name = g_strdup(""); + eir->name_complete = eir_data[1] == EIR_NAME_COMPLETE; + break; + } + + len += field_len + 1; + eir_data += field_len + 1; + } + + /* Bail out if got incorrect length */ + if (len > eir_length) + return -EINVAL; + + total = uuid16_count + uuid32_count + uuid128_count; + + /* No UUIDs were parsed, so skip code below */ + if (!total) + return 0; + + /* Generate uuids in SDP format (EIR data is Little Endian) */ + service.type = SDP_UUID16; + for (i = 0; i < uuid16_count; i++) { + uint16_t val16 = uuid16[1]; + + val16 = (val16 << 8) + uuid16[0]; + service.value.uuid16 = val16; + uuid_str = bt_uuid2string(&service); + eir->services = g_slist_append(eir->services, uuid_str); + uuid16 += 2; + } + + service.type = SDP_UUID32; + for (i = uuid16_count; i < uuid32_count + uuid16_count; i++) { + uint32_t val32 = uuid32[3]; + int k; + + for (k = 2; k >= 0; k--) + val32 = (val32 << 8) + uuid32[k]; + + service.value.uuid32 = val32; + uuid_str = bt_uuid2string(&service); + eir->services = g_slist_append(eir->services, uuid_str); + uuid32 += 4; + } + + service.type = SDP_UUID128; + for (i = uuid32_count + uuid16_count; i < total; i++) { + int k; + + for (k = 0; k < 16; k++) + service.value.uuid128.data[k] = uuid128[16 - k - 1]; + + uuid_str = bt_uuid2string(&service); + eir->services = g_slist_append(eir->services, uuid_str); + uuid128 += 16; + } + + return 0; +} + +static void free_eir_data(struct eir_data *eir) +{ + g_slist_foreach(eir->services, (GFunc) g_free, NULL); + g_slist_free(eir->services); + g_free(eir->name); +} + +void btd_event_advertising_report(bdaddr_t *local, le_advertising_info *info) +{ + struct btd_adapter *adapter; + struct eir_data eir_data; + int8_t rssi; + int err; + + adapter = manager_find_adapter(local); + if (adapter == NULL) { + error("No matching adapter found"); + return; + } + + memset(&eir_data, 0, sizeof(eir_data)); + err = parse_eir_data(&eir_data, info->data, info->length); + if (err < 0) + error("Error parsing advertising data: %s (%d)", + strerror(-err), -err); + + rssi = *(info->data + info->length); + + adapter_update_device_from_info(adapter, info->bdaddr, rssi, + info->evt_type, eir_data.name, + eir_data.services, eir_data.flags); + + free_eir_data(&eir_data); +} + +static void update_lastseen(bdaddr_t *sba, bdaddr_t *dba) +{ + time_t t; + struct tm *tm; + + t = time(NULL); + tm = gmtime(&t); + + write_lastseen_info(sba, dba, tm); +} + +static void update_lastused(bdaddr_t *sba, bdaddr_t *dba) +{ + time_t t; + struct tm *tm; + + t = time(NULL); + tm = gmtime(&t); + + write_lastused_info(sba, dba, tm); +} + +void btd_event_device_found(bdaddr_t *local, bdaddr_t *peer, uint32_t class, + int8_t rssi, uint8_t *data) +{ + char filename[PATH_MAX + 1]; + struct btd_adapter *adapter; + char local_addr[18], peer_addr[18], *alias, *name; + name_status_t name_status; + struct eir_data eir_data; + int state, err; + dbus_bool_t legacy; + unsigned char features[8]; + const char *dev_name; + + ba2str(local, local_addr); + ba2str(peer, peer_addr); + + adapter = manager_find_adapter(local); + if (!adapter) { + error("No matching adapter found"); + return; + } + + update_lastseen(local, peer); + write_remote_class(local, peer, class); + + if (data) + write_remote_eir(local, peer, data); + + /* + * Workaround to identify periodic inquiry: inquiry complete event is + * sent after each window, however there isn't an event to indicate the + * beginning of a new periodic inquiry window. + */ + state = adapter_get_state(adapter); + if (!(state & (STATE_STDINQ | STATE_LE_SCAN | STATE_PINQ))) { + state |= STATE_PINQ; + adapter_set_state(adapter, state); + } + + /* the inquiry result can be triggered by NON D-Bus client */ + if (adapter_get_discover_type(adapter) & DISC_RESOLVNAME && + adapter_has_discov_sessions(adapter)) + name_status = NAME_REQUIRED; + else + name_status = NAME_NOT_REQUIRED; + + create_name(filename, PATH_MAX, STORAGEDIR, local_addr, "aliases"); + alias = textfile_get(filename, peer_addr); + + create_name(filename, PATH_MAX, STORAGEDIR, local_addr, "names"); + name = textfile_get(filename, peer_addr); + + if (data) + legacy = FALSE; + else if (name == NULL) + legacy = TRUE; + else if (read_remote_features(local, peer, NULL, features) == 0) { + if (features[0] & 0x01) + legacy = FALSE; + else + legacy = TRUE; + } else + legacy = TRUE; + + memset(&eir_data, 0, sizeof(eir_data)); + err = parse_eir_data(&eir_data, data, EIR_DATA_LENGTH); + if (err < 0) + error("Error parsing EIR data: %s (%d)", strerror(-err), -err); + + /* Complete EIR names are always used. Shortened EIR names are only + * used if there is no name already in storage. */ + dev_name = name; + if (eir_data.name != NULL) { + if (eir_data.name_complete) { + write_device_name(local, peer, eir_data.name); + name_status = NAME_NOT_REQUIRED; + dev_name = eir_data.name; + } else if (name == NULL) + dev_name = eir_data.name; + } + + adapter_update_found_devices(adapter, peer, rssi, class, dev_name, + alias, legacy, eir_data.services, + name_status); + + free_eir_data(&eir_data); + free(name); + free(alias); +} + +void btd_event_set_legacy_pairing(bdaddr_t *local, bdaddr_t *peer, + gboolean legacy) +{ + struct btd_adapter *adapter; + struct remote_dev_info *dev, match; + + adapter = manager_find_adapter(local); + if (!adapter) { + error("No matching adapter found"); + return; + } + + memset(&match, 0, sizeof(struct remote_dev_info)); + bacpy(&match.bdaddr, peer); + match.name_status = NAME_ANY; + + dev = adapter_search_found_devices(adapter, &match); + if (dev) + dev->legacy = legacy; +} + +void btd_event_remote_class(bdaddr_t *local, bdaddr_t *peer, uint32_t class) +{ + uint32_t old_class = 0; + struct btd_adapter *adapter; + struct btd_device *device; + const gchar *dev_path; + DBusConnection *conn = get_dbus_connection(); + + read_remote_class(local, peer, &old_class); + + if (old_class == class) + return; + + write_remote_class(local, peer, class); + + if (!get_adapter_and_device(local, peer, &adapter, &device, FALSE)) + return; + + if (!device) + return; + + dev_path = device_get_path(device); + + emit_property_changed(conn, dev_path, DEVICE_INTERFACE, "Class", + DBUS_TYPE_UINT32, &class); +} + +void btd_event_remote_name(bdaddr_t *local, bdaddr_t *peer, uint8_t status, + char *name) +{ + struct btd_adapter *adapter; + char srcaddr[18], dstaddr[18]; + int state; + struct btd_device *device; + struct remote_dev_info match, *dev_info; + + if (status == 0) { + char *end; + + /* It's ok to cast end between const and non-const since + * we know it points to inside of name which is non-const */ + if (!g_utf8_validate(name, -1, (const char **) &end)) + *end = '\0'; + + write_device_name(local, peer, name); + } + + if (!get_adapter_and_device(local, peer, &adapter, &device, FALSE)) + return; + + ba2str(local, srcaddr); + ba2str(peer, dstaddr); + + if (status != 0) + goto proceed; + + bacpy(&match.bdaddr, peer); + match.name_status = NAME_ANY; + + dev_info = adapter_search_found_devices(adapter, &match); + if (dev_info) { + g_free(dev_info->name); + dev_info->name = g_strdup(name); + adapter_emit_device_found(adapter, dev_info); + } + + if (device) + device_set_name(device, name); + +proceed: + /* remove from remote name request list */ + adapter_remove_found_device(adapter, peer); + + /* check if there is more devices to request names */ + if (adapter_resolve_names(adapter) == 0) + return; + + state = adapter_get_state(adapter); + state &= ~STATE_RESOLVNAME; + adapter_set_state(adapter, state); +} + +int btd_event_link_key_notify(bdaddr_t *local, bdaddr_t *peer, + uint8_t *key, uint8_t key_type, + uint8_t pin_length) +{ + struct btd_adapter *adapter; + struct btd_device *device; + int ret; + + if (!get_adapter_and_device(local, peer, &adapter, &device, TRUE)) + return -ENODEV; + + DBG("storing link key of type 0x%02x", key_type); + + ret = write_link_key(local, peer, key, key_type, pin_length); + + if (ret == 0 && device_is_temporary(device)) + device_set_temporary(device, FALSE); + + return ret; +} + +void btd_event_conn_complete(bdaddr_t *local, bdaddr_t *peer) +{ + struct btd_adapter *adapter; + struct btd_device *device; + + if (!get_adapter_and_device(local, peer, &adapter, &device, TRUE)) + return; + + update_lastused(local, peer); + + adapter_add_connection(adapter, device); +} + +void btd_event_conn_failed(bdaddr_t *local, bdaddr_t *peer, uint8_t status) +{ + struct btd_adapter *adapter; + struct btd_device *device; + DBusConnection *conn = get_dbus_connection(); + + DBG("status 0x%02x", status); + + if (!get_adapter_and_device(local, peer, &adapter, &device, FALSE)) + return; + + if (!device) + return; + + if (device_is_temporary(device)) + adapter_remove_device(conn, adapter, device, TRUE); +} + +void btd_event_disconn_complete(bdaddr_t *local, bdaddr_t *peer) +{ + struct btd_adapter *adapter; + struct btd_device *device; + + DBG(""); + + if (!get_adapter_and_device(local, peer, &adapter, &device, FALSE)) + return; + + if (!device) + return; + + adapter_remove_connection(adapter, device); +} + +/* Section reserved to device HCI callbacks */ + +void btd_event_le_set_scan_enable_complete(bdaddr_t *local, uint8_t status) +{ + struct btd_adapter *adapter; + int state; + + adapter = manager_find_adapter(local); + if (!adapter) { + error("No matching adapter found"); + return; + } + + if (status) { + error("Can't enable/disable LE scan"); + return; + } + + state = adapter_get_state(adapter); + + /* Enabling or disabling ? */ + if (state & STATE_LE_SCAN) + state &= ~STATE_LE_SCAN; + else + state |= STATE_LE_SCAN; + + adapter_set_state(adapter, state); +} + +void btd_event_returned_link_key(bdaddr_t *local, bdaddr_t *peer) +{ + struct btd_adapter *adapter; + struct btd_device *device; + + if (!get_adapter_and_device(local, peer, &adapter, &device, TRUE)) + return; + + device_set_paired(device, TRUE); +} diff --git a/src/event.h b/src/event.h new file mode 100644 index 0000000..765390a --- /dev/null +++ b/src/event.h @@ -0,0 +1,44 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 + * + */ + +int btd_event_request_pin(bdaddr_t *sba, bdaddr_t *dba); +void btd_event_advertising_report(bdaddr_t *local, le_advertising_info *info); +void btd_event_device_found(bdaddr_t *local, bdaddr_t *peer, uint32_t class, + int8_t rssi, uint8_t *data); +void btd_event_set_legacy_pairing(bdaddr_t *local, bdaddr_t *peer, gboolean legacy); +void btd_event_remote_class(bdaddr_t *local, bdaddr_t *peer, uint32_t class); +void btd_event_remote_name(bdaddr_t *local, bdaddr_t *peer, uint8_t status, char *name); +void btd_event_conn_complete(bdaddr_t *local, bdaddr_t *peer); +void btd_event_conn_failed(bdaddr_t *local, bdaddr_t *peer, uint8_t status); +void btd_event_disconn_complete(bdaddr_t *local, bdaddr_t *peer); +void btd_event_bonding_complete(bdaddr_t *local, bdaddr_t *peer, + uint8_t status); +void btd_event_simple_pairing_complete(bdaddr_t *local, bdaddr_t *peer, uint8_t status); +void btd_event_le_set_scan_enable_complete(bdaddr_t *local, uint8_t status); +void btd_event_returned_link_key(bdaddr_t *local, bdaddr_t *peer); +int btd_event_user_confirm(bdaddr_t *sba, bdaddr_t *dba, uint32_t passkey); +int btd_event_user_passkey(bdaddr_t *sba, bdaddr_t *dba); +int btd_event_user_notify(bdaddr_t *sba, bdaddr_t *dba, uint32_t passkey); +int btd_event_link_key_notify(bdaddr_t *local, bdaddr_t *peer, uint8_t *key, + uint8_t key_type, uint8_t pin_length); diff --git a/src/genbuiltin b/src/genbuiltin new file mode 100755 index 0000000..8b6f047 --- /dev/null +++ b/src/genbuiltin @@ -0,0 +1,17 @@ +#!/bin/sh + +for i in $* +do + echo "extern struct bluetooth_plugin_desc __bluetooth_builtin_$i;" +done + +echo +echo "static struct bluetooth_plugin_desc *__bluetooth_builtin[] = {" + +for i in $* +do + echo " &__bluetooth_builtin_$i," +done + +echo " NULL" +echo "};" diff --git a/src/glib-helper.c b/src/glib-helper.c new file mode 100644 index 0000000..22c14e7 --- /dev/null +++ b/src/glib-helper.c @@ -0,0 +1,587 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "btio.h" +#include "sdpd.h" +#include "glib-helper.h" + +/* Number of seconds to keep a sdp_session_t in the cache */ +#define CACHE_TIMEOUT 2 + +struct cached_sdp_session { + bdaddr_t src; + bdaddr_t dst; + sdp_session_t *session; + guint timer; +}; + +static GSList *cached_sdp_sessions = NULL; + +static gboolean cached_session_expired(gpointer user_data) +{ + struct cached_sdp_session *cached = user_data; + + cached_sdp_sessions = g_slist_remove(cached_sdp_sessions, cached); + + sdp_close(cached->session); + + g_free(cached); + + return FALSE; +} + +static sdp_session_t *get_sdp_session(const bdaddr_t *src, const bdaddr_t *dst) +{ + GSList *l; + + for (l = cached_sdp_sessions; l != NULL; l = l->next) { + struct cached_sdp_session *c = l->data; + sdp_session_t *session; + + if (bacmp(&c->src, src) || bacmp(&c->dst, dst)) + continue; + + g_source_remove(c->timer); + + session = c->session; + + cached_sdp_sessions = g_slist_remove(cached_sdp_sessions, c); + g_free(c); + + return session; + } + + return sdp_connect(src, dst, SDP_NON_BLOCKING); +} + +static void cache_sdp_session(bdaddr_t *src, bdaddr_t *dst, + sdp_session_t *session) +{ + struct cached_sdp_session *cached; + + cached = g_new0(struct cached_sdp_session, 1); + + bacpy(&cached->src, src); + bacpy(&cached->dst, dst); + + cached->session = session; + + cached_sdp_sessions = g_slist_append(cached_sdp_sessions, cached); + + cached->timer = g_timeout_add_seconds(CACHE_TIMEOUT, + cached_session_expired, + cached); +} + +struct search_context { + bdaddr_t src; + bdaddr_t dst; + sdp_session_t *session; + bt_callback_t cb; + bt_destroy_t destroy; + gpointer user_data; + uuid_t uuid; + guint io_id; +}; + +static GSList *context_list = NULL; + +static void search_context_cleanup(struct search_context *ctxt) +{ + context_list = g_slist_remove(context_list, ctxt); + + if (ctxt->destroy) + ctxt->destroy(ctxt->user_data); + + g_free(ctxt); +} + +static void search_completed_cb(uint8_t type, uint16_t status, + uint8_t *rsp, size_t size, void *user_data) +{ + struct search_context *ctxt = user_data; + sdp_list_t *recs = NULL; + int scanned, seqlen = 0, bytesleft = size; + uint8_t dataType; + int err = 0; + + if (status || type != SDP_SVC_SEARCH_ATTR_RSP) { + err = -EPROTO; + goto done; + } + + scanned = sdp_extract_seqtype(rsp, bytesleft, &dataType, &seqlen); + if (!scanned || !seqlen) + goto done; + + rsp += scanned; + bytesleft -= scanned; + do { + sdp_record_t *rec; + int recsize; + + recsize = 0; + rec = sdp_extract_pdu(rsp, bytesleft, &recsize); + if (!rec) + break; + + if (!recsize) { + sdp_record_free(rec); + break; + } + + scanned += recsize; + rsp += recsize; + bytesleft -= recsize; + + recs = sdp_list_append(recs, rec); + } while (scanned < (ssize_t) size && bytesleft > 0); + +done: + cache_sdp_session(&ctxt->src, &ctxt->dst, ctxt->session); + + if (ctxt->cb) + ctxt->cb(recs, err, ctxt->user_data); + + if (recs) + sdp_list_free(recs, (sdp_free_func_t) sdp_record_free); + + search_context_cleanup(ctxt); +} + +static gboolean search_process_cb(GIOChannel *chan, GIOCondition cond, + gpointer user_data) +{ + struct search_context *ctxt = user_data; + int err = 0; + + if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) { + err = EIO; + goto failed; + } + + if (sdp_process(ctxt->session) < 0) + goto failed; + + return TRUE; + +failed: + if (err) { + sdp_close(ctxt->session); + ctxt->session = NULL; + + if (ctxt->cb) + ctxt->cb(NULL, err, ctxt->user_data); + + search_context_cleanup(ctxt); + } + + return FALSE; +} + +static gboolean connect_watch(GIOChannel *chan, GIOCondition cond, + gpointer user_data) +{ + struct search_context *ctxt = user_data; + sdp_list_t *search, *attrids; + uint32_t range = 0x0000ffff; + socklen_t len; + int sk, err = 0; + + sk = g_io_channel_unix_get_fd(chan); + ctxt->io_id = 0; + + len = sizeof(err); + if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &err, &len) < 0) { + err = errno; + goto failed; + } + + if (err != 0) + goto failed; + + if (sdp_set_notify(ctxt->session, search_completed_cb, ctxt) < 0) { + err = EIO; + goto failed; + } + + search = sdp_list_append(NULL, &ctxt->uuid); + attrids = sdp_list_append(NULL, &range); + if (sdp_service_search_attr_async(ctxt->session, + search, SDP_ATTR_REQ_RANGE, attrids) < 0) { + sdp_list_free(attrids, NULL); + sdp_list_free(search, NULL); + err = EIO; + goto failed; + } + + sdp_list_free(attrids, NULL); + sdp_list_free(search, NULL); + + /* Set callback responsible for update the internal SDP transaction */ + ctxt->io_id = g_io_add_watch(chan, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + search_process_cb, ctxt); + return FALSE; + +failed: + sdp_close(ctxt->session); + ctxt->session = NULL; + + if (ctxt->cb) + ctxt->cb(NULL, -err, ctxt->user_data); + + search_context_cleanup(ctxt); + + return FALSE; +} + +static int create_search_context(struct search_context **ctxt, + const bdaddr_t *src, + const bdaddr_t *dst, + uuid_t *uuid) +{ + sdp_session_t *s; + GIOChannel *chan; + + if (!ctxt) + return -EINVAL; + + s = get_sdp_session(src, dst); + if (!s) + return -errno; + + *ctxt = g_try_malloc0(sizeof(struct search_context)); + if (!*ctxt) { + sdp_close(s); + return -ENOMEM; + } + + bacpy(&(*ctxt)->src, src); + bacpy(&(*ctxt)->dst, dst); + (*ctxt)->session = s; + (*ctxt)->uuid = *uuid; + + chan = g_io_channel_unix_new(sdp_get_socket(s)); + (*ctxt)->io_id = g_io_add_watch(chan, + G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + connect_watch, *ctxt); + g_io_channel_unref(chan); + + return 0; +} + +int bt_search_service(const bdaddr_t *src, const bdaddr_t *dst, + uuid_t *uuid, bt_callback_t cb, void *user_data, + bt_destroy_t destroy) +{ + struct search_context *ctxt = NULL; + int err; + + if (!cb) + return -EINVAL; + + err = create_search_context(&ctxt, src, dst, uuid); + if (err < 0) + return err; + + ctxt->cb = cb; + ctxt->destroy = destroy; + ctxt->user_data = user_data; + + context_list = g_slist_append(context_list, ctxt); + + return 0; +} + +static gint find_by_bdaddr(gconstpointer data, gconstpointer user_data) +{ + const struct search_context *ctxt = data, *search = user_data; + + return (bacmp(&ctxt->dst, &search->dst) && + bacmp(&ctxt->src, &search->src)); +} + +int bt_cancel_discovery(const bdaddr_t *src, const bdaddr_t *dst) +{ + struct search_context match, *ctxt; + GSList *l; + + memset(&match, 0, sizeof(match)); + bacpy(&match.src, src); + bacpy(&match.dst, dst); + + /* Ongoing SDP Discovery */ + l = g_slist_find_custom(context_list, &match, find_by_bdaddr); + if (l == NULL) + return -ENOENT; + + ctxt = l->data; + + if (!ctxt->session) + return -ENOTCONN; + + if (ctxt->io_id) + g_source_remove(ctxt->io_id); + + if (ctxt->session) + sdp_close(ctxt->session); + + search_context_cleanup(ctxt); + + return 0; +} + +char *bt_uuid2string(uuid_t *uuid) +{ + gchar *str; + uuid_t uuid128; + unsigned int data0; + unsigned short data1; + unsigned short data2; + unsigned short data3; + unsigned int data4; + unsigned short data5; + + if (!uuid) + return NULL; + + switch (uuid->type) { + case SDP_UUID16: + sdp_uuid16_to_uuid128(&uuid128, uuid); + break; + case SDP_UUID32: + sdp_uuid32_to_uuid128(&uuid128, uuid); + break; + case SDP_UUID128: + memcpy(&uuid128, uuid, sizeof(uuid_t)); + break; + default: + /* Type of UUID unknown */ + return NULL; + } + + memcpy(&data0, &uuid128.value.uuid128.data[0], 4); + memcpy(&data1, &uuid128.value.uuid128.data[4], 2); + memcpy(&data2, &uuid128.value.uuid128.data[6], 2); + memcpy(&data3, &uuid128.value.uuid128.data[8], 2); + memcpy(&data4, &uuid128.value.uuid128.data[10], 4); + memcpy(&data5, &uuid128.value.uuid128.data[14], 2); + + str = g_try_malloc0(MAX_LEN_UUID_STR); + if (!str) + return NULL; + + sprintf(str, "%.8x-%.4x-%.4x-%.4x-%.8x%.4x", + g_ntohl(data0), g_ntohs(data1), + g_ntohs(data2), g_ntohs(data3), + g_ntohl(data4), g_ntohs(data5)); + + return str; +} + +static struct { + const char *name; + uint16_t class; +} bt_services[] = { + { "vcp", VIDEO_CONF_SVCLASS_ID }, + { "pbap", PBAP_SVCLASS_ID }, + { "sap", SAP_SVCLASS_ID }, + { "ftp", OBEX_FILETRANS_SVCLASS_ID }, + { "bpp", BASIC_PRINTING_SVCLASS_ID }, + { "bip", IMAGING_SVCLASS_ID }, + { "synch", IRMC_SYNC_SVCLASS_ID }, + { "dun", DIALUP_NET_SVCLASS_ID }, + { "opp", OBEX_OBJPUSH_SVCLASS_ID }, + { "fax", FAX_SVCLASS_ID }, + { "spp", SERIAL_PORT_SVCLASS_ID }, + { "hsp", HEADSET_SVCLASS_ID }, + { "hfp", HANDSFREE_SVCLASS_ID }, + { } +}; + +static uint16_t name2class(const char *pattern) +{ + int i; + + for (i = 0; bt_services[i].name; i++) { + if (strcasecmp(bt_services[i].name, pattern) == 0) + return bt_services[i].class; + } + + return 0; +} + +static inline gboolean is_uuid128(const char *string) +{ + return (strlen(string) == 36 && + string[8] == '-' && + string[13] == '-' && + string[18] == '-' && + string[23] == '-'); +} + +static int string2uuid16(uuid_t *uuid, const char *string) +{ + int length = strlen(string); + char *endptr = NULL; + uint16_t u16; + + if (length != 4 && length != 6) + return -EINVAL; + + u16 = strtol(string, &endptr, 16); + if (endptr && *endptr == '\0') { + sdp_uuid16_create(uuid, u16); + return 0; + } + + return -EINVAL; +} + +char *bt_name2string(const char *pattern) +{ + uuid_t uuid; + uint16_t uuid16; + int i; + + /* UUID 128 string format */ + if (is_uuid128(pattern)) + return g_strdup(pattern); + + /* Friendly service name format */ + uuid16 = name2class(pattern); + if (uuid16) + goto proceed; + + /* HEX format */ + uuid16 = strtol(pattern, NULL, 16); + for (i = 0; bt_services[i].class; i++) { + if (bt_services[i].class == uuid16) + goto proceed; + } + + return NULL; + +proceed: + sdp_uuid16_create(&uuid, uuid16); + + return bt_uuid2string(&uuid); +} + +int bt_string2uuid(uuid_t *uuid, const char *string) +{ + uint32_t data0, data4; + uint16_t data1, data2, data3, data5; + + if (is_uuid128(string) && + sscanf(string, "%08x-%04hx-%04hx-%04hx-%08x%04hx", + &data0, &data1, &data2, &data3, &data4, &data5) == 6) { + uint8_t val[16]; + + data0 = g_htonl(data0); + data1 = g_htons(data1); + data2 = g_htons(data2); + data3 = g_htons(data3); + data4 = g_htonl(data4); + data5 = g_htons(data5); + + memcpy(&val[0], &data0, 4); + memcpy(&val[4], &data1, 2); + memcpy(&val[6], &data2, 2); + memcpy(&val[8], &data3, 2); + memcpy(&val[10], &data4, 4); + memcpy(&val[14], &data5, 2); + + sdp_uuid128_create(uuid, val); + + return 0; + } else { + uint16_t class = name2class(string); + if (class) { + sdp_uuid16_create(uuid, class); + return 0; + } + + return string2uuid16(uuid, string); + } +} + +gchar *bt_list2string(GSList *list) +{ + GSList *l; + gchar *str, *tmp; + + if (!list) + return NULL; + + str = g_strdup((const gchar *) list->data); + + for (l = list->next; l; l = l->next) { + tmp = g_strconcat(str, " " , (const gchar *) l->data, NULL); + g_free(str); + str = tmp; + } + + return str; +} + +GSList *bt_string2list(const gchar *str) +{ + GSList *l = NULL; + gchar **uuids; + int i = 0; + + if (!str) + return NULL; + + /* FIXME: eglib doesn't support g_strsplit */ + uuids = g_strsplit(str, " ", 0); + if (!uuids) + return NULL; + + while (uuids[i]) { + l = g_slist_append(l, uuids[i]); + i++; + } + + g_free(uuids); + + return l; +} diff --git a/src/glib-helper.h b/src/glib-helper.h new file mode 100644 index 0000000..c83f5e2 --- /dev/null +++ b/src/glib-helper.h @@ -0,0 +1,36 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 + * + */ + +typedef void (*bt_callback_t) (sdp_list_t *recs, int err, gpointer user_data); +typedef void (*bt_destroy_t) (gpointer user_data); + +int bt_search_service(const bdaddr_t *src, const bdaddr_t *dst, + uuid_t *uuid, bt_callback_t cb, void *user_data, + bt_destroy_t destroy); +int bt_cancel_discovery(const bdaddr_t *src, const bdaddr_t *dst); + +gchar *bt_uuid2string(uuid_t *uuid); +char *bt_name2string(const char *string); +int bt_string2uuid(uuid_t *uuid, const char *string); +gchar *bt_list2string(GSList *list); +GSList *bt_string2list(const gchar *str); diff --git a/src/hcid.h b/src/hcid.h new file mode 100644 index 0000000..856723b --- /dev/null +++ b/src/hcid.h @@ -0,0 +1,66 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * 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 + * + */ + +struct main_opts { + char host_name[40]; + unsigned long flags; + char *name; + uint32_t class; + uint16_t pageto; + uint32_t discovto; + uint32_t pairto; + uint16_t link_mode; + uint16_t link_policy; + gboolean remember_powered; + gboolean reverse_sdp; + gboolean name_resolv; + gboolean debug_keys; + gboolean attrib_server; + gboolean le; + + uint8_t scan; + uint8_t mode; + uint8_t discov_interval; + char deviceid[15]; /* FIXME: */ +}; + +enum { + HCID_SET_NAME, + HCID_SET_CLASS, + HCID_SET_PAGETO, + HCID_SET_DISCOVTO, +}; + +extern struct main_opts main_opts; + +void btd_start_exit_timer(void); +void btd_stop_exit_timer(void); + +gboolean plugin_init(GKeyFile *config, const char *enable, + const char *disable); +void plugin_cleanup(void); + +void rfkill_init(void); +void rfkill_exit(void); diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..4ec4633 --- /dev/null +++ b/src/log.c @@ -0,0 +1,136 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 +#endif + +#include +#include +#include + +#include + +#include "log.h" + +void info(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + + vsyslog(LOG_INFO, format, ap); + + va_end(ap); +} + +void error(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + + vsyslog(LOG_ERR, format, ap); + + va_end(ap); +} + +void btd_debug(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + + vsyslog(LOG_DEBUG, format, ap); + + va_end(ap); +} + +extern struct btd_debug_desc __start___debug[]; +extern struct btd_debug_desc __stop___debug[]; + +static gchar **enabled = NULL; + +static gboolean is_enabled(struct btd_debug_desc *desc) +{ + int i; + + if (enabled == NULL) + return 0; + + for (i = 0; enabled[i] != NULL; i++) { + if (desc->name != NULL && g_pattern_match_simple(enabled[i], + desc->name) == TRUE) + return 1; + if (desc->file != NULL && g_pattern_match_simple(enabled[i], + desc->file) == TRUE) + return 1; + } + + return 0; +} + +void __btd_toggle_debug() +{ + struct btd_debug_desc *desc; + + for (desc = __start___debug; desc < __stop___debug; desc++) + desc->flags |= BTD_DEBUG_FLAG_PRINT; +} + +void __btd_log_init(const char *debug, int detach) +{ + int option = LOG_NDELAY | LOG_PID; + struct btd_debug_desc *desc; + const char *name = NULL, *file = NULL; + + if (debug != NULL) + enabled = g_strsplit_set(debug, ":, ", 0); + + for (desc = __start___debug; desc < __stop___debug; desc++) { + if (file != NULL || name != NULL) { + if (g_strcmp0(desc->file, file) == 0) { + if (desc->name == NULL) + desc->name = name; + } else + file = NULL; + } + + if (is_enabled(desc)) + desc->flags |= BTD_DEBUG_FLAG_PRINT; + } + + if (!detach) + option |= LOG_PERROR; + + openlog("bluetoothd", option, LOG_DAEMON); + + syslog(LOG_INFO, "Bluetooth deamon %s", VERSION); +} + +void __btd_log_cleanup(void) +{ + closelog(); + + g_strfreev(enabled); +} diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..cc45cbf --- /dev/null +++ b/src/log.h @@ -0,0 +1,57 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 + * + */ + +void info(const char *format, ...) __attribute__((format(printf, 1, 2))); +void error(const char *format, ...) __attribute__((format(printf, 1, 2))); + +void btd_debug(const char *format, ...) __attribute__((format(printf, 1, 2))); + +void __btd_log_init(const char *debug, int detach); +void __btd_log_cleanup(void); +void __btd_toggle_debug(); + +struct btd_debug_desc { + const char *name; + const char *file; +#define BTD_DEBUG_FLAG_DEFAULT (0) +#define BTD_DEBUG_FLAG_PRINT (1 << 0) + unsigned int flags; +} __attribute__((aligned(8))); + +/** + * DBG: + * @fmt: format string + * @arg...: list of arguments + * + * Simple macro around btd_debug() which also include the function + * name it is called in. + */ +#define DBG(fmt, arg...) do { \ + static struct btd_debug_desc __btd_debug_desc \ + __attribute__((used, section("__debug"), aligned(8))) = { \ + .file = __FILE__, .flags = BTD_DEBUG_FLAG_DEFAULT, \ + }; \ + if (__btd_debug_desc.flags & BTD_DEBUG_FLAG_PRINT) \ + btd_debug("%s:%s() " fmt, __FILE__, __FUNCTION__ , ## arg); \ +} while (0) + diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..c454327 --- /dev/null +++ b/src/main.c @@ -0,0 +1,509 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include + +#include "log.h" + +#include "hcid.h" +#include "sdpd.h" +#include "attrib-server.h" +#include "adapter.h" +#include "event.h" +#include "dbus-common.h" +#include "agent.h" +#include "manager.h" + +#ifdef HAVE_CAPNG +#include +#endif + +#define BLUEZ_NAME "org.bluez" + +#define LAST_ADAPTER_EXIT_TIMEOUT 30 + +#define DEFAULT_DISCOVERABLE_TIMEOUT 180 /* 3 minutes */ + +struct main_opts main_opts; + +static GKeyFile *load_config(const char *file) +{ + GError *err = NULL; + GKeyFile *keyfile; + + keyfile = g_key_file_new(); + + g_key_file_set_list_separator(keyfile, ','); + + if (!g_key_file_load_from_file(keyfile, file, 0, &err)) { + error("Parsing %s failed: %s", file, err->message); + g_error_free(err); + g_key_file_free(keyfile); + return NULL; + } + + return keyfile; +} + +static void parse_config(GKeyFile *config) +{ + GError *err = NULL; + char *str; + int val; + gboolean boolean; + + if (!config) + return; + + DBG("parsing main.conf"); + + val = g_key_file_get_integer(config, "General", + "DiscoverableTimeout", &err); + if (err) { + DBG("%s", err->message); + g_clear_error(&err); + } else { + DBG("discovto=%d", val); + main_opts.discovto = val; + main_opts.flags |= 1 << HCID_SET_DISCOVTO; + } + + val = g_key_file_get_integer(config, "General", + "PairableTimeout", &err); + if (err) { + DBG("%s", err->message); + g_clear_error(&err); + } else { + DBG("pairto=%d", val); + main_opts.pairto = val; + } + + val = g_key_file_get_integer(config, "General", "PageTimeout", &err); + if (err) { + DBG("%s", err->message); + g_clear_error(&err); + } else { + DBG("pageto=%d", val); + main_opts.pageto = val; + main_opts.flags |= 1 << HCID_SET_PAGETO; + } + + str = g_key_file_get_string(config, "General", "Name", &err); + if (err) { + DBG("%s", err->message); + g_clear_error(&err); + } else { + DBG("name=%s", str); + g_free(main_opts.name); + main_opts.name = g_strdup(str); + main_opts.flags |= 1 << HCID_SET_NAME; + g_free(str); + } + + str = g_key_file_get_string(config, "General", "Class", &err); + if (err) { + DBG("%s", err->message); + g_clear_error(&err); + } else { + DBG("class=%s", str); + main_opts.class = strtol(str, NULL, 16); + main_opts.flags |= 1 << HCID_SET_CLASS; + g_free(str); + } + + val = g_key_file_get_integer(config, "General", + "DiscoverSchedulerInterval", &err); + if (err) { + DBG("%s", err->message); + g_clear_error(&err); + } else { + DBG("discov_interval=%d", val); + main_opts.discov_interval = val; + } + + boolean = g_key_file_get_boolean(config, "General", + "InitiallyPowered", &err); + if (err) { + DBG("%s", err->message); + g_clear_error(&err); + } else if (boolean == FALSE) + main_opts.mode = MODE_OFF; + + boolean = g_key_file_get_boolean(config, "General", + "RememberPowered", &err); + if (err) { + DBG("%s", err->message); + g_clear_error(&err); + } else + main_opts.remember_powered = boolean; + + str = g_key_file_get_string(config, "General", "DeviceID", &err); + if (err) { + DBG("%s", err->message); + g_clear_error(&err); + } else { + DBG("deviceid=%s", str); + strncpy(main_opts.deviceid, str, + sizeof(main_opts.deviceid) - 1); + g_free(str); + } + + boolean = g_key_file_get_boolean(config, "General", + "ReverseServiceDiscovery", &err); + if (err) { + DBG("%s", err->message); + g_clear_error(&err); + } else + main_opts.reverse_sdp = boolean; + + boolean = g_key_file_get_boolean(config, "General", + "NameResolving", &err); + if (err) + g_clear_error(&err); + else + main_opts.name_resolv = boolean; + + boolean = g_key_file_get_boolean(config, "General", + "DebugKeys", &err); + if (err) + g_clear_error(&err); + else + main_opts.debug_keys = boolean; + + boolean = g_key_file_get_boolean(config, "General", + "AttributeServer", &err); + if (err) + g_clear_error(&err); + else + main_opts.attrib_server = boolean; + + boolean = g_key_file_get_boolean(config, "General", + "EnableLE", &err); + if (err) + g_clear_error(&err); + else + main_opts.le = boolean; + + main_opts.link_mode = HCI_LM_ACCEPT; + + main_opts.link_policy = HCI_LP_RSWITCH | HCI_LP_SNIFF | + HCI_LP_HOLD | HCI_LP_PARK; +} + +static void init_defaults(void) +{ + /* Default HCId settings */ + memset(&main_opts, 0, sizeof(main_opts)); + main_opts.scan = SCAN_PAGE; + main_opts.mode = MODE_CONNECTABLE; + main_opts.name = g_strdup("BlueZ"); + main_opts.discovto = DEFAULT_DISCOVERABLE_TIMEOUT; + main_opts.remember_powered = TRUE; + main_opts.reverse_sdp = TRUE; + main_opts.name_resolv = TRUE; + + if (gethostname(main_opts.host_name, sizeof(main_opts.host_name) - 1) < 0) + strcpy(main_opts.host_name, "noname"); +} + +static GMainLoop *event_loop; + +static void sig_term(int sig) +{ + g_main_loop_quit(event_loop); +} + +static void sig_debug(int sig) +{ + __btd_toggle_debug(); +} + +static gchar *option_debug = NULL; +static gchar *option_plugin = NULL; +static gchar *option_noplugin = NULL; +static gboolean option_detach = TRUE; +static gboolean option_version = FALSE; +static gboolean option_udev = FALSE; + +static guint last_adapter_timeout = 0; + +static gboolean exit_timeout(gpointer data) +{ + g_main_loop_quit(event_loop); + last_adapter_timeout = 0; + return FALSE; +} + +void btd_start_exit_timer(void) +{ + if (option_udev == FALSE) + return; + + if (last_adapter_timeout > 0) + g_source_remove(last_adapter_timeout); + + last_adapter_timeout = g_timeout_add_seconds(LAST_ADAPTER_EXIT_TIMEOUT, + exit_timeout, NULL); +} + +void btd_stop_exit_timer(void) +{ + if (last_adapter_timeout == 0) + return; + + g_source_remove(last_adapter_timeout); + last_adapter_timeout = 0; +} + +static void disconnect_dbus(void) +{ + DBusConnection *conn = get_dbus_connection(); + + if (!conn || !dbus_connection_get_is_connected(conn)) + return; + + manager_cleanup(conn, "/"); + + set_dbus_connection(NULL); + + dbus_connection_unref(conn); +} + +static int connect_dbus(void) +{ + DBusConnection *conn; + DBusError err; + + dbus_error_init(&err); + + conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, BLUEZ_NAME, &err); + if (!conn) { + if (dbus_error_is_set(&err)) { + dbus_error_free(&err); + return -EIO; + } + return -EALREADY; + } + + if (!manager_init(conn, "/")) + return -EIO; + + set_dbus_connection(conn); + + return 0; +} + +static gboolean parse_debug(const char *key, const char *value, + gpointer user_data, GError **error) +{ + if (value) + option_debug = g_strdup(value); + else + option_debug = g_strdup("*"); + + return TRUE; +} + +static GOptionEntry options[] = { + { "debug", 'd', G_OPTION_FLAG_OPTIONAL_ARG, + G_OPTION_ARG_CALLBACK, parse_debug, + "Specify debug options to enable", "DEBUG" }, + { "plugin", 'p', 0, G_OPTION_ARG_STRING, &option_plugin, + "Specify plugins to load", "NAME,..," }, + { "noplugin", 'P', 0, G_OPTION_ARG_STRING, &option_noplugin, + "Specify plugins not to load", "NAME,..." }, + { "nodetach", 'n', G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, &option_detach, + "Don't run as daemon in background" }, + { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version, + "Show version information and exit" }, + { "udev", 'u', 0, G_OPTION_ARG_NONE, &option_udev, + "Run from udev mode of operation" }, + { NULL }, +}; + +int main(int argc, char *argv[]) +{ + GOptionContext *context; + GError *err = NULL; + struct sigaction sa; + uint16_t mtu = 0; + GKeyFile *config; + + init_defaults(); + +#ifdef HAVE_CAPNG + /* Drop capabilities */ + capng_clear(CAPNG_SELECT_BOTH); + capng_updatev(CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED, + CAP_NET_BIND_SERVICE, CAP_NET_ADMIN, + CAP_NET_RAW, CAP_IPC_LOCK, -1); + capng_apply(CAPNG_SELECT_BOTH); +#endif + + context = g_option_context_new(NULL); + g_option_context_add_main_entries(context, options, NULL); + + if (g_option_context_parse(context, &argc, &argv, &err) == FALSE) { + if (err != NULL) { + g_printerr("%s\n", err->message); + g_error_free(err); + } else + g_printerr("An unknown error occurred\n"); + exit(1); + } + + g_option_context_free(context); + + if (option_version == TRUE) { + printf("%s\n", VERSION); + exit(0); + } + + if (option_udev == TRUE) { + int err; + + option_detach = TRUE; + err = connect_dbus(); + if (err < 0) { + if (err == -EALREADY) + exit(0); + exit(1); + } + } + + if (option_detach == TRUE && option_udev == FALSE) { + if (daemon(0, 0)) { + perror("Can't start daemon"); + exit(1); + } + } + + umask(0077); + + __btd_log_init(option_debug, option_detach); + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + sa.sa_handler = sig_debug; + sigaction(SIGUSR2, &sa, NULL); + + sa.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &sa, NULL); + + config = load_config(CONFIGDIR "/main.conf"); + + parse_config(config); + + agent_init(); + + if (option_udev == FALSE) { + if (connect_dbus() < 0) { + error("Unable to get on D-Bus"); + exit(1); + } + } else { + if (daemon(0, 0)) { + perror("Can't start daemon"); + exit(1); + } + } + + start_sdp_server(mtu, main_opts.deviceid, SDP_SERVER_COMPAT); + + if (main_opts.attrib_server) { + if (attrib_server_init() < 0) + error("Can't initialize attribute server"); + } + + /* Loading plugins has to be done after D-Bus has been setup since + * the plugins might wanna expose some paths on the bus. However the + * best order of how to init various subsystems of the Bluetooth + * daemon needs to be re-worked. */ + plugin_init(config, option_plugin, option_noplugin); + + event_loop = g_main_loop_new(NULL, FALSE); + + if (adapter_ops_setup() < 0) { + error("adapter_ops_setup failed"); + exit(1); + } + + rfkill_init(); + + DBG("Entering main loop"); + + g_main_loop_run(event_loop); + + disconnect_dbus(); + + rfkill_exit(); + + plugin_cleanup(); + + if (main_opts.attrib_server) + attrib_server_exit(); + + stop_sdp_server(); + + agent_exit(); + + g_main_loop_unref(event_loop); + + if (config) + g_key_file_free(config); + + info("Exit"); + + __btd_log_cleanup(); + + return 0; +} diff --git a/src/main.conf b/src/main.conf new file mode 100644 index 0000000..c03f135 --- /dev/null +++ b/src/main.conf @@ -0,0 +1,66 @@ +[General] + +# List of plugins that should not be loaded on bluetoothd startup +#DisablePlugins = network,input + +# Default adaper name +# %h - substituted for hostname +# %d - substituted for adapter id +Name = %h-%d + +# Default device class. Only the major and minor device class bits are +# considered. +Class = 0x000100 + +# How long to stay in discoverable mode before going back to non-discoverable +# The value is in seconds. Default is 180, i.e. 3 minutes. +# 0 = disable timer, i.e. stay discoverable forever +DiscoverableTimeout = 0 + +# How long to stay in pairable mode before going back to non-discoverable +# The value is in seconds. Default is 0. +# 0 = disable timer, i.e. stay pairable forever +PairableTimeout = 0 + +# Use some other page timeout than the controller default one +# which is 16384 (10 seconds). +PageTimeout = 8192 + +# Discover scheduler interval used in Adapter.DiscoverDevices +# The value is in seconds. Defaults is 0 to use controller scheduler. +DiscoverSchedulerInterval = 0 + +# What value should be assumed for the adapter Powered property when +# SetProperty(Powered, ...) hasn't been called yet. Defaults to true +InitiallyPowered = true + +# Remember the previously stored Powered state when initializing adapters +RememberPowered = true + +# Use vendor, product and version information for DID profile support. +# The values are separated by ":" and VID, PID and version. +#DeviceID = 1234:5678:abcd + +# Do reverse service discovery for previously unknown devices that connect to +# us. This option is really only needed for qualification since the BITE tester +# doesn't like us doing reverse SDP for some test cases (though there could in +# theory be other useful purposes for this too). Defaults to true. +ReverseServiceDiscovery = true + +# Enable name resolving after inquiry. Set it to 'false' if you don't need +# remote devices name and want shorter discovery cycle. Defaults to 'true'. +NameResolving = true + +# Enable runtime persistency of debug link keys. Default is false which +# makes debug link keys valid only for the duration of the connection +# that they were created for. +DebugKeys = false + +# Enable Low Energy support if the dongle supports. Default is false. +# Enable/Disable interleave discovery and attribute server over LE. +EnableLE = false + +# Enable the GATT Attribute Server. Default is false, because it is only +# useful for testing. Attribute server is not enabled over LE if EnableLE +# is false. +AttributeServer = false diff --git a/src/manager.c b/src/manager.c new file mode 100644 index 0000000..e805e0c --- /dev/null +++ b/src/manager.c @@ -0,0 +1,431 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include + +#include "hcid.h" +#include "dbus-common.h" +#include "log.h" +#include "adapter.h" +#include "error.h" +#include "manager.h" + +static char base_path[50] = "/org/bluez"; + +static DBusConnection *connection = NULL; +static int default_adapter_id = -1; +static GSList *adapters = NULL; + +const char *manager_get_base_path(void) +{ + return base_path; +} + +static DBusMessage *default_adapter(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + struct btd_adapter *adapter; + const gchar *path; + + adapter = manager_find_adapter_by_id(default_adapter_id); + if (!adapter) + return btd_error_no_such_adapter(msg); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + path = adapter_get_path(adapter); + + dbus_message_append_args(reply, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *find_adapter(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + struct btd_adapter *adapter; + const char *pattern; + int dev_id; + const gchar *path; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern, + DBUS_TYPE_INVALID)) + return NULL; + + /* hci_devid() would make sense to use here, except it is + * restricted to devices which are up */ + if (!strcmp(pattern, "any") || !strcmp(pattern, "00:00:00:00:00:00")) { + path = adapter_any_get_path(); + if (path != NULL) + goto done; + return btd_error_no_such_adapter(msg); + } else if (!strncmp(pattern, "hci", 3) && strlen(pattern) >= 4) { + dev_id = atoi(pattern + 3); + adapter = manager_find_adapter_by_id(dev_id); + } else { + bdaddr_t bdaddr; + str2ba(pattern, &bdaddr); + adapter = manager_find_adapter(&bdaddr); + } + + if (!adapter) + return btd_error_no_such_adapter(msg); + + path = adapter_get_path(adapter); + +done: + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *list_adapters(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessageIter iter; + DBusMessageIter array_iter; + DBusMessage *reply; + GSList *l; + + 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_TYPE_OBJECT_PATH_AS_STRING, &array_iter); + + for (l = adapters; l; l = l->next) { + struct btd_adapter *adapter = l->data; + const gchar *path = adapter_get_path(adapter); + + dbus_message_iter_append_basic(&array_iter, + DBUS_TYPE_OBJECT_PATH, &path); + } + + dbus_message_iter_close_container(&iter, &array_iter); + + return reply; +} + +static DBusMessage *get_properties(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + GSList *list; + char **array; + 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); + + array = g_new0(char *, g_slist_length(adapters) + 1); + for (i = 0, list = adapters; list; list = list->next) { + struct btd_adapter *adapter = list->data; + + array[i] = (char *) adapter_get_path(adapter); + i++; + } + dict_append_array(&dict, "Adapters", DBUS_TYPE_OBJECT_PATH, &array, i); + g_free(array); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static GDBusMethodTable manager_methods[] = { + { "GetProperties", "", "a{sv}",get_properties }, + { "DefaultAdapter", "", "o", default_adapter }, + { "FindAdapter", "s", "o", find_adapter }, + { "ListAdapters", "", "ao", list_adapters, + G_DBUS_METHOD_FLAG_DEPRECATED}, + { } +}; + +static GDBusSignalTable manager_signals[] = { + { "PropertyChanged", "sv" }, + { "AdapterAdded", "o" }, + { "AdapterRemoved", "o" }, + { "DefaultAdapterChanged", "o" }, + { } +}; + +dbus_bool_t manager_init(DBusConnection *conn, const char *path) +{ + connection = conn; + + snprintf(base_path, sizeof(base_path), "/org/bluez/%d", getpid()); + + return g_dbus_register_interface(conn, "/", MANAGER_INTERFACE, + manager_methods, manager_signals, + NULL, NULL, NULL); +} + +static void manager_update_adapters(void) +{ + GSList *list; + char **array; + int i; + + array = g_new0(char *, g_slist_length(adapters) + 1); + for (i = 0, list = adapters; list; list = list->next) { + struct btd_adapter *adapter = list->data; + + array[i] = (char *) adapter_get_path(adapter); + i++; + } + + emit_array_property_changed(connection, "/", + MANAGER_INTERFACE, "Adapters", + DBUS_TYPE_OBJECT_PATH, &array, i); + + g_free(array); +} + +static void manager_set_default_adapter(int id) +{ + struct btd_adapter *adapter; + const gchar *path; + + default_adapter_id = id; + + adapter = manager_find_adapter_by_id(id); + if (!adapter) + return; + + path = adapter_get_path(adapter); + + g_dbus_emit_signal(connection, "/", + MANAGER_INTERFACE, + "DefaultAdapterChanged", + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); +} + +static void manager_remove_adapter(struct btd_adapter *adapter) +{ + uint16_t dev_id = adapter_get_dev_id(adapter); + const gchar *path = adapter_get_path(adapter); + + adapters = g_slist_remove(adapters, adapter); + + manager_update_adapters(); + + if (default_adapter_id == dev_id || default_adapter_id < 0) { + int new_default = hci_get_route(NULL); + + manager_set_default_adapter(new_default); + } + + g_dbus_emit_signal(connection, "/", + MANAGER_INTERFACE, "AdapterRemoved", + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + + adapter_remove(adapter); + + if (adapters == NULL) + btd_start_exit_timer(); +} + +void manager_cleanup(DBusConnection *conn, const char *path) +{ + g_slist_foreach(adapters, (GFunc) manager_remove_adapter, NULL); + g_slist_free(adapters); + + g_dbus_unregister_interface(conn, "/", MANAGER_INTERFACE); +} + +static gint adapter_id_cmp(gconstpointer a, gconstpointer b) +{ + struct btd_adapter *adapter = (struct btd_adapter *) a; + uint16_t id = GPOINTER_TO_UINT(b); + uint16_t dev_id = adapter_get_dev_id(adapter); + + return dev_id == id ? 0 : -1; +} + +static gint adapter_cmp(gconstpointer a, gconstpointer b) +{ + struct btd_adapter *adapter = (struct btd_adapter *) a; + const bdaddr_t *bdaddr = b; + bdaddr_t src; + + adapter_get_address(adapter, &src); + + return bacmp(&src, bdaddr); +} + +struct btd_adapter *manager_find_adapter(const bdaddr_t *sba) +{ + GSList *match; + + match = g_slist_find_custom(adapters, sba, adapter_cmp); + if (!match) + return NULL; + + return match->data; +} + +struct btd_adapter *manager_find_adapter_by_id(int id) +{ + GSList *match; + + match = g_slist_find_custom(adapters, GINT_TO_POINTER(id), + adapter_id_cmp); + if (!match) + return NULL; + + return match->data; +} + +void manager_foreach_adapter(adapter_cb func, gpointer user_data) +{ + g_slist_foreach(adapters, (GFunc) func, user_data); +} + +GSList *manager_get_adapters(void) +{ + return adapters; +} + +void manager_add_adapter(const char *path) +{ + g_dbus_emit_signal(connection, "/", + MANAGER_INTERFACE, "AdapterAdded", + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + + manager_update_adapters(); + + btd_stop_exit_timer(); +} + +struct btd_adapter *btd_manager_register_adapter(int id) +{ + struct btd_adapter *adapter; + const char *path; + + adapter = manager_find_adapter_by_id(id); + if (adapter) { + error("Unable to register adapter: hci%d already exist", id); + return NULL; + } + + adapter = adapter_create(connection, id); + if (!adapter) + return NULL; + + adapters = g_slist_append(adapters, adapter); + + if (!adapter_init(adapter)) { + btd_adapter_unref(adapter); + return NULL; + } + + path = adapter_get_path(adapter); + g_dbus_emit_signal(connection, "/", + MANAGER_INTERFACE, "AdapterAdded", + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + + manager_update_adapters(); + + btd_stop_exit_timer(); + + if (default_adapter_id < 0) + manager_set_default_adapter(id); + + DBG("Adapter %s registered", path); + + return btd_adapter_ref(adapter); +} + +int btd_manager_unregister_adapter(int id) +{ + struct btd_adapter *adapter; + const gchar *path; + + adapter = manager_find_adapter_by_id(id); + if (!adapter) + return -1; + + path = adapter_get_path(adapter); + + info("Unregister path: %s", path); + + manager_remove_adapter(adapter); + + return 0; +} + +void btd_manager_set_did(uint16_t vendor, uint16_t product, uint16_t version) +{ + GSList *l; + + for (l = adapters; l != NULL; l = g_slist_next(l)) { + struct btd_adapter *adapter = l->data; + + btd_adapter_set_did(adapter, vendor, product, version); + } +} diff --git a/src/manager.h b/src/manager.h new file mode 100644 index 0000000..05c38b3 --- /dev/null +++ b/src/manager.h @@ -0,0 +1,43 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 + * + */ + +#include +#include + +#define MANAGER_INTERFACE "org.bluez.Manager" + +typedef void (*adapter_cb) (struct btd_adapter *adapter, gpointer user_data); + +dbus_bool_t manager_init(DBusConnection *conn, const char *path); +void manager_cleanup(DBusConnection *conn, const char *path); + +const char *manager_get_base_path(void); +struct btd_adapter *manager_find_adapter(const bdaddr_t *sba); +struct btd_adapter *manager_find_adapter_by_id(int id); +void manager_foreach_adapter(adapter_cb func, gpointer user_data); +GSList *manager_get_adapters(void); +struct btd_adapter *btd_manager_register_adapter(int id); +int btd_manager_unregister_adapter(int id); +void manager_add_adapter(const char *path); +void btd_manager_set_did(uint16_t vendor, uint16_t product, uint16_t version); diff --git a/src/oob.c b/src/oob.c new file mode 100644 index 0000000..75798fb --- /dev/null +++ b/src/oob.c @@ -0,0 +1,41 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 ST-Ericsson SA + * + * Author: Szymon Janc for ST-Ericsson + * + * + * 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 + * + */ + +#include "adapter.h" +#include "oob.h" + +static oob_read_cb_t local_oob_read_cb = NULL; + +void oob_register_cb(oob_read_cb_t cb) +{ + local_oob_read_cb = cb; +} + +void oob_read_local_data_complete(struct btd_adapter *adapter, uint8_t *hash, + uint8_t *randomizer) +{ + if (local_oob_read_cb) + local_oob_read_cb(adapter, hash, randomizer); +} diff --git a/src/oob.h b/src/oob.h new file mode 100644 index 0000000..5805082 --- /dev/null +++ b/src/oob.h @@ -0,0 +1,32 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 ST-Ericsson SA + * + * Author: Szymon Janc for ST-Ericsson + * + * + * 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 + * + */ + +typedef void (*oob_read_cb_t) (struct btd_adapter *adapter, uint8_t *hash, + uint8_t *randomizer); + +void oob_register_cb(oob_read_cb_t cb); + +void oob_read_local_data_complete(struct btd_adapter *adapter, uint8_t *hash, + uint8_t *randomizer); diff --git a/src/oui.c b/src/oui.c new file mode 100644 index 0000000..80bb3d3 --- /dev/null +++ b/src/oui.c @@ -0,0 +1,101 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "oui.h" + +/* http://standards.ieee.org/regauth/oui/oui.txt */ + +char *ouitocomp(const char *oui) +{ + struct stat st; + char *str, *map, *off, *end; + int fd; + + fd = open(OUIFILE, O_RDONLY); + if (fd < 0) + return NULL; + + if (fstat(fd, &st) < 0) { + close(fd); + return NULL; + } + + str = malloc(128); + if (!str) { + close(fd); + return NULL; + } + + memset(str, 0, 128); + + map = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (!map || map == MAP_FAILED) { + free(str); + close(fd); + return NULL; + } + + off = strstr(map, oui); + if (off) { + off += 18; + end = strpbrk(off, "\r\n"); + strncpy(str, off, end - off); + } else { + free(str); + str = NULL; + } + + munmap(map, st.st_size); + + close(fd); + + return str; +} + +int oui2comp(const char *oui, char *comp, size_t size) +{ + char *tmp; + + tmp = ouitocomp(oui); + if (!tmp) + return -1; + + snprintf(comp, size, "%s", tmp); + + free(tmp); + + return 0; +} diff --git a/src/oui.h b/src/oui.h new file mode 100644 index 0000000..ad66e56 --- /dev/null +++ b/src/oui.h @@ -0,0 +1,25 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 + * + */ + +char *ouitocomp(const char *oui); +int oui2comp(const char *oui, char *comp, size_t size); diff --git a/src/plugin.c b/src/plugin.c new file mode 100644 index 0000000..3506553 --- /dev/null +++ b/src/plugin.c @@ -0,0 +1,249 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 +#endif + +#include +#include +#include +#include + +#include + +#include + +#include "plugin.h" +#include "log.h" +#include "hcid.h" +#include "btio.h" + +static GSList *plugins = NULL; + +struct bluetooth_plugin { + void *handle; + gboolean active; + struct bluetooth_plugin_desc *desc; +}; + +static gint compare_priority(gconstpointer a, gconstpointer b) +{ + const struct bluetooth_plugin *plugin1 = a; + const struct bluetooth_plugin *plugin2 = b; + + return plugin2->desc->priority - plugin1->desc->priority; +} + +static gboolean add_plugin(void *handle, struct bluetooth_plugin_desc *desc) +{ + struct bluetooth_plugin *plugin; + + if (desc->init == NULL) + return FALSE; + + if (g_str_equal(desc->version, VERSION) == FALSE) { + error("Version mismatch for %s", desc->name); + return FALSE; + } + + DBG("Loading %s plugin", desc->name); + + plugin = g_try_new0(struct bluetooth_plugin, 1); + if (plugin == NULL) + return FALSE; + + plugin->handle = handle; + plugin->active = FALSE; + plugin->desc = desc; + + plugins = g_slist_insert_sorted(plugins, plugin, compare_priority); + + return TRUE; +} + +static gboolean enable_plugin(const char *name, char **conf_disable, + char **cli_enable, char **cli_disable) +{ + if (conf_disable) { + for (; *conf_disable; conf_disable++) + if (g_pattern_match_simple(*conf_disable, name)) + break; + if (*conf_disable) { + info("Excluding (conf) %s", name); + return FALSE; + } + } + + if (cli_disable) { + for (; *cli_disable; cli_disable++) + if (g_pattern_match_simple(*cli_disable, name)) + break; + if (*cli_disable) { + info("Excluding (cli) %s", name); + return FALSE; + } + } + + if (cli_enable) { + for (; *cli_enable; cli_enable++) + if (g_pattern_match_simple(*cli_enable, name)) + break; + if (!*cli_enable) { + info("Ignoring (cli) %s", name); + return FALSE; + } + } + + return TRUE; +} + +#include "builtin.h" + +gboolean plugin_init(GKeyFile *config, const char *enable, const char *disable) +{ + GSList *list; + GDir *dir; + const gchar *file; + char **conf_disabled, **cli_disabled, **cli_enabled; + unsigned int i; + + /* Make a call to BtIO API so its symbols got resolved before the + * plugins are loaded. */ + bt_io_error_quark(); + + if (config) + conf_disabled = g_key_file_get_string_list(config, "General", + "DisablePlugins", + NULL, NULL); + else + conf_disabled = NULL; + + if (enable) + cli_enabled = g_strsplit_set(enable, ", ", -1); + else + cli_enabled = NULL; + + if (disable) + cli_disabled = g_strsplit_set(disable, ", ", -1); + else + cli_disabled = NULL; + + DBG("Loading builtin plugins"); + + for (i = 0; __bluetooth_builtin[i]; i++) { + if (!enable_plugin(__bluetooth_builtin[i]->name, conf_disabled, + cli_enabled, cli_disabled)) + continue; + + add_plugin(NULL, __bluetooth_builtin[i]); + } + + if (strlen(PLUGINDIR) == 0) + goto start; + + DBG("Loading plugins %s", PLUGINDIR); + + dir = g_dir_open(PLUGINDIR, 0, NULL); + if (!dir) + goto start; + + while ((file = g_dir_read_name(dir)) != NULL) { + struct bluetooth_plugin_desc *desc; + void *handle; + gchar *filename; + + if (g_str_has_prefix(file, "lib") == TRUE || + g_str_has_suffix(file, ".so") == FALSE) + continue; + + filename = g_build_filename(PLUGINDIR, file, NULL); + + handle = dlopen(filename, RTLD_NOW); + if (handle == NULL) { + error("Can't load plugin %s: %s", filename, + dlerror()); + g_free(filename); + continue; + } + + g_free(filename); + + desc = dlsym(handle, "bluetooth_plugin_desc"); + if (desc == NULL) { + error("Can't load plugin description: %s", dlerror()); + dlclose(handle); + continue; + } + + if (!enable_plugin(desc->name, conf_disabled, + cli_enabled, cli_disabled)) { + dlclose(handle); + continue; + } + + if (add_plugin(handle, desc) == FALSE) + dlclose(handle); + } + + g_dir_close(dir); + +start: + for (list = plugins; list; list = list->next) { + struct bluetooth_plugin *plugin = list->data; + + if (plugin->desc->init() < 0) { + error("Failed to init %s plugin", plugin->desc->name); + continue; + } + + plugin->active = TRUE; + } + + g_strfreev(conf_disabled); + g_strfreev(cli_enabled); + g_strfreev(cli_disabled); + + return TRUE; +} + +void plugin_cleanup(void) +{ + GSList *list; + + DBG("Cleanup plugins"); + + for (list = plugins; list; list = list->next) { + struct bluetooth_plugin *plugin = list->data; + + if (plugin->active == TRUE && plugin->desc->exit) + plugin->desc->exit(); + + if (plugin->handle != NULL) + dlclose(plugin->handle); + + g_free(plugin); + } + + g_slist_free(plugins); +} diff --git a/src/plugin.h b/src/plugin.h new file mode 100644 index 0000000..30bd415 --- /dev/null +++ b/src/plugin.h @@ -0,0 +1,47 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 + * + */ +#define BLUETOOTH_PLUGIN_PRIORITY_LOW -100 +#define BLUETOOTH_PLUGIN_PRIORITY_DEFAULT 0 +#define BLUETOOTH_PLUGIN_PRIORITY_HIGH 100 + +struct bluetooth_plugin_desc { + const char *name; + const char *version; + int priority; + int (*init) (void); + void (*exit) (void); +}; + +#ifdef BLUETOOTH_PLUGIN_BUILTIN +#define BLUETOOTH_PLUGIN_DEFINE(name, version, priority, init, exit) \ + struct bluetooth_plugin_desc __bluetooth_builtin_ ## name = { \ + #name, version, priority, init, exit \ + }; +#else +#define BLUETOOTH_PLUGIN_DEFINE(name, version, priority, init, exit) \ + extern struct bluetooth_plugin_desc bluetooth_plugin_desc \ + __attribute__ ((visibility("default"))); \ + struct bluetooth_plugin_desc bluetooth_plugin_desc = { \ + #name, version, priority, init, exit \ + }; +#endif diff --git a/src/ppoll.h b/src/ppoll.h new file mode 100644 index 0000000..7d09d44 --- /dev/null +++ b/src/ppoll.h @@ -0,0 +1,16 @@ +#ifdef ppoll +#undef ppoll +#endif + +#define ppoll compat_ppoll + +static inline int compat_ppoll(struct pollfd *fds, nfds_t nfds, + const struct timespec *timeout, const sigset_t *sigmask) +{ + if (timeout == NULL) + return poll(fds, nfds, -1); + else if (timeout->tv_sec == 0) + return poll(fds, nfds, 500); + else + return poll(fds, nfds, timeout->tv_sec * 1000); +} diff --git a/src/rfkill.c b/src/rfkill.c new file mode 100644 index 0000000..b40c6e7 --- /dev/null +++ b/src/rfkill.c @@ -0,0 +1,173 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "log.h" +#include "adapter.h" +#include "manager.h" +#include "hcid.h" + +enum rfkill_type { + RFKILL_TYPE_ALL = 0, + RFKILL_TYPE_WLAN, + RFKILL_TYPE_BLUETOOTH, + RFKILL_TYPE_UWB, + RFKILL_TYPE_WIMAX, + RFKILL_TYPE_WWAN, +}; + +enum rfkill_operation { + RFKILL_OP_ADD = 0, + RFKILL_OP_DEL, + RFKILL_OP_CHANGE, + RFKILL_OP_CHANGE_ALL, +}; + +struct rfkill_event { + uint32_t idx; + uint8_t type; + uint8_t op; + uint8_t soft; + uint8_t hard; +}; + +static gboolean rfkill_event(GIOChannel *chan, + GIOCondition cond, gpointer data) +{ + unsigned char buf[32]; + struct rfkill_event *event = (void *) buf; + struct btd_adapter *adapter; + char sysname[PATH_MAX]; + ssize_t len; + int fd, id; + + if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR)) + return FALSE; + + fd = g_io_channel_unix_get_fd(chan); + + memset(buf, 0, sizeof(buf)); + + len = read(fd, buf, sizeof(buf)); + if (len < 0) { + if (errno == EAGAIN) + return TRUE; + return FALSE; + } + + if (len != sizeof(struct rfkill_event)) + return TRUE; + + DBG("RFKILL event idx %u type %u op %u soft %u hard %u", + event->idx, event->type, event->op, + event->soft, event->hard); + + if (event->soft || event->hard) + return TRUE; + + if (event->op != RFKILL_OP_CHANGE) + return TRUE; + + if (event->type != RFKILL_TYPE_BLUETOOTH && + event->type != RFKILL_TYPE_ALL) + return TRUE; + + snprintf(sysname, sizeof(sysname) - 1, + "/sys/class/rfkill/rfkill%u/name", event->idx); + + fd = open(sysname, O_RDONLY); + if (fd < 0) + return TRUE; + + memset(sysname, 0, sizeof(sysname)); + + if (read(fd, sysname, sizeof(sysname)) < 4) { + close(fd); + return TRUE; + } + + close(fd); + + if (g_str_has_prefix(sysname, "hci") == FALSE) + return TRUE; + + id = atoi(sysname + 3); + if (id < 0) + return TRUE; + + adapter = manager_find_adapter_by_id(id); + if (!adapter) + return TRUE; + + DBG("RFKILL unblock for hci%d", id); + + btd_adapter_restore_powered(adapter); + + return TRUE; +} + +static GIOChannel *channel = NULL; + +void rfkill_init(void) +{ + int fd; + + if (!main_opts.remember_powered) + return; + + fd = open("/dev/rfkill", O_RDWR); + if (fd < 0) { + error("Failed to open RFKILL control device"); + return; + } + + channel = g_io_channel_unix_new(fd); + g_io_channel_set_close_on_unref(channel, TRUE); + + g_io_add_watch(channel, G_IO_IN | G_IO_NVAL | G_IO_HUP | G_IO_ERR, + rfkill_event, NULL); +} + +void rfkill_exit(void) +{ + if (!channel) + return; + + g_io_channel_shutdown(channel, TRUE, NULL); + g_io_channel_unref(channel); + + channel = NULL; +} diff --git a/src/sdp-xml.c b/src/sdp-xml.c new file mode 100644 index 0000000..3aa9df0 --- /dev/null +++ b/src/sdp-xml.c @@ -0,0 +1,789 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2005-2010 Marcel Holtmann + * + * + * 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "sdp-xml.h" + +#define STRBUFSIZE 1024 +#define MAXINDENT 64 + +static void convert_raw_data_to_xml(sdp_data_t *value, int indent_level, + void *data, void (*appender)(void *, const char *)) +{ + int i, hex; + char buf[STRBUFSIZE]; + char indent[MAXINDENT]; + char next_indent[MAXINDENT]; + + if (!value) + return; + + if (indent_level >= MAXINDENT) + indent_level = MAXINDENT - 2; + + for (i = 0; i < indent_level; i++) { + indent[i] = '\t'; + next_indent[i] = '\t'; + } + + indent[i] = '\0'; + next_indent[i] = '\t'; + next_indent[i + 1] = '\0'; + + buf[STRBUFSIZE - 1] = '\0'; + + switch (value->dtd) { + case SDP_DATA_NIL: + appender(data, indent); + appender(data, "\n"); + break; + + case SDP_BOOL: + appender(data, indent); + appender(data, "val.uint8 ? "true" : "false"); + appender(data, "\" />\n"); + break; + + case SDP_UINT8: + appender(data, indent); + appender(data, "val.uint8); + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_UINT16: + appender(data, indent); + appender(data, "val.uint16); + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_UINT32: + appender(data, indent); + appender(data, "val.uint32); + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_UINT64: + appender(data, indent); + appender(data, "val.uint64); + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_UINT128: + appender(data, indent); + appender(data, "val.uint128.data[i]); + } + + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_INT8: + appender(data, indent); + appender(data, "val.int8); + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_INT16: + appender(data, indent); + appender(data, "val.int16); + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_INT32: + appender(data, indent); + appender(data, "val.int32); + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_INT64: + appender(data, indent); + appender(data, "val.int64); + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_INT128: + appender(data, indent); + appender(data, "val.int128.data[i]); + } + appender(data, buf); + + appender(data, "\" />\n"); + break; + + case SDP_UUID16: + appender(data, indent); + appender(data, "val.uuid.value.uuid16); + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_UUID32: + appender(data, indent); + appender(data, "val.uuid.value.uuid32); + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_UUID128: + appender(data, indent); + appender(data, "val.uuid.value. + uuid128.data[0], + (unsigned char) value->val.uuid.value. + uuid128.data[1], + (unsigned char) value->val.uuid.value. + uuid128.data[2], + (unsigned char) value->val.uuid.value. + uuid128.data[3], + (unsigned char) value->val.uuid.value. + uuid128.data[4], + (unsigned char) value->val.uuid.value. + uuid128.data[5], + (unsigned char) value->val.uuid.value. + uuid128.data[6], + (unsigned char) value->val.uuid.value. + uuid128.data[7], + (unsigned char) value->val.uuid.value. + uuid128.data[8], + (unsigned char) value->val.uuid.value. + uuid128.data[9], + (unsigned char) value->val.uuid.value. + uuid128.data[10], + (unsigned char) value->val.uuid.value. + uuid128.data[11], + (unsigned char) value->val.uuid.value. + uuid128.data[12], + (unsigned char) value->val.uuid.value. + uuid128.data[13], + (unsigned char) value->val.uuid.value. + uuid128.data[14], + (unsigned char) value->val.uuid.value. + uuid128.data[15]); + + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_TEXT_STR8: + case SDP_TEXT_STR16: + case SDP_TEXT_STR32: + { + int num_chars_to_escape = 0; + int length = value->unitSize - 1; + char *strBuf = 0; + + hex = 0; + + for (i = 0; i < length; i++) { + if (!isprint(value->val.str[i]) && + value->val.str[i] != '\0') { + hex = 1; + break; + } + + /* XML is evil, must do this... */ + if ((value->val.str[i] == '<') || + (value->val.str[i] == '>') || + (value->val.str[i] == '"') || + (value->val.str[i] == '&')) + num_chars_to_escape++; + } + + appender(data, indent); + + appender(data, "unitSize-1) * 2 + 1)); + + /* Unit Size seems to include the size for dtd + It is thus off by 1 + This is safe for Normal strings, but not + hex encoded data */ + for (i = 0; i < (value->unitSize-1); i++) + sprintf(&strBuf[i*sizeof(char)*2], + "%02x", + (unsigned char) value->val.str[i]); + + strBuf[(value->unitSize-1) * 2] = '\0'; + } + else { + int j; + /* escape the XML disallowed chars */ + strBuf = malloc(sizeof(char) * + (value->unitSize + 1 + num_chars_to_escape * 4)); + for (i = 0, j = 0; i < length; i++) { + if (value->val.str[i] == '&') { + strBuf[j++] = '&'; + strBuf[j++] = 'a'; + strBuf[j++] = 'm'; + strBuf[j++] = 'p'; + } + else if (value->val.str[i] == '<') { + strBuf[j++] = '&'; + strBuf[j++] = 'l'; + strBuf[j++] = 't'; + } + else if (value->val.str[i] == '>') { + strBuf[j++] = '&'; + strBuf[j++] = 'g'; + strBuf[j++] = 't'; + } + else if (value->val.str[i] == '"') { + strBuf[j++] = '&'; + strBuf[j++] = 'q'; + strBuf[j++] = 'u'; + strBuf[j++] = 'o'; + strBuf[j++] = 't'; + } + else if (value->val.str[i] == '\0') { + strBuf[j++] = ' '; + } else { + strBuf[j++] = value->val.str[i]; + } + } + + strBuf[j] = '\0'; + } + + appender(data, "value=\""); + appender(data, strBuf); + appender(data, "\" />\n"); + free(strBuf); + break; + } + + case SDP_URL_STR8: + case SDP_URL_STR16: + case SDP_URL_STR32: + { + char *strBuf; + + appender(data, indent); + appender(data, "val.str, value->unitSize - 1); + appender(data, strBuf); + free(strBuf); + appender(data, "\" />\n"); + break; + } + + case SDP_SEQ8: + case SDP_SEQ16: + case SDP_SEQ32: + appender(data, indent); + appender(data, "\n"); + + convert_raw_data_to_xml(value->val.dataseq, + indent_level + 1, data, appender); + + appender(data, indent); + appender(data, "\n"); + + break; + + case SDP_ALT8: + case SDP_ALT16: + case SDP_ALT32: + appender(data, indent); + + appender(data, "\n"); + + convert_raw_data_to_xml(value->val.dataseq, + indent_level + 1, data, appender); + appender(data, indent); + + appender(data, "\n"); + + break; + } + + convert_raw_data_to_xml(value->next, indent_level, data, appender); +} + +struct conversion_data { + void *data; + void (*appender)(void *data, const char *); +}; + +static void convert_raw_attr_to_xml_func(void *val, void *data) +{ + struct conversion_data *cd = data; + sdp_data_t *value = val; + char buf[STRBUFSIZE]; + + buf[STRBUFSIZE - 1] = '\0'; + snprintf(buf, STRBUFSIZE - 1, "\t\n", + value->attrId); + cd->appender(cd->data, buf); + + if (data) + convert_raw_data_to_xml(value, 2, cd->data, cd->appender); + else + cd->appender(cd->data, "\t\tNULL\n"); + + cd->appender(cd->data, "\t\n"); +} + +/* + * Will convert the sdp record to XML. The appender and data can be used + * to control where to output the record (e.g. file or a data buffer). The + * appender will be called repeatedly with data and the character buffer + * (containing parts of the generated XML) to append. + */ +void convert_sdp_record_to_xml(sdp_record_t *rec, + void *data, void (*appender)(void *, const char *)) +{ + struct conversion_data cd; + + cd.data = data; + cd.appender = appender; + + if (rec && rec->attrlist) { + appender(data, "\n\n"); + appender(data, "\n"); + sdp_list_foreach(rec->attrlist, + convert_raw_attr_to_xml_func, &cd); + appender(data, "\n"); + } +} + +static sdp_data_t *sdp_xml_parse_uuid128(const char *data) +{ + uint128_t val; + unsigned int i, j; + + char buf[3]; + + memset(&val, 0, sizeof(val)); + + buf[2] = '\0'; + + for (j = 0, i = 0; i < strlen(data);) { + if (data[i] == '-') { + i++; + continue; + } + + buf[0] = data[i]; + buf[1] = data[i + 1]; + + val.data[j++] = strtoul(buf, 0, 16); + i += 2; + } + + return sdp_data_alloc(SDP_UUID128, &val); +} + +sdp_data_t *sdp_xml_parse_uuid(const char *data, sdp_record_t *record) +{ + sdp_data_t *ret; + char *endptr; + uint32_t val; + uint16_t val2; + int len; + + len = strlen(data); + + if (len == 36) { + ret = sdp_xml_parse_uuid128(data); + goto result; + } + + val = strtoll(data, &endptr, 16); + + /* Couldn't parse */ + if (*endptr != '\0') + return NULL; + + if (val > USHRT_MAX) { + ret = sdp_data_alloc(SDP_UUID32, &val); + goto result; + } + + val2 = val; + + ret = sdp_data_alloc(SDP_UUID16, &val2); + +result: + if (record && ret) + sdp_pattern_add_uuid(record, &ret->val.uuid); + + return ret; +} + +sdp_data_t *sdp_xml_parse_int(const char * data, uint8_t dtd) +{ + char *endptr; + sdp_data_t *ret = NULL; + + switch (dtd) { + case SDP_BOOL: + { + uint8_t val = 0; + + if (!strcmp("true", data)) { + val = 1; + } + + else if (!strcmp("false", data)) { + val = 0; + } + else { + return NULL; + } + + ret = sdp_data_alloc(dtd, &val); + break; + } + + case SDP_INT8: + { + int8_t val = strtoul(data, &endptr, 0); + + /* Failed to parse */ + if ((endptr != data) && (*endptr != '\0')) + return NULL; + + ret = sdp_data_alloc(dtd, &val); + break; + } + + case SDP_UINT8: + { + uint8_t val = strtoul(data, &endptr, 0); + + /* Failed to parse */ + if ((endptr != data) && (*endptr != '\0')) + return NULL; + + ret = sdp_data_alloc(dtd, &val); + break; + } + + case SDP_INT16: + { + int16_t val = strtoul(data, &endptr, 0); + + /* Failed to parse */ + if ((endptr != data) && (*endptr != '\0')) + return NULL; + + ret = sdp_data_alloc(dtd, &val); + break; + } + + case SDP_UINT16: + { + uint16_t val = strtoul(data, &endptr, 0); + + /* Failed to parse */ + if ((endptr != data) && (*endptr != '\0')) + return NULL; + + ret = sdp_data_alloc(dtd, &val); + break; + } + + case SDP_INT32: + { + int32_t val = strtoul(data, &endptr, 0); + + /* Failed to parse */ + if ((endptr != data) && (*endptr != '\0')) + return NULL; + + ret = sdp_data_alloc(dtd, &val); + break; + } + + case SDP_UINT32: + { + uint32_t val = strtoul(data, &endptr, 0); + + /* Failed to parse */ + if ((endptr != data) && (*endptr != '\0')) + return NULL; + + ret = sdp_data_alloc(dtd, &val); + break; + } + + case SDP_INT64: + { + int64_t val = strtoull(data, &endptr, 0); + + /* Failed to parse */ + if ((endptr != data) && (*endptr != '\0')) + return NULL; + + ret = sdp_data_alloc(dtd, &val); + break; + } + + case SDP_UINT64: + { + uint64_t val = strtoull(data, &endptr, 0); + + /* Failed to parse */ + if ((endptr != data) && (*endptr != '\0')) + return NULL; + + ret = sdp_data_alloc(dtd, &val); + break; + } + + case SDP_INT128: + case SDP_UINT128: + { + uint128_t val; + int i = 0; + char buf[3]; + + buf[2] = '\0'; + + for (; i < 32; i += 2) { + buf[0] = data[i]; + buf[1] = data[i + 1]; + + val.data[i >> 1] = strtoul(buf, 0, 16); + } + + ret = sdp_data_alloc(dtd, &val); + break; + } + + }; + + return ret; +} + +static char *sdp_xml_parse_string_decode(const char *data, char encoding, uint32_t *length) +{ + int len = strlen(data); + char *text; + + if (encoding == SDP_XML_ENCODING_NORMAL) { + text = strdup(data); + *length = len; + } else { + char buf[3], *decoded; + int i; + + decoded = malloc((len >> 1) + 1); + + /* Ensure the string is a power of 2 */ + len = (len >> 1) << 1; + + buf[2] = '\0'; + + for (i = 0; i < len; i += 2) { + buf[0] = data[i]; + buf[1] = data[i + 1]; + + decoded[i >> 1] = strtoul(buf, 0, 16); + } + + decoded[len >> 1] = '\0'; + text = decoded; + *length = len >> 1; + } + + return text; +} + +sdp_data_t *sdp_xml_parse_url(const char *data) +{ + uint8_t dtd = SDP_URL_STR8; + char *url; + uint32_t length; + sdp_data_t *ret; + + url = sdp_xml_parse_string_decode(data, + SDP_XML_ENCODING_NORMAL, &length); + + if (length > UCHAR_MAX) + dtd = SDP_URL_STR16; + + ret = sdp_data_alloc_with_length(dtd, url, length); + + free(url); + + return ret; +} + +sdp_data_t *sdp_xml_parse_text(const char *data, char encoding) +{ + uint8_t dtd = SDP_TEXT_STR8; + char *text; + uint32_t length; + sdp_data_t *ret; + + text = sdp_xml_parse_string_decode(data, encoding, &length); + + if (length > UCHAR_MAX) + dtd = SDP_TEXT_STR16; + + ret = sdp_data_alloc_with_length(dtd, text, length); + + free(text); + + return ret; +} + +sdp_data_t *sdp_xml_parse_nil(const char *data) +{ + return sdp_data_alloc(SDP_DATA_NIL, 0); +} + +#define DEFAULT_XML_DATA_SIZE 1024 + +struct sdp_xml_data *sdp_xml_data_alloc() +{ + struct sdp_xml_data *elem; + + elem = malloc(sizeof(struct sdp_xml_data)); + if (!elem) + return NULL; + + memset(elem, 0, sizeof(struct sdp_xml_data)); + + /* Null terminate the text */ + elem->size = DEFAULT_XML_DATA_SIZE; + elem->text = malloc(DEFAULT_XML_DATA_SIZE); + elem->text[0] = '\0'; + + return elem; +} + +void sdp_xml_data_free(struct sdp_xml_data *elem) +{ + if (elem->data) + sdp_data_free(elem->data); + + free(elem->name); + free(elem->text); + free(elem); +} + +struct sdp_xml_data *sdp_xml_data_expand(struct sdp_xml_data *elem) +{ + char *newbuf; + + newbuf = malloc(elem->size * 2); + if (!newbuf) + return NULL; + + memcpy(newbuf, elem->text, elem->size); + elem->size *= 2; + free(elem->text); + + elem->text = newbuf; + + return elem; +} + +sdp_data_t *sdp_xml_parse_datatype(const char *el, struct sdp_xml_data *elem, + sdp_record_t *record) +{ + const char *data = elem->text; + + if (!strcmp(el, "boolean")) + return sdp_xml_parse_int(data, SDP_BOOL); + else if (!strcmp(el, "uint8")) + return sdp_xml_parse_int(data, SDP_UINT8); + else if (!strcmp(el, "uint16")) + return sdp_xml_parse_int(data, SDP_UINT16); + else if (!strcmp(el, "uint32")) + return sdp_xml_parse_int(data, SDP_UINT32); + else if (!strcmp(el, "uint64")) + return sdp_xml_parse_int(data, SDP_UINT64); + else if (!strcmp(el, "uint128")) + return sdp_xml_parse_int(data, SDP_UINT128); + else if (!strcmp(el, "int8")) + return sdp_xml_parse_int(data, SDP_INT8); + else if (!strcmp(el, "int16")) + return sdp_xml_parse_int(data, SDP_INT16); + else if (!strcmp(el, "int32")) + return sdp_xml_parse_int(data, SDP_INT32); + else if (!strcmp(el, "int64")) + return sdp_xml_parse_int(data, SDP_INT64); + else if (!strcmp(el, "int128")) + return sdp_xml_parse_int(data, SDP_INT128); + else if (!strcmp(el, "uuid")) + return sdp_xml_parse_uuid(data, record); + else if (!strcmp(el, "url")) + return sdp_xml_parse_url(data); + else if (!strcmp(el, "text")) + return sdp_xml_parse_text(data, elem->type); + else if (!strcmp(el, "nil")) + return sdp_xml_parse_nil(data); + + return NULL; +} diff --git a/src/sdp-xml.h b/src/sdp-xml.h new file mode 100644 index 0000000..7031276 --- /dev/null +++ b/src/sdp-xml.h @@ -0,0 +1,59 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2005-2010 Marcel Holtmann + * + * + * 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 + * + */ + + +#ifndef __SDP_XML_H +#define __SDP_XML_H + +#include + +#define SDP_XML_ENCODING_NORMAL 0 +#define SDP_XML_ENCODING_HEX 1 + +void convert_sdp_record_to_xml(sdp_record_t *rec, + void *user_data, void (*append_func) (void *, const char *)); + +sdp_data_t *sdp_xml_parse_nil(const char *data); +sdp_data_t *sdp_xml_parse_text(const char *data, char encoding); +sdp_data_t *sdp_xml_parse_url(const char *data); +sdp_data_t *sdp_xml_parse_int(const char *data, uint8_t dtd); +sdp_data_t *sdp_xml_parse_uuid(const char *data, sdp_record_t *record); + +struct sdp_xml_data { + char *text; /* Pointer to the current buffer */ + int size; /* Size of the current buffer */ + sdp_data_t *data; /* The current item being built */ + struct sdp_xml_data *next; /* Next item on the stack */ + char type; /* 0 = Text or Hexadecimal */ + char *name; /* Name, optional in the dtd */ + /* TODO: What is it used for? */ +}; + +struct sdp_xml_data *sdp_xml_data_alloc(); +void sdp_xml_data_free(struct sdp_xml_data *elem); +struct sdp_xml_data *sdp_xml_data_expand(struct sdp_xml_data *elem); + +sdp_data_t *sdp_xml_parse_datatype(const char *el, struct sdp_xml_data *elem, + sdp_record_t *record); + +#endif /* __SDP_XML_H */ diff --git a/src/sdpd-database.c b/src/sdpd-database.c new file mode 100644 index 0000000..08f542f --- /dev/null +++ b/src/sdpd-database.c @@ -0,0 +1,346 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2001-2002 Nokia Corporation + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * Copyright (C) 2002-2003 Stephen Crane + * + * + * 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 +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "sdpd.h" +#include "log.h" +#include "adapter.h" +#include "manager.h" + +static sdp_list_t *service_db; +static sdp_list_t *access_db; + +typedef struct { + uint32_t handle; + bdaddr_t device; +} sdp_access_t; + +/* + * Ordering function called when inserting a service record. + * The service repository is a linked list in sorted order + * and the service record handle is the sort key + */ +int record_sort(const void *r1, const void *r2) +{ + const sdp_record_t *rec1 = r1; + const sdp_record_t *rec2 = r2; + + if (!rec1 || !rec2) { + error("NULL RECORD LIST FATAL"); + return -1; + } + + return rec1->handle - rec2->handle; +} + +static int access_sort(const void *r1, const void *r2) +{ + const sdp_access_t *rec1 = r1; + const sdp_access_t *rec2 = r2; + + if (!rec1 || !rec2) { + error("NULL RECORD LIST FATAL"); + return -1; + } + + return rec1->handle - rec2->handle; +} + +static void access_free(void *p) +{ + free(p); +} + +/* + * Reset the service repository by deleting its contents + */ +void sdp_svcdb_reset() +{ + sdp_list_free(service_db, (sdp_free_func_t) sdp_record_free); + sdp_list_free(access_db, access_free); +} + +typedef struct _indexed { + int sock; + sdp_record_t *record; +} sdp_indexed_t; + +static sdp_list_t *socket_index; + +/* + * collect all services registered over this socket + */ +void sdp_svcdb_collect_all(int sock) +{ + sdp_list_t *p, *q; + + for (p = socket_index, q = 0; p; ) { + sdp_indexed_t *item = p->data; + if (item->sock == sock) { + sdp_list_t *next = p->next; + sdp_record_remove(item->record->handle); + sdp_record_free(item->record); + free(item); + if (q) + q->next = next; + else + socket_index = next; + free(p); + p = next; + } else if (item->sock > sock) + return; + else { + q = p; + p = p->next; + } + } +} + +void sdp_svcdb_collect(sdp_record_t *rec) +{ + sdp_list_t *p, *q; + + for (p = socket_index, q = 0; p; q = p, p = p->next) { + sdp_indexed_t *item = p->data; + if (rec == item->record) { + free(item); + if (q) + q->next = p->next; + else + socket_index = p->next; + free(p); + return; + } + } +} + +static int compare_indices(const void *i1, const void *i2) +{ + const sdp_indexed_t *s1 = i1; + const sdp_indexed_t *s2 = i2; + return s1->sock - s2->sock; +} + +void sdp_svcdb_set_collectable(sdp_record_t *record, int sock) +{ + sdp_indexed_t *item = malloc(sizeof(sdp_indexed_t)); + item->sock = sock; + item->record = record; + socket_index = sdp_list_insert_sorted(socket_index, item, compare_indices); +} + +/* + * Add a service record to the repository + */ +void sdp_record_add(const bdaddr_t *device, sdp_record_t *rec) +{ + struct btd_adapter *adapter; + sdp_access_t *dev; + + SDPDBG("Adding rec : 0x%lx", (long) rec); + SDPDBG("with handle : 0x%x", rec->handle); + + service_db = sdp_list_insert_sorted(service_db, rec, record_sort); + + dev = malloc(sizeof(*dev)); + if (!dev) + return; + + bacpy(&dev->device, device); + dev->handle = rec->handle; + + access_db = sdp_list_insert_sorted(access_db, dev, access_sort); + + if (bacmp(device, BDADDR_ANY) == 0) { + manager_foreach_adapter(adapter_service_insert, rec); + return; + } + + adapter = manager_find_adapter(device); + if (adapter) + adapter_service_insert(adapter, rec); +} + +static sdp_list_t *record_locate(uint32_t handle) +{ + if (service_db) { + sdp_list_t *p; + sdp_record_t r; + + r.handle = handle; + p = sdp_list_find(service_db, &r, record_sort); + return p; + } + + SDPDBG("Could not find svcRec for : 0x%x", handle); + return NULL; +} + +static sdp_list_t *access_locate(uint32_t handle) +{ + if (access_db) { + sdp_list_t *p; + sdp_access_t a; + + a.handle = handle; + p = sdp_list_find(access_db, &a, access_sort); + return p; + } + + SDPDBG("Could not find access data for : 0x%x", handle); + return NULL; +} + +/* + * Given a service record handle, find the record associated with it. + */ +sdp_record_t *sdp_record_find(uint32_t handle) +{ + sdp_list_t *p = record_locate(handle); + + if (!p) { + SDPDBG("Couldn't find record for : 0x%x", handle); + return 0; + } + + return p->data; +} + +/* + * Given a service record handle, remove its record from the repository + */ +int sdp_record_remove(uint32_t handle) +{ + sdp_list_t *p = record_locate(handle); + sdp_record_t *r; + sdp_access_t *a; + + if (!p) { + error("Remove : Couldn't find record for : 0x%x", handle); + return -1; + } + + r = p->data; + if (r) + service_db = sdp_list_remove(service_db, r); + + p = access_locate(handle); + if (p == NULL || p->data == NULL) + return 0; + + a = p->data; + + if (bacmp(&a->device, BDADDR_ANY) != 0) { + struct btd_adapter *adapter = manager_find_adapter(&a->device); + if (adapter) + adapter_service_remove(adapter, r); + } else + manager_foreach_adapter(adapter_service_remove, r); + + access_db = sdp_list_remove(access_db, a); + access_free(a); + + return 0; +} + +/* + * Return a pointer to the linked list containing the records in sorted order + */ +sdp_list_t *sdp_get_record_list(void) +{ + return service_db; +} + +sdp_list_t *sdp_get_access_list(void) +{ + return access_db; +} + +int sdp_check_access(uint32_t handle, bdaddr_t *device) +{ + sdp_list_t *p = access_locate(handle); + sdp_access_t *a; + + if (!p) + return 1; + + a = p->data; + if (!a) + return 1; + + if (bacmp(&a->device, device) && + bacmp(&a->device, BDADDR_ANY) && + bacmp(device, BDADDR_ANY)) + return 0; + + return 1; +} + +uint32_t sdp_next_handle(void) +{ + uint32_t handle = 0x10000; + + while (sdp_record_find(handle)) + handle++; + + return handle; +} + +void sdp_init_services_list(bdaddr_t *device) +{ + sdp_list_t *p; + + DBG(""); + + for (p = access_db; p != NULL; p = p->next) { + sdp_access_t *access = p->data; + sdp_record_t *rec; + + if (bacmp(BDADDR_ANY, &access->device)) + continue; + + rec = sdp_record_find(access->handle); + if (rec == NULL) + continue; + + SDPDBG("adding record with handle %x", access->handle); + + manager_foreach_adapter(adapter_service_insert, rec); + } +} diff --git a/src/sdpd-request.c b/src/sdpd-request.c new file mode 100644 index 0000000..1722f78 --- /dev/null +++ b/src/sdpd-request.c @@ -0,0 +1,1094 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2001-2002 Nokia Corporation + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * Copyright (C) 2002-2003 Stephen Crane + * + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "sdpd.h" +#include "log.h" + +typedef struct { + uint32_t timestamp; + union { + uint16_t maxBytesSent; + uint16_t lastIndexSent; + } cStateValue; +} sdp_cont_state_t; + +#define SDP_CONT_STATE_SIZE (sizeof(uint8_t) + sizeof(sdp_cont_state_t)) + +#define MIN(x, y) ((x) < (y)) ? (x): (y) + +typedef struct _sdp_cstate_list sdp_cstate_list_t; + +struct _sdp_cstate_list { + sdp_cstate_list_t *next; + uint32_t timestamp; + sdp_buf_t buf; +}; + +static sdp_cstate_list_t *cstates; + +// FIXME: should probably remove it when it's found +static sdp_buf_t *sdp_get_cached_rsp(sdp_cont_state_t *cstate) +{ + sdp_cstate_list_t *p; + + for (p = cstates; p; p = p->next) + if (p->timestamp == cstate->timestamp) + return &p->buf; + return 0; +} + +static uint32_t sdp_cstate_alloc_buf(sdp_buf_t *buf) +{ + sdp_cstate_list_t *cstate = malloc(sizeof(sdp_cstate_list_t)); + uint8_t *data = malloc(buf->data_size); + + memcpy(data, buf->data, buf->data_size); + memset((char *)cstate, 0, sizeof(sdp_cstate_list_t)); + cstate->buf.data = data; + cstate->buf.data_size = buf->data_size; + cstate->buf.buf_size = buf->data_size; + cstate->timestamp = sdp_get_time(); + cstate->next = cstates; + cstates = cstate; + return cstate->timestamp; +} + +/* Additional values for checking datatype (not in spec) */ +#define SDP_TYPE_UUID 0xfe +#define SDP_TYPE_ATTRID 0xff + +struct attrid { + uint8_t dtd; + union { + uint16_t uint16; + uint32_t uint32; + }; +}; + +/* + * Generic data element sequence extractor. Builds + * a list whose elements are those found in the + * sequence. The data type of elements found in the + * sequence is returned in the reference pDataType + */ +static int extract_des(uint8_t *buf, int len, sdp_list_t **svcReqSeq, uint8_t *pDataType, uint8_t expectedType) +{ + uint8_t seqType; + int scanned, data_size = 0; + short numberOfElements = 0; + int seqlen = 0; + sdp_list_t *pSeq = NULL; + uint8_t dataType; + int status = 0; + const uint8_t *p; + size_t bufsize; + + scanned = sdp_extract_seqtype(buf, len, &seqType, &data_size); + + SDPDBG("Seq type : %d", seqType); + if (!scanned || (seqType != SDP_SEQ8 && seqType != SDP_SEQ16)) { + error("Unknown seq type"); + return -1; + } + p = buf + scanned; + bufsize = len - scanned; + + SDPDBG("Data size : %d", data_size); + + for (;;) { + char *pElem = NULL; + int localSeqLength = 0; + + if (bufsize < sizeof(uint8_t)) { + SDPDBG("->Unexpected end of buffer"); + goto failed; + } + + dataType = *p; + + SDPDBG("Data type: 0x%02x", dataType); + + if (expectedType == SDP_TYPE_UUID) { + if (dataType != SDP_UUID16 && dataType != SDP_UUID32 && dataType != SDP_UUID128) { + SDPDBG("->Unexpected Data type (expected UUID_ANY)"); + goto failed; + } + } else if (expectedType == SDP_TYPE_ATTRID && + (dataType != SDP_UINT16 && dataType != SDP_UINT32)) { + SDPDBG("->Unexpected Data type (expected 0x%02x or 0x%02x)", + SDP_UINT16, SDP_UINT32); + goto failed; + } else if (expectedType != SDP_TYPE_ATTRID && dataType != expectedType) { + SDPDBG("->Unexpected Data type (expected 0x%02x)", expectedType); + goto failed; + } + + switch (dataType) { + case SDP_UINT16: + p += sizeof(uint8_t); + seqlen += sizeof(uint8_t); + bufsize -= sizeof(uint8_t); + if (bufsize < sizeof(uint16_t)) { + SDPDBG("->Unexpected end of buffer"); + goto failed; + } + + if (expectedType == SDP_TYPE_ATTRID) { + struct attrid *aid; + aid = malloc(sizeof(struct attrid)); + aid->dtd = dataType; + bt_put_unaligned(ntohs(bt_get_unaligned((uint16_t *)p)), (uint16_t *)&aid->uint16); + pElem = (char *) aid; + } else { + pElem = malloc(sizeof(uint16_t)); + bt_put_unaligned(ntohs(bt_get_unaligned((uint16_t *)p)), (uint16_t *)pElem); + } + p += sizeof(uint16_t); + seqlen += sizeof(uint16_t); + bufsize -= sizeof(uint16_t); + break; + case SDP_UINT32: + p += sizeof(uint8_t); + seqlen += sizeof(uint8_t); + bufsize -= sizeof(uint8_t); + if (bufsize < (int)sizeof(uint32_t)) { + SDPDBG("->Unexpected end of buffer"); + goto failed; + } + + if (expectedType == SDP_TYPE_ATTRID) { + struct attrid *aid; + aid = malloc(sizeof(struct attrid)); + aid->dtd = dataType; + bt_put_unaligned(ntohl(bt_get_unaligned((uint32_t *)p)), (uint32_t *)&aid->uint32); + pElem = (char *) aid; + } else { + pElem = malloc(sizeof(uint32_t)); + bt_put_unaligned(ntohl(bt_get_unaligned((uint32_t *)p)), (uint32_t *)pElem); + } + p += sizeof(uint32_t); + seqlen += sizeof(uint32_t); + bufsize -= sizeof(uint32_t); + break; + case SDP_UUID16: + case SDP_UUID32: + case SDP_UUID128: + pElem = malloc(sizeof(uuid_t)); + status = sdp_uuid_extract(p, bufsize, (uuid_t *) pElem, &localSeqLength); + if (status < 0) { + free(pElem); + goto failed; + } + seqlen += localSeqLength; + p += localSeqLength; + bufsize -= localSeqLength; + break; + default: + return -1; + } + if (status == 0) { + pSeq = sdp_list_append(pSeq, pElem); + numberOfElements++; + SDPDBG("No of elements : %d", numberOfElements); + + if (seqlen == data_size) + break; + else if (seqlen > data_size || seqlen > len) + goto failed; + } else + free(pElem); + } + *svcReqSeq = pSeq; + scanned += seqlen; + *pDataType = dataType; + return scanned; + +failed: + sdp_list_free(pSeq, free); + return -1; +} + +static int sdp_set_cstate_pdu(sdp_buf_t *buf, sdp_cont_state_t *cstate) +{ + uint8_t *pdata = buf->data + buf->data_size; + int length = 0; + + if (cstate) { + SDPDBG("Non null sdp_cstate_t id : 0x%x", cstate->timestamp); + *(uint8_t *)pdata = sizeof(sdp_cont_state_t); + pdata += sizeof(uint8_t); + length += sizeof(uint8_t); + memcpy(pdata, cstate, sizeof(sdp_cont_state_t)); + length += sizeof(sdp_cont_state_t); + } else { + // set "null" continuation state + *(uint8_t *)pdata = 0; + pdata += sizeof(uint8_t); + length += sizeof(uint8_t); + } + buf->data_size += length; + return length; +} + +static int sdp_cstate_get(uint8_t *buffer, size_t len, + sdp_cont_state_t **cstate) +{ + uint8_t cStateSize = *buffer; + + SDPDBG("Continuation State size : %d", cStateSize); + + if (cStateSize == 0) { + *cstate = NULL; + return 0; + } + + buffer++; + len--; + + if (len < sizeof(sdp_cont_state_t)) + return -EINVAL; + + /* + * Check if continuation state exists, if yes attempt + * to get response remainder from cache, else send error + */ + + *cstate = malloc(sizeof(sdp_cont_state_t)); + if (!(*cstate)) + return -ENOMEM; + + memcpy(*cstate, buffer, sizeof(sdp_cont_state_t)); + + SDPDBG("Cstate TS : 0x%x", (*cstate)->timestamp); + SDPDBG("Bytes sent : %d", (*cstate)->cStateValue.maxBytesSent); + + return 0; +} + +/* + * The matching process is defined as "each and every UUID + * specified in the "search pattern" must be present in the + * "target pattern". Here "search pattern" is the set of UUIDs + * specified by the service discovery client and "target pattern" + * is the set of UUIDs present in a service record. + * + * Return 1 if each and every UUID in the search + * pattern exists in the target pattern, 0 if the + * match succeeds and -1 on error. + */ +static int sdp_match_uuid(sdp_list_t *search, sdp_list_t *pattern) +{ + /* + * The target is a sorted list, so we need not look + * at all elements to confirm existence of an element + * from the search pattern + */ + int patlen = sdp_list_len(pattern); + + if (patlen < sdp_list_len(search)) + return -1; + for (; search; search = search->next) { + uuid_t *uuid128; + void *data = search->data; + sdp_list_t *list; + if (data == NULL) + return -1; + + // create 128-bit form of the search UUID + uuid128 = sdp_uuid_to_uuid128((uuid_t *)data); + list = sdp_list_find(pattern, uuid128, sdp_uuid128_cmp); + bt_free(uuid128); + if (!list) + return 0; + } + return 1; +} + +/* + * Service search request PDU. This method extracts the search pattern + * (a sequence of UUIDs) and calls the matching function + * to find matching services + */ +static int service_search_req(sdp_req_t *req, sdp_buf_t *buf) +{ + int status = 0, i, plen, mlen, mtu, scanned; + sdp_list_t *pattern = NULL; + uint16_t expected, actual, rsp_count = 0; + uint8_t dtd; + sdp_cont_state_t *cstate = NULL; + uint8_t *pCacheBuffer = NULL; + int handleSize = 0; + uint32_t cStateId = 0; + short *pTotalRecordCount, *pCurrentRecordCount; + uint8_t *pdata = req->buf + sizeof(sdp_pdu_hdr_t); + size_t data_left = req->len - sizeof(sdp_pdu_hdr_t); + + scanned = extract_des(pdata, data_left, &pattern, &dtd, SDP_TYPE_UUID); + + if (scanned == -1) { + status = SDP_INVALID_SYNTAX; + goto done; + } + pdata += scanned; + data_left -= scanned; + + plen = ntohs(((sdp_pdu_hdr_t *)(req->buf))->plen); + mlen = scanned + sizeof(uint16_t) + 1; + // ensure we don't read past buffer + if (plen < mlen || plen != mlen + *(uint8_t *)(pdata+sizeof(uint16_t))) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + if (data_left < sizeof(uint16_t)) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + expected = ntohs(bt_get_unaligned((uint16_t *)pdata)); + + SDPDBG("Expected count: %d", expected); + SDPDBG("Bytes scanned : %d", scanned); + + pdata += sizeof(uint16_t); + data_left -= sizeof(uint16_t); + + /* + * Check if continuation state exists, if yes attempt + * to get rsp remainder from cache, else send error + */ + if (sdp_cstate_get(pdata, data_left, &cstate) < 0) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + mtu = req->mtu - sizeof(sdp_pdu_hdr_t) - sizeof(uint16_t) - sizeof(uint16_t) - SDP_CONT_STATE_SIZE; + actual = MIN(expected, mtu >> 2); + + /* make space in the rsp buffer for total and current record counts */ + pdata = buf->data; + + /* total service record count = 0 */ + pTotalRecordCount = (short *)pdata; + bt_put_unaligned(0, (uint16_t *)pdata); + pdata += sizeof(uint16_t); + buf->data_size += sizeof(uint16_t); + + /* current service record count = 0 */ + pCurrentRecordCount = (short *)pdata; + bt_put_unaligned(0, (uint16_t *)pdata); + pdata += sizeof(uint16_t); + buf->data_size += sizeof(uint16_t); + + if (cstate == NULL) { + /* for every record in the DB, do a pattern search */ + sdp_list_t *list = sdp_get_record_list(); + + handleSize = 0; + for (; list && rsp_count < expected; list = list->next) { + sdp_record_t *rec = list->data; + + SDPDBG("Checking svcRec : 0x%x", rec->handle); + + if (sdp_match_uuid(pattern, rec->pattern) > 0 && + sdp_check_access(rec->handle, &req->device)) { + rsp_count++; + bt_put_unaligned(htonl(rec->handle), (uint32_t *)pdata); + pdata += sizeof(uint32_t); + handleSize += sizeof(uint32_t); + } + } + + SDPDBG("Match count: %d", rsp_count); + + buf->data_size += handleSize; + bt_put_unaligned(htons(rsp_count), (uint16_t *)pTotalRecordCount); + bt_put_unaligned(htons(rsp_count), (uint16_t *)pCurrentRecordCount); + + if (rsp_count > actual) { + /* cache the rsp and generate a continuation state */ + cStateId = sdp_cstate_alloc_buf(buf); + /* + * subtract handleSize since we now send only + * a subset of handles + */ + buf->data_size -= handleSize; + } else { + /* NULL continuation state */ + sdp_set_cstate_pdu(buf, NULL); + } + } + + /* under both the conditions below, the rsp buffer is not built yet */ + if (cstate || cStateId > 0) { + short lastIndex = 0; + + if (cstate) { + /* + * Get the previous sdp_cont_state_t and obtain + * the cached rsp + */ + sdp_buf_t *pCache = sdp_get_cached_rsp(cstate); + if (pCache) { + pCacheBuffer = pCache->data; + /* get the rsp_count from the cached buffer */ + rsp_count = ntohs(bt_get_unaligned((uint16_t *)pCacheBuffer)); + + /* get index of the last sdp_record_t sent */ + lastIndex = cstate->cStateValue.lastIndexSent; + } else { + status = SDP_INVALID_CSTATE; + goto done; + } + } else { + pCacheBuffer = buf->data; + lastIndex = 0; + } + + /* + * Set the local buffer pointer to after the + * current record count and increment the cached + * buffer pointer to beyond the counters + */ + pdata = (uint8_t *) pCurrentRecordCount + sizeof(uint16_t); + + /* increment beyond the totalCount and the currentCount */ + pCacheBuffer += 2 * sizeof(uint16_t); + + if (cstate) { + handleSize = 0; + for (i = lastIndex; (i - lastIndex) < actual && i < rsp_count; i++) { + bt_put_unaligned(bt_get_unaligned((uint32_t *)(pCacheBuffer + i * sizeof(uint32_t))), (uint32_t *)pdata); + pdata += sizeof(uint32_t); + handleSize += sizeof(uint32_t); + } + } else { + handleSize = actual << 2; + i = actual; + } + + buf->data_size += handleSize; + bt_put_unaligned(htons(rsp_count), (uint16_t *)pTotalRecordCount); + bt_put_unaligned(htons(i - lastIndex), (uint16_t *)pCurrentRecordCount); + + if (i == rsp_count) { + /* set "null" continuationState */ + sdp_set_cstate_pdu(buf, NULL); + } else { + /* + * there's more: set lastIndexSent to + * the new value and move on + */ + sdp_cont_state_t newState; + + SDPDBG("Setting non-NULL sdp_cstate_t"); + + if (cstate) + memcpy(&newState, cstate, sizeof(sdp_cont_state_t)); + else { + memset(&newState, 0, sizeof(sdp_cont_state_t)); + newState.timestamp = cStateId; + } + newState.cStateValue.lastIndexSent = i; + sdp_set_cstate_pdu(buf, &newState); + } + } + +done: + free(cstate); + if (pattern) + sdp_list_free(pattern, free); + + return status; +} + +/* + * Extract attribute identifiers from the request PDU. + * Clients could request a subset of attributes (by id) + * from a service record, instead of the whole set. The + * requested identifiers are present in the PDU form of + * the request + */ +static int extract_attrs(sdp_record_t *rec, sdp_list_t *seq, sdp_buf_t *buf) +{ + sdp_buf_t pdu; + + if (!rec) + return SDP_INVALID_RECORD_HANDLE; + + if (seq == NULL) { + SDPDBG("Attribute sequence is NULL"); + return 0; + } + + SDPDBG("Entries in attr seq : %d", sdp_list_len(seq)); + + sdp_gen_record_pdu(rec, &pdu); + + for (; seq; seq = seq->next) { + struct attrid *aid = seq->data; + + SDPDBG("AttrDataType : %d", aid->dtd); + + if (aid->dtd == SDP_UINT16) { + uint16_t attr = bt_get_unaligned((uint16_t *)&aid->uint16); + sdp_data_t *a = sdp_data_get(rec, attr); + if (a) + sdp_append_to_pdu(buf, a); + } else if (aid->dtd == SDP_UINT32) { + uint32_t range = bt_get_unaligned((uint32_t *)&aid->uint32); + uint16_t attr; + uint16_t low = (0xffff0000 & range) >> 16; + uint16_t high = 0x0000ffff & range; + sdp_data_t *data; + + SDPDBG("attr range : 0x%x", range); + SDPDBG("Low id : 0x%x", low); + SDPDBG("High id : 0x%x", high); + + if (low == 0x0000 && high == 0xffff && pdu.data_size <= buf->buf_size) { + /* copy it */ + memcpy(buf->data, pdu.data, pdu.data_size); + buf->data_size = pdu.data_size; + break; + } + /* (else) sub-range of attributes */ + for (attr = low; attr < high; attr++) { + data = sdp_data_get(rec, attr); + if (data) + sdp_append_to_pdu(buf, data); + } + data = sdp_data_get(rec, high); + if (data) + sdp_append_to_pdu(buf, data); + } else { + error("Unexpected data type : 0x%x", aid->dtd); + error("Expect uint16_t or uint32_t"); + free(pdu.data); + return SDP_INVALID_SYNTAX; + } + } + + free(pdu.data); + + return 0; +} + +/* + * A request for the attributes of a service record. + * First check if the service record (specified by + * service record handle) exists, then call the attribute + * streaming function + */ +static int service_attr_req(sdp_req_t *req, sdp_buf_t *buf) +{ + sdp_cont_state_t *cstate = NULL; + uint8_t *pResponse = NULL; + short cstate_size = 0; + sdp_list_t *seq = NULL; + uint8_t dtd = 0; + int scanned = 0; + unsigned int max_rsp_size; + int status = 0, plen, mlen; + uint8_t *pdata = req->buf + sizeof(sdp_pdu_hdr_t); + size_t data_left = req->len - sizeof(sdp_pdu_hdr_t); + uint32_t handle; + + if (data_left < sizeof(uint32_t)) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + handle = ntohl(bt_get_unaligned((uint32_t *)pdata)); + + pdata += sizeof(uint32_t); + data_left -= sizeof(uint32_t); + + if (data_left < sizeof(uint16_t)) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + max_rsp_size = ntohs(bt_get_unaligned((uint16_t *)pdata)); + + pdata += sizeof(uint16_t); + data_left -= sizeof(uint16_t); + + if (data_left < sizeof(sdp_pdu_hdr_t)) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + /* extract the attribute list */ + scanned = extract_des(pdata, data_left, &seq, &dtd, SDP_TYPE_ATTRID); + if (scanned == -1) { + status = SDP_INVALID_SYNTAX; + goto done; + } + pdata += scanned; + data_left -= scanned; + + plen = ntohs(((sdp_pdu_hdr_t *)(req->buf))->plen); + mlen = scanned + sizeof(uint32_t) + sizeof(uint16_t) + 1; + // ensure we don't read past buffer + if (plen < mlen || plen != mlen + *(uint8_t *)pdata) { + status = SDP_INVALID_PDU_SIZE; + goto done; + } + + /* + * if continuation state exists, attempt + * to get rsp remainder from cache, else send error + */ + if (sdp_cstate_get(pdata, data_left, &cstate) < 0) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + SDPDBG("SvcRecHandle : 0x%x", handle); + SDPDBG("max_rsp_size : %d", max_rsp_size); + + /* + * Check that max_rsp_size is within valid range + * a minimum size of 0x0007 has to be used for data field + */ + if (max_rsp_size < 0x0007) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + /* + * Calculate Attribute size acording to MTU + * We can send only (MTU - sizeof(sdp_pdu_hdr_t) - sizeof(sdp_cont_state_t)) + */ + max_rsp_size = MIN(max_rsp_size, req->mtu - sizeof(sdp_pdu_hdr_t) - + sizeof(uint32_t) - SDP_CONT_STATE_SIZE - sizeof(uint16_t)); + + /* pull header for AttributeList byte count */ + buf->data += sizeof(uint16_t); + buf->buf_size -= sizeof(uint16_t); + + if (cstate) { + sdp_buf_t *pCache = sdp_get_cached_rsp(cstate); + + SDPDBG("Obtained cached rsp : %p", pCache); + + if (pCache) { + short sent = MIN(max_rsp_size, pCache->data_size - cstate->cStateValue.maxBytesSent); + pResponse = pCache->data; + memcpy(buf->data, pResponse + cstate->cStateValue.maxBytesSent, sent); + buf->data_size += sent; + cstate->cStateValue.maxBytesSent += sent; + + SDPDBG("Response size : %d sending now : %d bytes sent so far : %d", + pCache->data_size, sent, cstate->cStateValue.maxBytesSent); + if (cstate->cStateValue.maxBytesSent == pCache->data_size) + cstate_size = sdp_set_cstate_pdu(buf, NULL); + else + cstate_size = sdp_set_cstate_pdu(buf, cstate); + } else { + status = SDP_INVALID_CSTATE; + error("NULL cache buffer and non-NULL continuation state"); + } + } else { + sdp_record_t *rec = sdp_record_find(handle); + status = extract_attrs(rec, seq, buf); + if (buf->data_size > max_rsp_size) { + sdp_cont_state_t newState; + + memset((char *)&newState, 0, sizeof(sdp_cont_state_t)); + newState.timestamp = sdp_cstate_alloc_buf(buf); + /* + * Reset the buffer size to the maximum expected and + * set the sdp_cont_state_t + */ + SDPDBG("Creating continuation state of size : %d", buf->data_size); + buf->data_size = max_rsp_size; + newState.cStateValue.maxBytesSent = max_rsp_size; + cstate_size = sdp_set_cstate_pdu(buf, &newState); + } else { + if (buf->data_size == 0) + sdp_append_to_buf(buf, 0, 0); + cstate_size = sdp_set_cstate_pdu(buf, NULL); + } + } + + // push header + buf->data -= sizeof(uint16_t); + buf->buf_size += sizeof(uint16_t); + +done: + free(cstate); + if (seq) + sdp_list_free(seq, free); + if (status) + return status; + + /* set attribute list byte count */ + bt_put_unaligned(htons(buf->data_size - cstate_size), (uint16_t *)buf->data); + buf->data_size += sizeof(uint16_t); + return 0; +} + +/* + * combined service search and attribute extraction + */ +static int service_search_attr_req(sdp_req_t *req, sdp_buf_t *buf) +{ + int status = 0, plen, totscanned; + uint8_t *pdata, *pResponse = NULL; + unsigned int max; + int scanned, rsp_count = 0; + sdp_list_t *pattern = NULL, *seq = NULL, *svcList; + sdp_cont_state_t *cstate = NULL; + short cstate_size = 0; + uint8_t dtd = 0; + sdp_buf_t tmpbuf; + size_t data_left = req->len; + + tmpbuf.data = NULL; + pdata = req->buf + sizeof(sdp_pdu_hdr_t); + data_left = req->len - sizeof(sdp_pdu_hdr_t); + scanned = extract_des(pdata, data_left, &pattern, &dtd, SDP_TYPE_UUID); + if (scanned == -1) { + status = SDP_INVALID_SYNTAX; + goto done; + } + totscanned = scanned; + + SDPDBG("Bytes scanned: %d", scanned); + + pdata += scanned; + data_left -= scanned; + + if (data_left < sizeof(uint16_t)) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + max = ntohs(bt_get_unaligned((uint16_t *)pdata)); + + pdata += sizeof(uint16_t); + data_left -= sizeof(uint16_t); + + SDPDBG("Max Attr expected: %d", max); + + if (data_left < sizeof(sdp_pdu_hdr_t)) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + /* extract the attribute list */ + scanned = extract_des(pdata, data_left, &seq, &dtd, SDP_TYPE_ATTRID); + if (scanned == -1) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + pdata += scanned; + data_left -= scanned; + + totscanned += scanned + sizeof(uint16_t) + 1; + + plen = ntohs(((sdp_pdu_hdr_t *)(req->buf))->plen); + if (plen < totscanned || plen != totscanned + *(uint8_t *)pdata) { + status = SDP_INVALID_PDU_SIZE; + goto done; + } + + /* + * if continuation state exists attempt + * to get rsp remainder from cache, else send error + */ + if (sdp_cstate_get(pdata, data_left, &cstate) < 0) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + svcList = sdp_get_record_list(); + + tmpbuf.data = malloc(USHRT_MAX); + tmpbuf.data_size = 0; + tmpbuf.buf_size = USHRT_MAX; + memset(tmpbuf.data, 0, USHRT_MAX); + + /* + * Calculate Attribute size acording to MTU + * We can send only (MTU - sizeof(sdp_pdu_hdr_t) - sizeof(sdp_cont_state_t)) + */ + max = MIN(max, req->mtu - sizeof(sdp_pdu_hdr_t) - SDP_CONT_STATE_SIZE - sizeof(uint16_t)); + + /* pull header for AttributeList byte count */ + buf->data += sizeof(uint16_t); + buf->buf_size -= sizeof(uint16_t); + + if (cstate == NULL) { + /* no continuation state -> create new response */ + sdp_list_t *p; + for (p = svcList; p; p = p->next) { + sdp_record_t *rec = p->data; + if (sdp_match_uuid(pattern, rec->pattern) > 0 && + sdp_check_access(rec->handle, &req->device)) { + rsp_count++; + status = extract_attrs(rec, seq, &tmpbuf); + + SDPDBG("Response count : %d", rsp_count); + SDPDBG("Local PDU size : %d", tmpbuf.data_size); + if (status) { + SDPDBG("Extract attr from record returns err"); + break; + } + if (buf->data_size + tmpbuf.data_size < buf->buf_size) { + // to be sure no relocations + sdp_append_to_buf(buf, tmpbuf.data, tmpbuf.data_size); + tmpbuf.data_size = 0; + memset(tmpbuf.data, 0, USHRT_MAX); + } else { + error("Relocation needed"); + break; + } + SDPDBG("Net PDU size : %d", buf->data_size); + } + } + if (buf->data_size > max) { + sdp_cont_state_t newState; + + memset((char *)&newState, 0, sizeof(sdp_cont_state_t)); + newState.timestamp = sdp_cstate_alloc_buf(buf); + /* + * Reset the buffer size to the maximum expected and + * set the sdp_cont_state_t + */ + buf->data_size = max; + newState.cStateValue.maxBytesSent = max; + cstate_size = sdp_set_cstate_pdu(buf, &newState); + } else + cstate_size = sdp_set_cstate_pdu(buf, NULL); + } else { + /* continuation State exists -> get from cache */ + sdp_buf_t *pCache = sdp_get_cached_rsp(cstate); + if (pCache) { + uint16_t sent = MIN(max, pCache->data_size - cstate->cStateValue.maxBytesSent); + pResponse = pCache->data; + memcpy(buf->data, pResponse + cstate->cStateValue.maxBytesSent, sent); + buf->data_size += sent; + cstate->cStateValue.maxBytesSent += sent; + if (cstate->cStateValue.maxBytesSent == pCache->data_size) + cstate_size = sdp_set_cstate_pdu(buf, NULL); + else + cstate_size = sdp_set_cstate_pdu(buf, cstate); + } else { + status = SDP_INVALID_CSTATE; + SDPDBG("Non-null continuation state, but null cache buffer"); + } + } + + if (!rsp_count && !cstate) { + // found nothing + buf->data_size = 0; + sdp_append_to_buf(buf, tmpbuf.data, tmpbuf.data_size); + sdp_set_cstate_pdu(buf, NULL); + } + + // push header + buf->data -= sizeof(uint16_t); + buf->buf_size += sizeof(uint16_t); + + if (!status) { + /* set attribute list byte count */ + bt_put_unaligned(htons(buf->data_size - cstate_size), (uint16_t *)buf->data); + buf->data_size += sizeof(uint16_t); + } + +done: + free(cstate); + free(tmpbuf.data); + if (pattern) + sdp_list_free(pattern, free); + if (seq) + sdp_list_free(seq, free); + return status; +} + +/* + * Top level request processor. Calls the appropriate processing + * function based on request type. Handles service registration + * client requests also. + */ +static void process_request(sdp_req_t *req) +{ + sdp_pdu_hdr_t *reqhdr = (sdp_pdu_hdr_t *)req->buf; + sdp_pdu_hdr_t *rsphdr; + sdp_buf_t rsp; + uint8_t *buf = malloc(USHRT_MAX); + int sent = 0; + int status = SDP_INVALID_SYNTAX; + + memset(buf, 0, USHRT_MAX); + rsp.data = buf + sizeof(sdp_pdu_hdr_t); + rsp.data_size = 0; + rsp.buf_size = USHRT_MAX - sizeof(sdp_pdu_hdr_t); + rsphdr = (sdp_pdu_hdr_t *)buf; + + if (ntohs(reqhdr->plen) != req->len - sizeof(sdp_pdu_hdr_t)) { + status = SDP_INVALID_PDU_SIZE; + goto send_rsp; + } + switch (reqhdr->pdu_id) { + case SDP_SVC_SEARCH_REQ: + SDPDBG("Got a svc srch req"); + status = service_search_req(req, &rsp); + rsphdr->pdu_id = SDP_SVC_SEARCH_RSP; + break; + case SDP_SVC_ATTR_REQ: + SDPDBG("Got a svc attr req"); + status = service_attr_req(req, &rsp); + rsphdr->pdu_id = SDP_SVC_ATTR_RSP; + break; + case SDP_SVC_SEARCH_ATTR_REQ: + SDPDBG("Got a svc srch attr req"); + status = service_search_attr_req(req, &rsp); + rsphdr->pdu_id = SDP_SVC_SEARCH_ATTR_RSP; + break; + /* Following requests are allowed only for local connections */ + case SDP_SVC_REGISTER_REQ: + SDPDBG("Service register request"); + if (req->local) { + status = service_register_req(req, &rsp); + rsphdr->pdu_id = SDP_SVC_REGISTER_RSP; + } + break; + case SDP_SVC_UPDATE_REQ: + SDPDBG("Service update request"); + if (req->local) { + status = service_update_req(req, &rsp); + rsphdr->pdu_id = SDP_SVC_UPDATE_RSP; + } + break; + case SDP_SVC_REMOVE_REQ: + SDPDBG("Service removal request"); + if (req->local) { + status = service_remove_req(req, &rsp); + rsphdr->pdu_id = SDP_SVC_REMOVE_RSP; + } + break; + default: + error("Unknown PDU ID : 0x%x received", reqhdr->pdu_id); + status = SDP_INVALID_SYNTAX; + break; + } + +send_rsp: + if (status) { + rsphdr->pdu_id = SDP_ERROR_RSP; + bt_put_unaligned(htons(status), (uint16_t *)rsp.data); + rsp.data_size = sizeof(uint16_t); + } + + SDPDBG("Sending rsp. status %d", status); + + rsphdr->tid = reqhdr->tid; + rsphdr->plen = htons(rsp.data_size); + + /* point back to the real buffer start and set the real rsp length */ + rsp.data_size += sizeof(sdp_pdu_hdr_t); + rsp.data = buf; + + /* stream the rsp PDU */ + sent = send(req->sock, rsp.data, rsp.data_size, 0); + + SDPDBG("Bytes Sent : %d", sent); + + free(rsp.data); + free(req->buf); +} + +void handle_request(int sk, uint8_t *data, int len) +{ + struct sockaddr_l2 sa; + socklen_t size; + sdp_req_t req; + + size = sizeof(sa); + if (getpeername(sk, (struct sockaddr *) &sa, &size) < 0) { + error("getpeername: %s", strerror(errno)); + return; + } + + if (sa.l2_family == AF_BLUETOOTH) { + struct l2cap_options lo; + + memset(&lo, 0, sizeof(lo)); + size = sizeof(lo); + + if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &lo, &size) < 0) { + error("getsockopt: %s", strerror(errno)); + return; + } + + bacpy(&req.bdaddr, &sa.l2_bdaddr); + req.mtu = lo.omtu; + req.local = 0; + memset(&sa, 0, sizeof(sa)); + size = sizeof(sa); + + if (getsockname(sk, (struct sockaddr *) &sa, &size) < 0) { + error("getsockname: %s", strerror(errno)); + return; + } + + bacpy(&req.device, &sa.l2_bdaddr); + } else { + bacpy(&req.device, BDADDR_ANY); + bacpy(&req.bdaddr, BDADDR_LOCAL); + req.mtu = 2048; + req.local = 1; + } + + req.sock = sk; + req.buf = data; + req.len = len; + + process_request(&req); +} diff --git a/src/sdpd-server.c b/src/sdpd-server.c new file mode 100644 index 0000000..a92ae2c --- /dev/null +++ b/src/sdpd-server.c @@ -0,0 +1,292 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2001-2002 Nokia Corporation + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * Copyright (C) 2002-2003 Stephen Crane + * + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#include "log.h" +#include "sdpd.h" + +static guint l2cap_id = 0, unix_id = 0; + +static int l2cap_sock, unix_sock; + +/* + * SDP server initialization on startup includes creating the + * l2cap and unix sockets over which discovery and registration clients + * access us respectively + */ +static int init_server(uint16_t mtu, int master, int compat) +{ + struct l2cap_options opts; + struct sockaddr_l2 l2addr; + struct sockaddr_un unaddr; + socklen_t optlen; + + /* Register the public browse group root */ + register_public_browse_group(); + + /* Register the SDP server's service record */ + register_server_service(); + + /* Create L2CAP socket */ + l2cap_sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (l2cap_sock < 0) { + error("opening L2CAP socket: %s", strerror(errno)); + return -1; + } + + memset(&l2addr, 0, sizeof(l2addr)); + l2addr.l2_family = AF_BLUETOOTH; + bacpy(&l2addr.l2_bdaddr, BDADDR_ANY); + l2addr.l2_psm = htobs(SDP_PSM); + + if (bind(l2cap_sock, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0) { + error("binding L2CAP socket: %s", strerror(errno)); + return -1; + } + + if (master) { + int opt = L2CAP_LM_MASTER; + if (setsockopt(l2cap_sock, SOL_L2CAP, L2CAP_LM, &opt, sizeof(opt)) < 0) { + error("setsockopt: %s", strerror(errno)); + return -1; + } + } + + if (mtu > 0) { + memset(&opts, 0, sizeof(opts)); + optlen = sizeof(opts); + + if (getsockopt(l2cap_sock, SOL_L2CAP, L2CAP_OPTIONS, &opts, &optlen) < 0) { + error("getsockopt: %s", strerror(errno)); + return -1; + } + + opts.omtu = mtu; + opts.imtu = mtu; + + if (setsockopt(l2cap_sock, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts)) < 0) { + error("setsockopt: %s", strerror(errno)); + return -1; + } + } + + if (listen(l2cap_sock, 5) < 0) { + error("listen: %s", strerror(errno)); + return -1; + } + + if (!compat) { + unix_sock = -1; + return 0; + } + + /* Create local Unix socket */ + unix_sock = socket(PF_UNIX, SOCK_STREAM, 0); + if (unix_sock < 0) { + error("opening UNIX socket: %s", strerror(errno)); + return -1; + } + + memset(&unaddr, 0, sizeof(unaddr)); + unaddr.sun_family = AF_UNIX; + strcpy(unaddr.sun_path, SDP_UNIX_PATH); + + unlink(unaddr.sun_path); + + if (bind(unix_sock, (struct sockaddr *) &unaddr, sizeof(unaddr)) < 0) { + error("binding UNIX socket: %s", strerror(errno)); + return -1; + } + + if (listen(unix_sock, 5) < 0) { + error("listen UNIX socket: %s", strerror(errno)); + return -1; + } + + chmod(SDP_UNIX_PATH, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + + return 0; +} + +static gboolean io_session_event(GIOChannel *chan, GIOCondition cond, gpointer data) +{ + sdp_pdu_hdr_t hdr; + uint8_t *buf; + int sk, len, size; + + if (cond & G_IO_NVAL) + return FALSE; + + sk = g_io_channel_unix_get_fd(chan); + + if (cond & (G_IO_HUP | G_IO_ERR)) { + sdp_svcdb_collect_all(sk); + return FALSE; + } + + len = recv(sk, &hdr, sizeof(sdp_pdu_hdr_t), MSG_PEEK); + if (len <= 0) { + sdp_svcdb_collect_all(sk); + return FALSE; + } + + size = sizeof(sdp_pdu_hdr_t) + ntohs(hdr.plen); + buf = malloc(size); + if (!buf) + return TRUE; + + len = recv(sk, buf, size, 0); + if (len <= 0) { + sdp_svcdb_collect_all(sk); + free(buf); + return FALSE; + } + + handle_request(sk, buf, len); + + return TRUE; +} + +static gboolean io_accept_event(GIOChannel *chan, GIOCondition cond, gpointer data) +{ + GIOChannel *io; + int nsk; + + if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) + return FALSE; + + if (data == &l2cap_sock) { + struct sockaddr_l2 addr; + socklen_t len = sizeof(addr); + + nsk = accept(l2cap_sock, (struct sockaddr *) &addr, &len); + } else if (data == &unix_sock) { + struct sockaddr_un addr; + socklen_t len = sizeof(addr); + + nsk = accept(unix_sock, (struct sockaddr *) &addr, &len); + } else + return FALSE; + + if (nsk < 0) { + error("Can't accept connection: %s", strerror(errno)); + return TRUE; + } + + io = g_io_channel_unix_new(nsk); + g_io_channel_set_close_on_unref(io, TRUE); + + g_io_add_watch(io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + io_session_event, data); + + g_io_channel_unref(io); + + return TRUE; +} + +int start_sdp_server(uint16_t mtu, const char *did, uint32_t flags) +{ + int compat = flags & SDP_SERVER_COMPAT; + int master = flags & SDP_SERVER_MASTER; + GIOChannel *io; + + info("Starting SDP server"); + + if (init_server(mtu, master, compat) < 0) { + error("Server initialization failed"); + return -1; + } + + if (did && strlen(did) > 0) { + const char *ptr = did; + uint16_t vid = 0x0000, pid = 0x0000, ver = 0x0000; + + vid = (uint16_t) strtol(ptr, NULL, 16); + ptr = strchr(ptr, ':'); + if (ptr) { + pid = (uint16_t) strtol(ptr + 1, NULL, 16); + ptr = strchr(ptr + 1, ':'); + if (ptr) + ver = (uint16_t) strtol(ptr + 1, NULL, 16); + register_device_id(vid, pid, ver); + } + } + + io = g_io_channel_unix_new(l2cap_sock); + g_io_channel_set_close_on_unref(io, TRUE); + + l2cap_id = g_io_add_watch(io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + io_accept_event, &l2cap_sock); + g_io_channel_unref(io); + + if (compat && unix_sock > fileno(stderr)) { + io = g_io_channel_unix_new(unix_sock); + g_io_channel_set_close_on_unref(io, TRUE); + + unix_id = g_io_add_watch(io, + G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + io_accept_event, &unix_sock); + g_io_channel_unref(io); + } + + return 0; +} + +void stop_sdp_server(void) +{ + info("Stopping SDP server"); + + sdp_svcdb_reset(); + + if (unix_id > 0) + g_source_remove(unix_id); + + if (l2cap_id > 0) + g_source_remove(l2cap_id); + + l2cap_id = unix_id = 0; + l2cap_sock = unix_sock = -1; +} diff --git a/src/sdpd-service.c b/src/sdpd-service.c new file mode 100644 index 0000000..0d6722a --- /dev/null +++ b/src/sdpd-service.c @@ -0,0 +1,537 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2001-2002 Nokia Corporation + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * Copyright (C) 2002-2003 Stephen Crane + * + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include "sdpd.h" +#include "log.h" +#include "adapter.h" +#include "manager.h" + +static sdp_record_t *server = NULL; + +/* + * List of version numbers supported by the SDP server. + * Add to this list when newer versions are supported. + */ +static sdp_version_t sdpVnumArray[1] = { + { 1, 0 } +}; +static const int sdpServerVnumEntries = 1; + +/* + * A simple function which returns the time of day in + * seconds. Used for updating the service db state + * attribute of the service record of the SDP server + */ +uint32_t sdp_get_time() +{ + /* + * To handle failure in gettimeofday, so an old + * value is returned and service does not fail + */ + static struct timeval tm; + + gettimeofday(&tm, NULL); + return (uint32_t) tm.tv_sec; +} + +/* + * The service database state is an attribute of the service record + * of the SDP server itself. This attribute is guaranteed to + * change if any of the contents of the service repository + * changes. This function updates the timestamp of value of + * the svcDBState attribute + * Set the SDP server DB. Simply a timestamp which is the marker + * when the DB was modified. + */ +static void update_db_timestamp(void) +{ + uint32_t dbts = sdp_get_time(); + sdp_data_t *d = sdp_data_alloc(SDP_UINT32, &dbts); + sdp_attr_replace(server, SDP_ATTR_SVCDB_STATE, d); +} + +void register_public_browse_group(void) +{ + sdp_list_t *browselist; + uuid_t bgscid, pbgid; + sdp_data_t *sdpdata; + sdp_record_t *browse = sdp_record_alloc(); + + browse->handle = SDP_SERVER_RECORD_HANDLE + 1; + + sdp_record_add(BDADDR_ANY, browse); + sdpdata = sdp_data_alloc(SDP_UINT32, &browse->handle); + sdp_attr_add(browse, SDP_ATTR_RECORD_HANDLE, sdpdata); + + sdp_uuid16_create(&bgscid, BROWSE_GRP_DESC_SVCLASS_ID); + browselist = sdp_list_append(0, &bgscid); + sdp_set_service_classes(browse, browselist); + sdp_list_free(browselist, 0); + + sdp_uuid16_create(&pbgid, PUBLIC_BROWSE_GROUP); + sdp_attr_add_new(browse, SDP_ATTR_GROUP_ID, + SDP_UUID16, &pbgid.value.uuid16); +} + +/* + * The SDP server must present its own service record to + * the service repository. This can be accessed by service + * discovery clients. This method constructs a service record + * and stores it in the repository + */ +void register_server_service(void) +{ + sdp_list_t *classIDList; + uuid_t classID; + void **versions, **versionDTDs; + uint8_t dtd; + sdp_data_t *pData; + int i; + + server = sdp_record_alloc(); + server->pattern = NULL; + + /* Force the record to be SDP_SERVER_RECORD_HANDLE */ + server->handle = SDP_SERVER_RECORD_HANDLE; + + sdp_record_add(BDADDR_ANY, server); + sdp_attr_add(server, SDP_ATTR_RECORD_HANDLE, + sdp_data_alloc(SDP_UINT32, &server->handle)); + + sdp_uuid16_create(&classID, SDP_SERVER_SVCLASS_ID); + classIDList = sdp_list_append(0, &classID); + sdp_set_service_classes(server, classIDList); + sdp_list_free(classIDList, 0); + + /* + * Set the version numbers supported, these are passed as arguments + * to the server on command line. Now defaults to 1.0 + * Build the version number sequence first + */ + versions = malloc(sdpServerVnumEntries * sizeof(void *)); + versionDTDs = malloc(sdpServerVnumEntries * sizeof(void *)); + dtd = SDP_UINT16; + for (i = 0; i < sdpServerVnumEntries; i++) { + uint16_t *version = malloc(sizeof(uint16_t)); + *version = sdpVnumArray[i].major; + *version = (*version << 8); + *version |= sdpVnumArray[i].minor; + versions[i] = version; + versionDTDs[i] = &dtd; + } + pData = sdp_seq_alloc(versionDTDs, versions, sdpServerVnumEntries); + for (i = 0; i < sdpServerVnumEntries; i++) + free(versions[i]); + free(versions); + free(versionDTDs); + sdp_attr_add(server, SDP_ATTR_VERSION_NUM_LIST, pData); + + update_db_timestamp(); +} + +void register_device_id(const uint16_t vendor, const uint16_t product, + const uint16_t version) +{ + const uint16_t spec = 0x0102, source = 0x0002; + const uint8_t primary = 1; + sdp_list_t *class_list, *group_list, *profile_list; + uuid_t class_uuid, group_uuid; + sdp_data_t *sdp_data, *primary_data, *source_data; + sdp_data_t *spec_data, *vendor_data, *product_data, *version_data; + sdp_profile_desc_t profile; + sdp_record_t *record = sdp_record_alloc(); + + info("Adding device id record for %04x:%04x", vendor, product); + + btd_manager_set_did(vendor, product, version); + + record->handle = sdp_next_handle(); + + sdp_record_add(BDADDR_ANY, record); + sdp_data = sdp_data_alloc(SDP_UINT32, &record->handle); + sdp_attr_add(record, SDP_ATTR_RECORD_HANDLE, sdp_data); + + sdp_uuid16_create(&class_uuid, PNP_INFO_SVCLASS_ID); + class_list = sdp_list_append(0, &class_uuid); + sdp_set_service_classes(record, class_list); + sdp_list_free(class_list, NULL); + + sdp_uuid16_create(&group_uuid, PUBLIC_BROWSE_GROUP); + group_list = sdp_list_append(NULL, &group_uuid); + sdp_set_browse_groups(record, group_list); + sdp_list_free(group_list, NULL); + + sdp_uuid16_create(&profile.uuid, PNP_INFO_PROFILE_ID); + profile.version = spec; + profile_list = sdp_list_append(NULL, &profile); + sdp_set_profile_descs(record, profile_list); + sdp_list_free(profile_list, NULL); + + spec_data = sdp_data_alloc(SDP_UINT16, &spec); + sdp_attr_add(record, 0x0200, spec_data); + + vendor_data = sdp_data_alloc(SDP_UINT16, &vendor); + sdp_attr_add(record, 0x0201, vendor_data); + + product_data = sdp_data_alloc(SDP_UINT16, &product); + sdp_attr_add(record, 0x0202, product_data); + + version_data = sdp_data_alloc(SDP_UINT16, &version); + sdp_attr_add(record, 0x0203, version_data); + + primary_data = sdp_data_alloc(SDP_BOOL, &primary); + sdp_attr_add(record, 0x0204, primary_data); + + source_data = sdp_data_alloc(SDP_UINT16, &source); + sdp_attr_add(record, 0x0205, source_data); + + update_db_timestamp(); +} + +int add_record_to_server(const bdaddr_t *src, sdp_record_t *rec) +{ + sdp_data_t *data; + sdp_list_t *pattern; + + if (rec->handle == 0xffffffff) { + rec->handle = sdp_next_handle(); + if (rec->handle < 0x10000) + return -ENOSPC; + } else { + if (sdp_record_find(rec->handle)) + return -EEXIST; + } + + DBG("Adding record with handle 0x%05x", rec->handle); + + sdp_record_add(src, rec); + + data = sdp_data_alloc(SDP_UINT32, &rec->handle); + sdp_attr_replace(rec, SDP_ATTR_RECORD_HANDLE, data); + + if (sdp_data_get(rec, SDP_ATTR_BROWSE_GRP_LIST) == NULL) { + uuid_t uuid; + sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP); + sdp_pattern_add_uuid(rec, &uuid); + } + + for (pattern = rec->pattern; pattern; pattern = pattern->next) { + char uuid[32]; + + if (pattern->data == NULL) + continue; + + sdp_uuid2strn((uuid_t *) pattern->data, uuid, sizeof(uuid)); + DBG("Record pattern UUID %s", uuid); + } + + update_db_timestamp(); + + return 0; +} + +int remove_record_from_server(uint32_t handle) +{ + sdp_record_t *rec; + + DBG("Removing record with handle 0x%05x", handle); + + rec = sdp_record_find(handle); + if (!rec) + return -ENOENT; + + if (sdp_record_remove(handle) == 0) + update_db_timestamp(); + + sdp_record_free(rec); + + return 0; +} + +/* FIXME: refactor for server-side */ +static sdp_record_t *extract_pdu_server(bdaddr_t *device, uint8_t *p, + unsigned int bufsize, + uint32_t handleExpected, int *scanned) +{ + int extractStatus = -1, localExtractedLength = 0; + uint8_t dtd; + int seqlen = 0; + sdp_record_t *rec = NULL; + uint16_t attrId, lookAheadAttrId; + sdp_data_t *pAttr = NULL; + uint32_t handle = 0xffffffff; + + *scanned = sdp_extract_seqtype(p, bufsize, &dtd, &seqlen); + p += *scanned; + bufsize -= *scanned; + + if (bufsize < sizeof(uint8_t) + sizeof(uint8_t)) { + SDPDBG("Unexpected end of packet"); + return NULL; + } + + lookAheadAttrId = ntohs(bt_get_unaligned((uint16_t *) (p + sizeof(uint8_t)))); + + SDPDBG("Look ahead attr id : %d", lookAheadAttrId); + + if (lookAheadAttrId == SDP_ATTR_RECORD_HANDLE) { + if (bufsize < (sizeof(uint8_t) * 2) + + sizeof(uint16_t) + sizeof(uint32_t)) { + SDPDBG("Unexpected end of packet"); + return NULL; + } + handle = ntohl(bt_get_unaligned((uint32_t *) (p + + sizeof(uint8_t) + sizeof(uint16_t) + + sizeof(uint8_t)))); + SDPDBG("SvcRecHandle : 0x%x", handle); + rec = sdp_record_find(handle); + } else if (handleExpected != 0xffffffff) + rec = sdp_record_find(handleExpected); + + if (!rec) { + rec = sdp_record_alloc(); + rec->attrlist = NULL; + if (lookAheadAttrId == SDP_ATTR_RECORD_HANDLE) { + rec->handle = handle; + sdp_record_add(device, rec); + } else if (handleExpected != 0xffffffff) { + rec->handle = handleExpected; + sdp_record_add(device, rec); + } + } else { + sdp_list_free(rec->attrlist, (sdp_free_func_t) sdp_data_free); + rec->attrlist = NULL; + } + + while (localExtractedLength < seqlen) { + int attrSize = sizeof(uint8_t); + int attrValueLength = 0; + + if (bufsize < attrSize + sizeof(uint16_t)) { + SDPDBG("Unexpected end of packet: Terminating extraction of attributes"); + break; + } + + SDPDBG("Extract PDU, sequenceLength: %d localExtractedLength: %d", + seqlen, localExtractedLength); + dtd = *(uint8_t *) p; + + attrId = ntohs(bt_get_unaligned((uint16_t *) (p + attrSize))); + attrSize += sizeof(uint16_t); + + SDPDBG("DTD of attrId : %d Attr id : 0x%x", dtd, attrId); + + pAttr = sdp_extract_attr(p + attrSize, bufsize - attrSize, + &attrValueLength, rec); + + SDPDBG("Attr id : 0x%x attrValueLength : %d", attrId, attrValueLength); + + attrSize += attrValueLength; + if (pAttr == NULL) { + SDPDBG("Terminating extraction of attributes"); + break; + } + localExtractedLength += attrSize; + p += attrSize; + bufsize -= attrSize; + sdp_attr_replace(rec, attrId, pAttr); + extractStatus = 0; + SDPDBG("Extract PDU, seqLength: %d localExtractedLength: %d", + seqlen, localExtractedLength); + } + + if (extractStatus == 0) { + SDPDBG("Successful extracting of Svc Rec attributes"); +#ifdef SDP_DEBUG + sdp_print_service_attr(rec->attrlist); +#endif + *scanned += seqlen; + } + return rec; +} + +/* + * Add the newly created service record to the service repository + */ +int service_register_req(sdp_req_t *req, sdp_buf_t *rsp) +{ + int scanned = 0; + sdp_data_t *handle; + uint8_t *p = req->buf + sizeof(sdp_pdu_hdr_t); + int bufsize = req->len - sizeof(sdp_pdu_hdr_t); + sdp_record_t *rec; + + req->flags = *p++; + if (req->flags & SDP_DEVICE_RECORD) { + bacpy(&req->device, (bdaddr_t *) p); + p += sizeof(bdaddr_t); + bufsize -= sizeof(bdaddr_t); + } + + // save image of PDU: we need it when clients request this attribute + rec = extract_pdu_server(&req->device, p, bufsize, 0xffffffff, &scanned); + if (!rec) + goto invalid; + + if (rec->handle == 0xffffffff) { + rec->handle = sdp_next_handle(); + if (rec->handle < 0x10000) { + sdp_record_free(rec); + goto invalid; + } + } else { + if (sdp_record_find(rec->handle)) { + /* extract_pdu_server will add the record handle + * if it is missing. So instead of failing, skip + * the record adding to avoid duplication. */ + goto success; + } + } + + sdp_record_add(&req->device, rec); + if (!(req->flags & SDP_RECORD_PERSIST)) + sdp_svcdb_set_collectable(rec, req->sock); + + handle = sdp_data_alloc(SDP_UINT32, &rec->handle); + sdp_attr_replace(rec, SDP_ATTR_RECORD_HANDLE, handle); + +success: + /* if the browse group descriptor is NULL, + * ensure that the record belongs to the ROOT group */ + if (sdp_data_get(rec, SDP_ATTR_BROWSE_GRP_LIST) == NULL) { + uuid_t uuid; + sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP); + sdp_pattern_add_uuid(rec, &uuid); + } + + update_db_timestamp(); + + /* Build a rsp buffer */ + bt_put_unaligned(htonl(rec->handle), (uint32_t *) rsp->data); + rsp->data_size = sizeof(uint32_t); + + return 0; + +invalid: + bt_put_unaligned(htons(SDP_INVALID_SYNTAX), (uint16_t *) rsp->data); + rsp->data_size = sizeof(uint16_t); + + return -1; +} + +/* + * Update a service record + */ +int service_update_req(sdp_req_t *req, sdp_buf_t *rsp) +{ + sdp_record_t *orec, *nrec; + int status = 0, scanned = 0; + uint8_t *p = req->buf + sizeof(sdp_pdu_hdr_t); + int bufsize = req->len - sizeof(sdp_pdu_hdr_t); + uint32_t handle = ntohl(bt_get_unaligned((uint32_t *) p)); + + SDPDBG("Svc Rec Handle: 0x%x", handle); + + p += sizeof(uint32_t); + bufsize -= sizeof(uint32_t); + + orec = sdp_record_find(handle); + + SDPDBG("SvcRecOld: %p", orec); + + if (!orec) { + status = SDP_INVALID_RECORD_HANDLE; + goto done; + } + + nrec = extract_pdu_server(BDADDR_ANY, p, bufsize, handle, &scanned); + if (!nrec) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + assert(nrec == orec); + + update_db_timestamp(); + +done: + p = rsp->data; + bt_put_unaligned(htons(status), (uint16_t *) p); + rsp->data_size = sizeof(uint16_t); + return status; +} + +/* + * Remove a registered service record + */ +int service_remove_req(sdp_req_t *req, sdp_buf_t *rsp) +{ + uint8_t *p = req->buf + sizeof(sdp_pdu_hdr_t); + uint32_t handle = ntohl(bt_get_unaligned((uint32_t *) p)); + sdp_record_t *rec; + int status = 0; + + /* extract service record handle */ + p += sizeof(uint32_t); + + rec = sdp_record_find(handle); + if (rec) { + sdp_svcdb_collect(rec); + status = sdp_record_remove(handle); + sdp_record_free(rec); + if (status == 0) + update_db_timestamp(); + } else { + status = SDP_INVALID_RECORD_HANDLE; + SDPDBG("Could not find record : 0x%x", handle); + } + + p = rsp->data; + bt_put_unaligned(htons(status), (uint16_t *) p); + rsp->data_size = sizeof(uint16_t); + + return status; +} diff --git a/src/sdpd.h b/src/sdpd.h new file mode 100644 index 0000000..dc7a256 --- /dev/null +++ b/src/sdpd.h @@ -0,0 +1,97 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2001-2002 Nokia Corporation + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * Copyright (C) 2002-2003 Stephen Crane + * + * + * 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 + * + */ + +#include +#include + +#ifdef SDP_DEBUG +#include +#define SDPDBG(fmt, arg...) syslog(LOG_DEBUG, "%s: " fmt "\n", __func__ , ## arg) +#else +#define SDPDBG(fmt...) +#endif + +#define EIR_DATA_LENGTH 240 + +#define EIR_FLAGS 0x01 /* flags */ +#define EIR_UUID16_SOME 0x02 /* 16-bit UUID, more available */ +#define EIR_UUID16_ALL 0x03 /* 16-bit UUID, all listed */ +#define EIR_UUID32_SOME 0x04 /* 32-bit UUID, more available */ +#define EIR_UUID32_ALL 0x05 /* 32-bit UUID, all listed */ +#define EIR_UUID128_SOME 0x06 /* 128-bit UUID, more available */ +#define EIR_UUID128_ALL 0x07 /* 128-bit UUID, all listed */ +#define EIR_NAME_SHORT 0x08 /* shortened local name */ +#define EIR_NAME_COMPLETE 0x09 /* complete local name */ +#define EIR_TX_POWER 0x0A /* transmit power level */ +#define EIR_DEVICE_ID 0x10 /* device ID */ + +typedef struct request { + bdaddr_t device; + bdaddr_t bdaddr; + int local; + int sock; + int mtu; + int flags; + uint8_t *buf; + int len; +} sdp_req_t; + +void handle_request(int sk, uint8_t *data, int len); + +int service_register_req(sdp_req_t *req, sdp_buf_t *rsp); +int service_update_req(sdp_req_t *req, sdp_buf_t *rsp); +int service_remove_req(sdp_req_t *req, sdp_buf_t *rsp); + +void register_public_browse_group(void); +void register_server_service(void); +void register_device_id(const uint16_t vendor, const uint16_t product, + const uint16_t version); + +int record_sort(const void *r1, const void *r2); +void sdp_svcdb_reset(void); +void sdp_svcdb_collect_all(int sock); +void sdp_svcdb_set_collectable(sdp_record_t *rec, int sock); +void sdp_svcdb_collect(sdp_record_t *rec); +sdp_record_t *sdp_record_find(uint32_t handle); +void sdp_record_add(const bdaddr_t *device, sdp_record_t *rec); +int sdp_record_remove(uint32_t handle); +sdp_list_t *sdp_get_record_list(void); +sdp_list_t *sdp_get_access_list(void); +int sdp_check_access(uint32_t handle, bdaddr_t *device); +uint32_t sdp_next_handle(void); + +uint32_t sdp_get_time(); + +#define SDP_SERVER_COMPAT (1 << 0) +#define SDP_SERVER_MASTER (1 << 1) + +int start_sdp_server(uint16_t mtu, const char *did, uint32_t flags); +void stop_sdp_server(void); + +int add_record_to_server(const bdaddr_t *src, sdp_record_t *rec); +int remove_record_from_server(uint32_t handle); + +void sdp_init_services_list(bdaddr_t *device); diff --git a/src/storage.c b/src/storage.c new file mode 100644 index 0000000..28aea30 --- /dev/null +++ b/src/storage.c @@ -0,0 +1,1345 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "textfile.h" +#include "adapter.h" +#include "device.h" +#include "glib-helper.h" +#include "storage.h" + +struct match { + GSList *keys; + char *pattern; +}; + +static inline int create_filename(char *buf, size_t size, + const bdaddr_t *bdaddr, const char *name) +{ + char addr[18]; + + ba2str(bdaddr, addr); + + return create_name(buf, size, STORAGEDIR, addr, name); +} + +int read_device_alias(const char *src, const char *dst, char *alias, size_t size) +{ + char filename[PATH_MAX + 1], *tmp; + int err; + + create_name(filename, PATH_MAX, STORAGEDIR, src, "aliases"); + + tmp = textfile_get(filename, dst); + if (!tmp) + return -ENXIO; + + err = snprintf(alias, size, "%s", tmp); + + free(tmp); + + return err < 0 ? -EIO : 0; +} + +int write_device_alias(const char *src, const char *dst, const char *alias) +{ + char filename[PATH_MAX + 1]; + + create_name(filename, PATH_MAX, STORAGEDIR, src, "aliases"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + return textfile_put(filename, dst, alias); +} + +int write_discoverable_timeout(bdaddr_t *bdaddr, int timeout) +{ + char filename[PATH_MAX + 1], str[32]; + + snprintf(str, sizeof(str), "%d", timeout); + + create_filename(filename, PATH_MAX, bdaddr, "config"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + return textfile_put(filename, "discovto", str); +} + +int read_discoverable_timeout(const char *src, int *timeout) +{ + char filename[PATH_MAX + 1], *str; + + create_name(filename, PATH_MAX, STORAGEDIR, src, "config"); + + str = textfile_get(filename, "discovto"); + if (!str) + return -ENOENT; + + if (sscanf(str, "%d", timeout) != 1) { + free(str); + return -ENOENT; + } + + free(str); + + return 0; +} + +int write_pairable_timeout(bdaddr_t *bdaddr, int timeout) +{ + char filename[PATH_MAX + 1], str[32]; + + snprintf(str, sizeof(str), "%d", timeout); + + create_filename(filename, PATH_MAX, bdaddr, "config"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + return textfile_put(filename, "pairto", str); +} + +int read_pairable_timeout(const char *src, int *timeout) +{ + char filename[PATH_MAX + 1], *str; + + create_name(filename, PATH_MAX, STORAGEDIR, src, "config"); + + str = textfile_get(filename, "pairto"); + if (!str) + return -ENOENT; + + if (sscanf(str, "%d", timeout) != 1) { + free(str); + return -ENOENT; + } + + free(str); + + return 0; +} + +int write_device_mode(bdaddr_t *bdaddr, const char *mode) +{ + char filename[PATH_MAX + 1]; + + create_filename(filename, PATH_MAX, bdaddr, "config"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + if (strcmp(mode, "off") != 0) + textfile_put(filename, "onmode", mode); + + return textfile_put(filename, "mode", mode); +} + +int read_device_mode(const char *src, char *mode, int length) +{ + char filename[PATH_MAX + 1], *str; + + create_name(filename, PATH_MAX, STORAGEDIR, src, "config"); + + str = textfile_get(filename, "mode"); + if (!str) + return -ENOENT; + + strncpy(mode, str, length); + mode[length - 1] = '\0'; + + free(str); + + return 0; +} + +int read_on_mode(const char *src, char *mode, int length) +{ + char filename[PATH_MAX + 1], *str; + + create_name(filename, PATH_MAX, STORAGEDIR, src, "config"); + + str = textfile_get(filename, "onmode"); + if (!str) + return -ENOENT; + + strncpy(mode, str, length); + mode[length - 1] = '\0'; + + free(str); + + return 0; +} + +int write_local_name(bdaddr_t *bdaddr, const char *name) +{ + char filename[PATH_MAX + 1], str[249]; + int i; + + memset(str, 0, sizeof(str)); + for (i = 0; i < 248 && name[i]; i++) + if ((unsigned char) name[i] < 32 || name[i] == 127) + str[i] = '.'; + else + str[i] = name[i]; + + create_filename(filename, PATH_MAX, bdaddr, "config"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + return textfile_put(filename, "name", str); +} + +int read_local_name(bdaddr_t *bdaddr, char *name) +{ + char filename[PATH_MAX + 1], *str; + int len; + + create_filename(filename, PATH_MAX, bdaddr, "config"); + + str = textfile_get(filename, "name"); + if (!str) + return -ENOENT; + + len = strlen(str); + if (len > 248) + str[248] = '\0'; + strcpy(name, str); + + free(str); + + return 0; +} + +int write_local_class(bdaddr_t *bdaddr, uint8_t *class) +{ + char filename[PATH_MAX + 1], str[9]; + + sprintf(str, "0x%2.2x%2.2x%2.2x", class[2], class[1], class[0]); + + create_filename(filename, PATH_MAX, bdaddr, "config"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + return textfile_put(filename, "class", str); +} + +int read_local_class(bdaddr_t *bdaddr, uint8_t *class) +{ + char filename[PATH_MAX + 1], tmp[3], *str; + int i; + + create_filename(filename, PATH_MAX, bdaddr, "config"); + + str = textfile_get(filename, "class"); + if (!str) + return -ENOENT; + + memset(tmp, 0, sizeof(tmp)); + for (i = 0; i < 3; i++) { + memcpy(tmp, str + (i * 2) + 2, 2); + class[2 - i] = (uint8_t) strtol(tmp, NULL, 16); + } + + free(str); + + return 0; +} + +int write_remote_class(bdaddr_t *local, bdaddr_t *peer, uint32_t class) +{ + char filename[PATH_MAX + 1], addr[18], str[9]; + + create_filename(filename, PATH_MAX, local, "classes"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(peer, addr); + sprintf(str, "0x%6.6x", class); + + return textfile_put(filename, addr, str); +} + +int read_remote_class(bdaddr_t *local, bdaddr_t *peer, uint32_t *class) +{ + char filename[PATH_MAX + 1], addr[18], *str; + + create_filename(filename, PATH_MAX, local, "classes"); + + ba2str(peer, addr); + + str = textfile_get(filename, addr); + if (!str) + return -ENOENT; + + if (sscanf(str, "%x", class) != 1) { + free(str); + return -ENOENT; + } + + free(str); + + return 0; +} + +int write_device_name(bdaddr_t *local, bdaddr_t *peer, char *name) +{ + char filename[PATH_MAX + 1], addr[18], str[249]; + int i; + + memset(str, 0, sizeof(str)); + for (i = 0; i < 248 && name[i]; i++) + if ((unsigned char) name[i] < 32 || name[i] == 127) + str[i] = '.'; + else + str[i] = name[i]; + + create_filename(filename, PATH_MAX, local, "names"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(peer, addr); + return textfile_put(filename, addr, str); +} + +int read_device_name(const char *src, const char *dst, char *name) +{ + char filename[PATH_MAX + 1], *str; + int len; + + create_name(filename, PATH_MAX, STORAGEDIR, src, "names"); + + str = textfile_get(filename, dst); + if (!str) + return -ENOENT; + + len = strlen(str); + if (len > 248) + str[248] = '\0'; + strcpy(name, str); + + free(str); + + return 0; +} + +int write_remote_eir(bdaddr_t *local, bdaddr_t *peer, uint8_t *data) +{ + char filename[PATH_MAX + 1], addr[18], str[481]; + int i; + + memset(str, 0, sizeof(str)); + for (i = 0; i < 240; i++) + sprintf(str + (i * 2), "%2.2X", data[i]); + + create_filename(filename, PATH_MAX, local, "eir"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(peer, addr); + return textfile_put(filename, addr, str); +} + +int read_remote_eir(bdaddr_t *local, bdaddr_t *peer, uint8_t *data) +{ + char filename[PATH_MAX + 1], addr[18], *str; + int i; + + create_filename(filename, PATH_MAX, local, "eir"); + + ba2str(peer, addr); + + str = textfile_get(filename, addr); + if (!str) + return -ENOENT; + + if (!data) { + free(str); + return 0; + } + + if (strlen(str) < 480) { + free(str); + return -EIO; + } + + for (i = 0; i < 240; i++) + sscanf(str + (i * 2), "%02hhX", &data[i]); + + free(str); + + return 0; +} + +int write_version_info(bdaddr_t *local, bdaddr_t *peer, uint16_t manufacturer, + uint8_t lmp_ver, uint16_t lmp_subver) +{ + char filename[PATH_MAX + 1], addr[18], str[16]; + + memset(str, 0, sizeof(str)); + sprintf(str, "%d %d %d", manufacturer, lmp_ver, lmp_subver); + + create_filename(filename, PATH_MAX, local, "manufacturers"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(peer, addr); + return textfile_put(filename, addr, str); +} + +int write_features_info(bdaddr_t *local, bdaddr_t *peer, + unsigned char *page1, unsigned char *page2) +{ + char filename[PATH_MAX + 1], addr[18]; + char str[] = "0000000000000000 0000000000000000"; + char *old_value; + int i; + + ba2str(peer, addr); + + create_filename(filename, PATH_MAX, local, "features"); + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + old_value = textfile_get(filename, addr); + + if (page1) + for (i = 0; i < 8; i++) + sprintf(str + (i * 2), "%2.2X", page1[i]); + else if (old_value && strlen(old_value) >= 16) + strncpy(str, old_value, 16); + + if (page2) + for (i = 0; i < 8; i++) + sprintf(str + 17 + (i * 2), "%2.2X", page2[i]); + else if (old_value && strlen(old_value) >= 33) + strncpy(str + 17, old_value + 17, 16); + + free(old_value); + + return textfile_put(filename, addr, str); +} + +static int decode_bytes(const char *str, unsigned char *bytes, size_t len) +{ + unsigned int i; + + for (i = 0; i < len; i++) { + if (sscanf(str + (i * 2), "%02hhX", &bytes[i]) != 1) + return -EINVAL; + } + + return 0; +} + +int read_remote_features(bdaddr_t *local, bdaddr_t *peer, + unsigned char *page1, unsigned char *page2) +{ + char filename[PATH_MAX + 1], addr[18], *str; + size_t len; + int err; + + if (page1 == NULL && page2 == NULL) + return -EINVAL; + + create_filename(filename, PATH_MAX, local, "features"); + + ba2str(peer, addr); + + str = textfile_get(filename, addr); + if (!str) + return -ENOENT; + + len = strlen(str); + + err = -ENOENT; + + if (page1 && len >= 16) + err = decode_bytes(str, page1, 8); + + if (page2 && len >= 33) + err = decode_bytes(str + 17, page2, 8); + + free(str); + + return err; +} + +int write_lastseen_info(bdaddr_t *local, bdaddr_t *peer, struct tm *tm) +{ + char filename[PATH_MAX + 1], addr[18], str[24]; + + memset(str, 0, sizeof(str)); + strftime(str, sizeof(str), "%Y-%m-%d %H:%M:%S %Z", tm); + + create_filename(filename, PATH_MAX, local, "lastseen"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(peer, addr); + return textfile_put(filename, addr, str); +} + +int write_lastused_info(bdaddr_t *local, bdaddr_t *peer, struct tm *tm) +{ + char filename[PATH_MAX + 1], addr[18], str[24]; + + memset(str, 0, sizeof(str)); + strftime(str, sizeof(str), "%Y-%m-%d %H:%M:%S %Z", tm); + + create_filename(filename, PATH_MAX, local, "lastused"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(peer, addr); + return textfile_put(filename, addr, str); +} + +int write_link_key(bdaddr_t *local, bdaddr_t *peer, unsigned char *key, uint8_t type, int length) +{ + char filename[PATH_MAX + 1], addr[18], str[38]; + int i; + + memset(str, 0, sizeof(str)); + for (i = 0; i < 16; i++) + sprintf(str + (i * 2), "%2.2X", key[i]); + sprintf(str + 32, " %d %d", type, length); + + create_filename(filename, PATH_MAX, local, "linkkeys"); + + create_file(filename, S_IRUSR | S_IWUSR); + + ba2str(peer, addr); + + if (length < 0) { + char *tmp = textfile_get(filename, addr); + if (tmp) { + if (strlen(tmp) > 34) + memcpy(str + 34, tmp + 34, 3); + free(tmp); + } + } + + return textfile_put(filename, addr, str); +} + +int read_link_key(bdaddr_t *local, bdaddr_t *peer, unsigned char *key, uint8_t *type) +{ + char filename[PATH_MAX + 1], addr[18], tmp[3], *str; + int i; + + create_filename(filename, PATH_MAX, local, "linkkeys"); + + ba2str(peer, addr); + str = textfile_get(filename, addr); + if (!str) + return -ENOENT; + + if (!key) { + free(str); + return 0; + } + + memset(tmp, 0, sizeof(tmp)); + for (i = 0; i < 16; i++) { + memcpy(tmp, str + (i * 2), 2); + key[i] = (uint8_t) strtol(tmp, NULL, 16); + } + + if (type) { + memcpy(tmp, str + 33, 2); + *type = (uint8_t) strtol(tmp, NULL, 10); + } + + free(str); + + return 0; +} + +int read_pin_code(bdaddr_t *local, bdaddr_t *peer, char *pin) +{ + char filename[PATH_MAX + 1], addr[18], *str; + int len; + + create_filename(filename, PATH_MAX, local, "pincodes"); + + ba2str(peer, addr); + str = textfile_get(filename, addr); + if (!str) + return -ENOENT; + + strncpy(pin, str, 16); + len = strlen(pin); + + free(str); + + return len; +} + +static GSList *service_string_to_list(char *services) +{ + GSList *l = NULL; + char *start = services; + int i, finished = 0; + + for (i = 0; !finished; i++) { + if (services[i] == '\0') + finished = 1; + + if (services[i] == ' ' || services[i] == '\0') { + services[i] = '\0'; + l = g_slist_append(l, start); + start = services + i + 1; + } + } + + return l; +} + +static char *service_list_to_string(GSList *services) +{ + char str[1024]; + int len = 0; + + if (!services) + return g_strdup(""); + + memset(str, 0, sizeof(str)); + + while (services) { + int ret; + char *ident = services->data; + + ret = snprintf(str + len, sizeof(str) - len - 1, "%s%s", + ident, services->next ? " " : ""); + + if (ret > 0) + len += ret; + + services = services->next; + } + + return g_strdup(str); +} + +int write_trust(const char *src, const char *addr, const char *service, + gboolean trust) +{ + char filename[PATH_MAX + 1], *str; + GSList *services = NULL, *match; + gboolean trusted; + int ret; + + create_name(filename, PATH_MAX, STORAGEDIR, src, "trusts"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + str = textfile_caseget(filename, addr); + if (str) + services = service_string_to_list(str); + + match = g_slist_find_custom(services, service, (GCompareFunc) strcmp); + trusted = match ? TRUE : FALSE; + + /* If the old setting is the same as the requested one, we're done */ + if (trusted == trust) { + g_slist_free(services); + free(str); + return 0; + } + + if (trust) + services = g_slist_append(services, (void *) service); + else + services = g_slist_remove(services, match->data); + + /* Remove the entry if the last trusted service was removed */ + if (!trust && !services) + ret = textfile_casedel(filename, addr); + else { + char *new_str = service_list_to_string(services); + ret = textfile_caseput(filename, addr, new_str); + free(new_str); + } + + g_slist_free(services); + + free(str); + + return ret; +} + +gboolean read_trust(const bdaddr_t *local, const char *addr, const char *service) +{ + char filename[PATH_MAX + 1], *str; + GSList *services; + gboolean ret; + + create_filename(filename, PATH_MAX, local, "trusts"); + + str = textfile_caseget(filename, addr); + if (!str) + return FALSE; + + services = service_string_to_list(str); + + if (g_slist_find_custom(services, service, (GCompareFunc) strcmp)) + ret = TRUE; + else + ret = FALSE; + + g_slist_free(services); + free(str); + + return ret; +} + +struct trust_list { + GSList *trusts; + const char *service; +}; + +static void append_trust(char *key, char *value, void *data) +{ + struct trust_list *list = data; + + if (strstr(value, list->service)) + list->trusts = g_slist_append(list->trusts, g_strdup(key)); +} + +GSList *list_trusts(bdaddr_t *local, const char *service) +{ + char filename[PATH_MAX + 1]; + struct trust_list list; + + create_filename(filename, PATH_MAX, local, "trusts"); + + list.trusts = NULL; + list.service = service; + + if (textfile_foreach(filename, append_trust, &list) < 0) + return NULL; + + return list.trusts; +} + +int write_device_profiles(bdaddr_t *src, bdaddr_t *dst, const char *profiles) +{ + char filename[PATH_MAX + 1], addr[18]; + + if (!profiles) + return -EINVAL; + + create_filename(filename, PATH_MAX, src, "profiles"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(dst, addr); + return textfile_put(filename, addr, profiles); +} + +int delete_entry(bdaddr_t *src, const char *storage, const char *key) +{ + char filename[PATH_MAX + 1]; + + create_filename(filename, PATH_MAX, src, storage); + + return textfile_del(filename, key); +} + +int store_record(const gchar *src, const gchar *dst, sdp_record_t *rec) +{ + char filename[PATH_MAX + 1], key[28]; + sdp_buf_t buf; + int err, size, i; + char *str; + + create_name(filename, PATH_MAX, STORAGEDIR, src, "sdp"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + snprintf(key, sizeof(key), "%17s#%08X", dst, rec->handle); + + if (sdp_gen_record_pdu(rec, &buf) < 0) + return -1; + + size = buf.data_size; + + str = g_malloc0(size*2+1); + + for (i = 0; i < size; i++) + sprintf(str + (i * 2), "%02X", buf.data[i]); + + err = textfile_put(filename, key, str); + + free(buf.data); + free(str); + + return err; +} + +sdp_record_t *record_from_string(const gchar *str) +{ + sdp_record_t *rec; + int size, i, len; + uint8_t *pdata; + char tmp[3]; + + size = strlen(str)/2; + pdata = g_malloc0(size); + + tmp[2] = 0; + for (i = 0; i < size; i++) { + memcpy(tmp, str + (i * 2), 2); + pdata[i] = (uint8_t) strtol(tmp, NULL, 16); + } + + rec = sdp_extract_pdu(pdata, size, &len); + free(pdata); + + return rec; +} + + +sdp_record_t *fetch_record(const gchar *src, const gchar *dst, + const uint32_t handle) +{ + char filename[PATH_MAX + 1], key[28], *str; + sdp_record_t *rec; + + create_name(filename, PATH_MAX, STORAGEDIR, src, "sdp"); + + snprintf(key, sizeof(key), "%17s#%08X", dst, handle); + + str = textfile_get(filename, key); + if (!str) + return NULL; + + rec = record_from_string(str); + free(str); + + return rec; +} + +int delete_record(const gchar *src, const gchar *dst, const uint32_t handle) +{ + char filename[PATH_MAX + 1], key[28]; + + create_name(filename, PATH_MAX, STORAGEDIR, src, "sdp"); + + snprintf(key, sizeof(key), "%17s#%08X", dst, handle); + + return textfile_del(filename, key); +} + +struct record_list { + sdp_list_t *recs; + const gchar *addr; +}; + +static void create_stored_records_from_keys(char *key, char *value, + void *user_data) +{ + struct record_list *rec_list = user_data; + const gchar *addr = rec_list->addr; + sdp_record_t *rec; + + if (strncmp(key, addr, 17)) + return; + + rec = record_from_string(value); + + rec_list->recs = sdp_list_append(rec_list->recs, rec); +} + +void delete_all_records(const bdaddr_t *src, const bdaddr_t *dst) +{ + sdp_list_t *records, *seq; + char srcaddr[18], dstaddr[18]; + + ba2str(src, srcaddr); + ba2str(dst, dstaddr); + + records = read_records(src, dst); + + for (seq = records; seq; seq = seq->next) { + sdp_record_t *rec = seq->data; + delete_record(srcaddr, dstaddr, rec->handle); + } + + if (records) + sdp_list_free(records, (sdp_free_func_t) sdp_record_free); +} + +sdp_list_t *read_records(const bdaddr_t *src, const bdaddr_t *dst) +{ + char filename[PATH_MAX + 1]; + struct record_list rec_list; + char srcaddr[18], dstaddr[18]; + + ba2str(src, srcaddr); + ba2str(dst, dstaddr); + + rec_list.addr = dstaddr; + rec_list.recs = NULL; + + create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "sdp"); + textfile_foreach(filename, create_stored_records_from_keys, &rec_list); + + return rec_list.recs; +} + +sdp_record_t *find_record_in_list(sdp_list_t *recs, const char *uuid) +{ + sdp_list_t *seq; + + for (seq = recs; seq; seq = seq->next) { + sdp_record_t *rec = (sdp_record_t *) seq->data; + sdp_list_t *svcclass = NULL; + char *uuid_str; + + if (sdp_get_service_classes(rec, &svcclass) < 0) + continue; + + /* Extract the uuid */ + uuid_str = bt_uuid2string(svcclass->data); + if (!uuid_str) + continue; + + if (!strcasecmp(uuid_str, uuid)) { + sdp_list_free(svcclass, free); + free(uuid_str); + return rec; + } + + sdp_list_free(svcclass, free); + free(uuid_str); + } + return NULL; +} + +int store_device_id(const gchar *src, const gchar *dst, + const uint16_t source, const uint16_t vendor, + const uint16_t product, const uint16_t version) +{ + char filename[PATH_MAX + 1], str[20]; + + create_name(filename, PATH_MAX, STORAGEDIR, src, "did"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + snprintf(str, sizeof(str), "%04X %04X %04X %04X", source, + vendor, product, version); + + return textfile_put(filename, dst, str); +} + +static int read_device_id_from_did(const gchar *src, const gchar *dst, + uint16_t *source, uint16_t *vendor, + uint16_t *product, uint16_t *version) +{ + char filename[PATH_MAX + 1]; + char *str, *vendor_str, *product_str, *version_str; + + create_name(filename, PATH_MAX, STORAGEDIR, src, "did"); + + str = textfile_get(filename, dst); + if (!str) + return -ENOENT; + + vendor_str = strchr(str, ' '); + if (!vendor_str) { + free(str); + return -ENOENT; + } + *(vendor_str++) = 0; + + product_str = strchr(vendor_str, ' '); + if (!product_str) { + free(str); + return -ENOENT; + } + *(product_str++) = 0; + + version_str = strchr(product_str, ' '); + if (!version_str) { + free(str); + return -ENOENT; + } + *(version_str++) = 0; + + if (source) + *source = (uint16_t) strtol(str, NULL, 16); + if (vendor) + *vendor = (uint16_t) strtol(vendor_str, NULL, 16); + if (product) + *product = (uint16_t) strtol(product_str, NULL, 16); + if (version) + *version = (uint16_t) strtol(version_str, NULL, 16); + + free(str); + + return 0; +} + +int read_device_id(const gchar *srcaddr, const gchar *dstaddr, + uint16_t *source, uint16_t *vendor, + uint16_t *product, uint16_t *version) +{ + uint16_t lsource, lvendor, lproduct, lversion; + sdp_list_t *recs; + sdp_record_t *rec; + bdaddr_t src, dst; + int err; + + err = read_device_id_from_did(srcaddr, dstaddr, &lsource, + vendor, product, version); + if (!err) { + if (lsource == 0xffff) + err = -ENOENT; + + return err; + } + + str2ba(srcaddr, &src); + str2ba(dstaddr, &dst); + + recs = read_records(&src, &dst); + rec = find_record_in_list(recs, PNP_UUID); + + if (rec) { + sdp_data_t *pdlist; + + pdlist = sdp_data_get(rec, SDP_ATTR_VENDOR_ID_SOURCE); + lsource = pdlist ? pdlist->val.uint16 : 0x0000; + + pdlist = sdp_data_get(rec, SDP_ATTR_VENDOR_ID); + lvendor = pdlist ? pdlist->val.uint16 : 0x0000; + + pdlist = sdp_data_get(rec, SDP_ATTR_PRODUCT_ID); + lproduct = pdlist ? pdlist->val.uint16 : 0x0000; + + pdlist = sdp_data_get(rec, SDP_ATTR_VERSION); + lversion = pdlist ? pdlist->val.uint16 : 0x0000; + + err = 0; + } + + sdp_list_free(recs, (sdp_free_func_t)sdp_record_free); + + if (err) { + /* FIXME: We should try EIR data if we have it, too */ + + /* If we don't have the data, we don't want to go through the + * above search every time. */ + lsource = 0xffff; + lvendor = 0x0000; + lproduct = 0x0000; + lversion = 0x0000; + } + + store_device_id(srcaddr, dstaddr, lsource, lvendor, lproduct, lversion); + + if (err) + return err; + + if (source) + *source = lsource; + if (vendor) + *vendor = lvendor; + if (product) + *product = lproduct; + if (version) + *version = lversion; + + return 0; +} + +int write_device_pairable(bdaddr_t *bdaddr, gboolean mode) +{ + char filename[PATH_MAX + 1]; + + create_filename(filename, PATH_MAX, bdaddr, "config"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + return textfile_put(filename, "pairable", mode ? "yes" : "no"); +} + +int read_device_pairable(bdaddr_t *bdaddr, gboolean *mode) +{ + char filename[PATH_MAX + 1], *str; + + create_filename(filename, PATH_MAX, bdaddr, "config"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + str = textfile_get(filename, "pairable"); + if (!str) + return -ENOENT; + + *mode = strcmp(str, "yes") == 0 ? TRUE : FALSE; + + free(str); + + return 0; +} + +gboolean read_blocked(const bdaddr_t *local, const bdaddr_t *remote) +{ + char filename[PATH_MAX + 1], *str, addr[18]; + + create_filename(filename, PATH_MAX, local, "blocked"); + + ba2str(remote, addr); + + str = textfile_caseget(filename, addr); + if (!str) + return FALSE; + + free(str); + + return TRUE; +} + +int write_blocked(const bdaddr_t *local, const bdaddr_t *remote, + gboolean blocked) +{ + char filename[PATH_MAX + 1], addr[18]; + + create_filename(filename, PATH_MAX, local, "blocked"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(remote, addr); + + if (blocked == FALSE) + return textfile_casedel(filename, addr); + + return textfile_caseput(filename, addr, ""); +} + +int write_device_services(const bdaddr_t *sba, const bdaddr_t *dba, + const char *services) +{ + char filename[PATH_MAX + 1], addr[18]; + + create_filename(filename, PATH_MAX, sba, "primary"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(dba, addr); + + return textfile_put(filename, addr, services); +} + +static void filter_keys(char *key, char *value, void *data) +{ + struct match *match = data; + const char *address = match->pattern; + + /* Each key contains: MAC#handle*/ + if (strncasecmp(key, address, 17) == 0) + match->keys = g_slist_append(match->keys, g_strdup(key)); +} + +int delete_device_service(const bdaddr_t *sba, const bdaddr_t *dba) +{ + GSList *l; + struct match match; + char filename[PATH_MAX + 1], address[18]; + int err; + + create_filename(filename, PATH_MAX, sba, "primary"); + + memset(address, 0, sizeof(address)); + ba2str(dba, address); + + err = textfile_del(filename, address); + if (err < 0) + return err; + + /* Deleting all characteristics of a given address */ + memset(&match, 0, sizeof(match)); + match.pattern = address; + + create_filename(filename, PATH_MAX, sba, "characteristic"); + err = textfile_foreach(filename, filter_keys, &match); + if (err < 0) + return err; + + for (l = match.keys; l; l = l->next) { + const char *key = l->data; + textfile_del(filename, key); + } + + g_slist_foreach(match.keys, (GFunc) g_free, NULL); + g_slist_free(match.keys); + + /* Deleting all attributes values of a given address */ + memset(&match, 0, sizeof(match)); + match.pattern = address; + + create_filename(filename, PATH_MAX, sba, "attributes"); + err = textfile_foreach(filename, filter_keys, &match); + if (err < 0) + return err; + + for (l = match.keys; l; l = l->next) { + const char *key = l->data; + textfile_del(filename, key); + } + + g_slist_foreach(match.keys, (GFunc) g_free, NULL); + g_slist_free(match.keys); + + return 0; +} + +char *read_device_services(const bdaddr_t *sba, const bdaddr_t *dba) +{ + char filename[PATH_MAX + 1], addr[18]; + + create_filename(filename, PATH_MAX, sba, "primary"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(dba, addr); + + return textfile_caseget(filename, addr); +} + +int write_device_characteristics(const bdaddr_t *sba, const bdaddr_t *dba, + uint16_t handle, const char *chars) +{ + char filename[PATH_MAX + 1], addr[18], key[23]; + + create_filename(filename, PATH_MAX, sba, "characteristic"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(dba, addr); + + snprintf(key, sizeof(key), "%17s#%04X", addr, handle); + + return textfile_put(filename, key, chars); +} + +char *read_device_characteristics(const bdaddr_t *sba, const bdaddr_t *dba, + uint16_t handle) +{ + char filename[PATH_MAX + 1], addr[18], key[23]; + + create_filename(filename, PATH_MAX, sba, "characteristic"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(dba, addr); + + snprintf(key, sizeof(key), "%17s#%04X", addr, handle); + + return textfile_caseget(filename, key); +} + +int write_device_attribute(const bdaddr_t *sba, const bdaddr_t *dba, + uint16_t handle, const char *chars) +{ + char filename[PATH_MAX + 1], addr[18], key[23]; + + create_filename(filename, PATH_MAX, sba, "attributes"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(dba, addr); + + snprintf(key, sizeof(key), "%17s#%04X", addr, handle); + + return textfile_put(filename, key, chars); +} + +int read_device_attributes(const bdaddr_t *sba, textfile_cb func, void *data) +{ + char filename[PATH_MAX + 1]; + + create_filename(filename, PATH_MAX, sba, "attributes"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + return textfile_foreach(filename, func, data); +} + +int write_device_type(const bdaddr_t *sba, const bdaddr_t *dba, + device_type_t type) +{ + char filename[PATH_MAX + 1], addr[18], chars[3]; + + create_filename(filename, PATH_MAX, sba, "types"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(dba, addr); + + snprintf(chars, sizeof(chars), "%2.2X", type); + + return textfile_put(filename, addr, chars); +} + +device_type_t read_device_type(const bdaddr_t *sba, const bdaddr_t *dba) +{ + char filename[PATH_MAX + 1], addr[18], *chars; + device_type_t type; + + create_filename(filename, PATH_MAX, sba, "types"); + + create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + + ba2str(dba, addr); + + chars = textfile_caseget(filename, addr); + if (chars == NULL) + return DEVICE_TYPE_UNKNOWN; + + type = strtol(chars, NULL, 16); + + free(chars); + + return type; +} diff --git a/src/storage.h b/src/storage.h new file mode 100644 index 0000000..6929ada --- /dev/null +++ b/src/storage.h @@ -0,0 +1,92 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * 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 + * + */ + +#include "textfile.h" + +int read_device_alias(const char *src, const char *dst, char *alias, size_t size); +int write_device_alias(const char *src, const char *dst, const char *alias); +int write_discoverable_timeout(bdaddr_t *bdaddr, int timeout); +int read_discoverable_timeout(const char *src, int *timeout); +int write_pairable_timeout(bdaddr_t *bdaddr, int timeout); +int read_pairable_timeout(const char *src, int *timeout); +int write_device_mode(bdaddr_t *bdaddr, const char *mode); +int read_device_mode(const char *src, char *mode, int length); +int read_on_mode(const char *src, char *mode, int length); +int write_local_name(bdaddr_t *bdaddr, const char *name); +int read_local_name(bdaddr_t *bdaddr, char *name); +int write_local_class(bdaddr_t *bdaddr, uint8_t *class); +int read_local_class(bdaddr_t *bdaddr, uint8_t *class); +int write_remote_class(bdaddr_t *local, bdaddr_t *peer, uint32_t class); +int read_remote_class(bdaddr_t *local, bdaddr_t *peer, uint32_t *class); +int write_device_name(bdaddr_t *local, bdaddr_t *peer, char *name); +int read_device_name(const char *src, const char *dst, char *name); +int write_remote_eir(bdaddr_t *local, bdaddr_t *peer, uint8_t *data); +int read_remote_eir(bdaddr_t *local, bdaddr_t *peer, uint8_t *data); +int write_version_info(bdaddr_t *local, bdaddr_t *peer, uint16_t manufacturer, uint8_t lmp_ver, uint16_t lmp_subver); +int write_features_info(bdaddr_t *local, bdaddr_t *peer, unsigned char *page1, unsigned char *page2); +int read_remote_features(bdaddr_t *local, bdaddr_t *peer, unsigned char *page1, unsigned char *page2); +int write_lastseen_info(bdaddr_t *local, bdaddr_t *peer, struct tm *tm); +int write_lastused_info(bdaddr_t *local, bdaddr_t *peer, struct tm *tm); +int write_link_key(bdaddr_t *local, bdaddr_t *peer, unsigned char *key, uint8_t type, int length); +int read_link_key(bdaddr_t *local, bdaddr_t *peer, unsigned char *key, uint8_t *type); +int read_pin_code(bdaddr_t *local, bdaddr_t *peer, char *pin); +gboolean read_trust(const bdaddr_t *local, const char *addr, const char *service); +int write_trust(const char *src, const char *addr, const char *service, gboolean trust); +GSList *list_trusts(bdaddr_t *local, const char *service); +int write_device_profiles(bdaddr_t *src, bdaddr_t *dst, const char *profiles); +int delete_entry(bdaddr_t *src, const char *storage, const char *key); +int store_record(const gchar *src, const gchar *dst, sdp_record_t *rec); +sdp_record_t *record_from_string(const gchar *str); +sdp_record_t *fetch_record(const gchar *src, const gchar *dst, const uint32_t handle); +int delete_record(const gchar *src, const gchar *dst, const uint32_t handle); +void delete_all_records(const bdaddr_t *src, const bdaddr_t *dst); +sdp_list_t *read_records(const bdaddr_t *src, const bdaddr_t *dst); +sdp_record_t *find_record_in_list(sdp_list_t *recs, const char *uuid); +int store_device_id(const gchar *src, const gchar *dst, + const uint16_t source, const uint16_t vendor, + const uint16_t product, const uint16_t version); +int read_device_id(const gchar *src, const gchar *dst, + uint16_t *source, uint16_t *vendor, + uint16_t *product, uint16_t *version); +int write_device_pairable(bdaddr_t *local, gboolean mode); +int read_device_pairable(bdaddr_t *local, gboolean *mode); +gboolean read_blocked(const bdaddr_t *local, const bdaddr_t *remote); +int write_blocked(const bdaddr_t *local, const bdaddr_t *remote, + gboolean blocked); +int write_device_services(const bdaddr_t *sba, const bdaddr_t *dba, + const char *services); +int delete_device_service(const bdaddr_t *sba, const bdaddr_t *dba); +char *read_device_services(const bdaddr_t *sba, const bdaddr_t *dba); +int write_device_characteristics(const bdaddr_t *sba, const bdaddr_t *dba, + uint16_t handle, const char *chars); +char *read_device_characteristics(const bdaddr_t *sba, const bdaddr_t *dba, + uint16_t handle); +int write_device_attribute(const bdaddr_t *sba, const bdaddr_t *dba, + uint16_t handle, const char *chars); +int read_device_attributes(const bdaddr_t *sba, textfile_cb func, void *data); +int write_device_type(const bdaddr_t *sba, const bdaddr_t *dba, + device_type_t type); +device_type_t read_device_type(const bdaddr_t *sba, const bdaddr_t *dba); + +#define PNP_UUID "00001200-0000-1000-8000-00805f9b34fb" + diff --git a/src/textfile.c b/src/textfile.c new file mode 100644 index 0000000..d115ff6 --- /dev/null +++ b/src/textfile.c @@ -0,0 +1,492 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "textfile.h" + +int create_dirs(const char *filename, const mode_t mode) +{ + struct stat st; + char dir[PATH_MAX + 1], *prev, *next; + int err; + + err = stat(filename, &st); + if (!err && S_ISREG(st.st_mode)) + return 0; + + memset(dir, 0, PATH_MAX + 1); + strcat(dir, "/"); + + prev = strchr(filename, '/'); + + while (prev) { + next = strchr(prev + 1, '/'); + if (!next) + break; + + if (next - prev == 1) { + prev = next; + continue; + } + + strncat(dir, prev + 1, next - prev); + mkdir(dir, mode); + + prev = next; + } + + return 0; +} + +int create_file(const char *filename, const mode_t mode) +{ + int fd; + + umask(S_IWGRP | S_IWOTH); + create_dirs(filename, S_IRUSR | S_IWUSR | S_IXUSR | + S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); + + fd = open(filename, O_RDWR | O_CREAT, mode); + if (fd < 0) + return fd; + + close(fd); + + return 0; +} + +int create_name(char *buf, size_t size, const char *path, const char *address, const char *name) +{ + return snprintf(buf, size, "%s/%s/%s", path, address, name); +} + +static inline char *find_key(char *map, size_t size, const char *key, size_t len, int icase) +{ + char *ptr = map; + size_t ptrlen = size; + + while (ptrlen > len + 1) { + int cmp = (icase) ? strncasecmp(ptr, key, len) : strncmp(ptr, key, len); + if (cmp == 0) { + if (ptr == map) + return ptr; + + if ((*(ptr - 1) == '\r' || *(ptr - 1) == '\n') && + *(ptr + len) == ' ') + return ptr; + } + + if (icase) { + char *p1 = memchr(ptr + 1, tolower(*key), ptrlen - 1); + char *p2 = memchr(ptr + 1, toupper(*key), ptrlen - 1); + + if (!p1) + ptr = p2; + else if (!p2) + ptr = p1; + else + ptr = (p1 < p2) ? p1 : p2; + } else + ptr = memchr(ptr + 1, *key, ptrlen - 1); + + if (!ptr) + return NULL; + + ptrlen = size - (ptr - map); + } + + return NULL; +} + +static inline int write_key_value(int fd, const char *key, const char *value) +{ + char *str; + size_t size; + int err = 0; + + size = strlen(key) + strlen(value) + 2; + + str = malloc(size + 1); + if (!str) + return ENOMEM; + + sprintf(str, "%s %s\n", key, value); + + if (write(fd, str, size) < 0) + err = errno; + + free(str); + + return err; +} + +static char *strnpbrk(const char *s, ssize_t len, const char *accept) +{ + const char *p = s; + const char *end; + + end = s + len - 1; + + while (p <= end && *p) { + const char *a = accept; + + while (*a) { + if (*p == *a) + return (char *) p; + a++; + } + + p++; + } + + return NULL; +} + +static int write_key(const char *pathname, const char *key, const char *value, int icase) +{ + struct stat st; + char *map, *off, *end, *str; + off_t size, pos; size_t base; + int fd, len, err = 0; + + fd = open(pathname, O_RDWR); + if (fd < 0) + return -errno; + + if (flock(fd, LOCK_EX) < 0) { + err = errno; + goto close; + } + + if (fstat(fd, &st) < 0) { + err = errno; + goto unlock; + } + + size = st.st_size; + + if (!size) { + if (value) { + pos = lseek(fd, size, SEEK_SET); + err = write_key_value(fd, key, value); + } + goto unlock; + } + + map = mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_LOCKED, fd, 0); + if (!map || map == MAP_FAILED) { + err = errno; + goto unlock; + } + + len = strlen(key); + off = find_key(map, size, key, len, icase); + if (!off) { + if (value) { + munmap(map, size); + pos = lseek(fd, size, SEEK_SET); + err = write_key_value(fd, key, value); + } + goto unlock; + } + + base = off - map; + + end = strnpbrk(off, size, "\r\n"); + if (!end) { + err = EILSEQ; + goto unmap; + } + + if (value && ((ssize_t) strlen(value) == end - off - len - 1) && + !strncmp(off + len + 1, value, end - off - len - 1)) + goto unmap; + + len = strspn(end, "\r\n"); + end += len; + + len = size - (end - map); + if (!len) { + munmap(map, size); + if (ftruncate(fd, base) < 0) { + err = errno; + goto unlock; + } + pos = lseek(fd, base, SEEK_SET); + if (value) + err = write_key_value(fd, key, value); + + goto unlock; + } + + if (len < 0 || len > size) { + err = EILSEQ; + goto unmap; + } + + str = malloc(len); + if (!str) { + err = errno; + goto unmap; + } + + memcpy(str, end, len); + + munmap(map, size); + if (ftruncate(fd, base) < 0) { + err = errno; + free(str); + goto unlock; + } + pos = lseek(fd, base, SEEK_SET); + if (value) + err = write_key_value(fd, key, value); + + if (write(fd, str, len) < 0) + err = errno; + + free(str); + + goto unlock; + +unmap: + munmap(map, size); + +unlock: + flock(fd, LOCK_UN); + +close: + fdatasync(fd); + + close(fd); + errno = err; + + return -err; +} + +static char *read_key(const char *pathname, const char *key, int icase) +{ + struct stat st; + char *map, *off, *end, *str = NULL; + off_t size; size_t len; + int fd, err = 0; + + fd = open(pathname, O_RDONLY); + if (fd < 0) + return NULL; + + if (flock(fd, LOCK_SH) < 0) { + err = errno; + goto close; + } + + if (fstat(fd, &st) < 0) { + err = errno; + goto unlock; + } + + size = st.st_size; + + map = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + if (!map || map == MAP_FAILED) { + err = errno; + goto unlock; + } + + len = strlen(key); + off = find_key(map, size, key, len, icase); + if (!off) { + err = EILSEQ; + goto unmap; + } + + end = strnpbrk(off, size - (map - off), "\r\n"); + if (!end) { + err = EILSEQ; + goto unmap; + } + + str = malloc(end - off - len); + if (!str) { + err = EILSEQ; + goto unmap; + } + + memset(str, 0, end - off - len); + strncpy(str, off + len + 1, end - off - len - 1); + +unmap: + munmap(map, size); + +unlock: + flock(fd, LOCK_UN); + +close: + close(fd); + errno = err; + + return str; +} + +int textfile_put(const char *pathname, const char *key, const char *value) +{ + return write_key(pathname, key, value, 0); +} + +int textfile_caseput(const char *pathname, const char *key, const char *value) +{ + return write_key(pathname, key, value, 1); +} + +int textfile_del(const char *pathname, const char *key) +{ + return write_key(pathname, key, NULL, 0); +} + +int textfile_casedel(const char *pathname, const char *key) +{ + return write_key(pathname, key, NULL, 1); +} + +char *textfile_get(const char *pathname, const char *key) +{ + return read_key(pathname, key, 0); +} + +char *textfile_caseget(const char *pathname, const char *key) +{ + return read_key(pathname, key, 1); +} + +int textfile_foreach(const char *pathname, textfile_cb func, void *data) +{ + struct stat st; + char *map, *off, *end, *key, *value; + off_t size; size_t len; + int fd, err = 0; + + fd = open(pathname, O_RDONLY); + if (fd < 0) + return -errno; + + if (flock(fd, LOCK_SH) < 0) { + err = errno; + goto close; + } + + if (fstat(fd, &st) < 0) { + err = errno; + goto unlock; + } + + size = st.st_size; + + map = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + if (!map || map == MAP_FAILED) { + err = errno; + goto unlock; + } + + off = map; + + while (size - (off - map) > 0) { + end = strnpbrk(off, size - (off - map), " "); + if (!end) { + err = EILSEQ; + break; + } + + len = end - off; + + key = malloc(len + 1); + if (!key) { + err = errno; + break; + } + + memset(key, 0, len + 1); + memcpy(key, off, len); + + off = end + 1; + + if (size - (off - map) < 0) { + err = EILSEQ; + free(key); + break; + } + + end = strnpbrk(off, size - (off - map), "\r\n"); + if (!end) { + err = EILSEQ; + free(key); + break; + } + + len = end - off; + + value = malloc(len + 1); + if (!value) { + err = errno; + free(key); + break; + } + + memset(value, 0, len + 1); + memcpy(value, off, len); + + func(key, value, data); + + free(key); + free(value); + + off = end + 1; + } + + munmap(map, size); + +unlock: + flock(fd, LOCK_UN); + +close: + close(fd); + errno = err; + + return 0; +} diff --git a/src/textfile.h b/src/textfile.h new file mode 100644 index 0000000..dc5fc2b --- /dev/null +++ b/src/textfile.h @@ -0,0 +1,43 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * 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 + * + */ + +#ifndef __TEXTFILE_H +#define __TEXTFILE_H + +int create_dirs(const char *filename, const mode_t mode); +int create_file(const char *filename, const mode_t mode); +int create_name(char *buf, size_t size, const char *path, + const char *address, const char *name); + +int textfile_put(const char *pathname, const char *key, const char *value); +int textfile_caseput(const char *pathname, const char *key, const char *value); +int textfile_del(const char *pathname, const char *key); +int textfile_casedel(const char *pathname, const char *key); +char *textfile_get(const char *pathname, const char *key); +char *textfile_caseget(const char *pathname, const char *key); + +typedef void (*textfile_cb) (char *key, char *value, void *data); + +int textfile_foreach(const char *pathname, textfile_cb func, void *data); + +#endif /* __TEXTFILE_H */ diff --git a/src/uinput.h b/src/uinput.h new file mode 100644 index 0000000..e83986b --- /dev/null +++ b/src/uinput.h @@ -0,0 +1,724 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2010 Marcel Holtmann + * + * + * 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 + * + */ + +#ifndef __UINPUT_H +#define __UINPUT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +/* Events */ + +#define EV_SYN 0x00 +#define EV_KEY 0x01 +#define EV_REL 0x02 +#define EV_ABS 0x03 +#define EV_MSC 0x04 +#define EV_LED 0x11 +#define EV_SND 0x12 +#define EV_REP 0x14 +#define EV_FF 0x15 +#define EV_PWR 0x16 +#define EV_FF_STATUS 0x17 +#define EV_MAX 0x1f + +/* Synchronization events */ + +#define SYN_REPORT 0 +#define SYN_CONFIG 1 + +/* + * Keys and buttons + * + * Most of the keys/buttons are modeled after USB HUT 1.12 + * (see http://www.usb.org/developers/hidpage). + * Abbreviations in the comments: + * AC - Application Control + * AL - Application Launch Button + * SC - System Control + */ + +#define KEY_RESERVED 0 +#define KEY_ESC 1 +#define KEY_1 2 +#define KEY_2 3 +#define KEY_3 4 +#define KEY_4 5 +#define KEY_5 6 +#define KEY_6 7 +#define KEY_7 8 +#define KEY_8 9 +#define KEY_9 10 +#define KEY_0 11 +#define KEY_MINUS 12 +#define KEY_EQUAL 13 +#define KEY_BACKSPACE 14 +#define KEY_TAB 15 +#define KEY_Q 16 +#define KEY_W 17 +#define KEY_E 18 +#define KEY_R 19 +#define KEY_T 20 +#define KEY_Y 21 +#define KEY_U 22 +#define KEY_I 23 +#define KEY_O 24 +#define KEY_P 25 +#define KEY_LEFTBRACE 26 +#define KEY_RIGHTBRACE 27 +#define KEY_ENTER 28 +#define KEY_LEFTCTRL 29 +#define KEY_A 30 +#define KEY_S 31 +#define KEY_D 32 +#define KEY_F 33 +#define KEY_G 34 +#define KEY_H 35 +#define KEY_J 36 +#define KEY_K 37 +#define KEY_L 38 +#define KEY_SEMICOLON 39 +#define KEY_APOSTROPHE 40 +#define KEY_GRAVE 41 +#define KEY_LEFTSHIFT 42 +#define KEY_BACKSLASH 43 +#define KEY_Z 44 +#define KEY_X 45 +#define KEY_C 46 +#define KEY_V 47 +#define KEY_B 48 +#define KEY_N 49 +#define KEY_M 50 +#define KEY_COMMA 51 +#define KEY_DOT 52 +#define KEY_SLASH 53 +#define KEY_RIGHTSHIFT 54 +#define KEY_KPASTERISK 55 +#define KEY_LEFTALT 56 +#define KEY_SPACE 57 +#define KEY_CAPSLOCK 58 +#define KEY_F1 59 +#define KEY_F2 60 +#define KEY_F3 61 +#define KEY_F4 62 +#define KEY_F5 63 +#define KEY_F6 64 +#define KEY_F7 65 +#define KEY_F8 66 +#define KEY_F9 67 +#define KEY_F10 68 +#define KEY_NUMLOCK 69 +#define KEY_SCROLLLOCK 70 +#define KEY_KP7 71 +#define KEY_KP8 72 +#define KEY_KP9 73 +#define KEY_KPMINUS 74 +#define KEY_KP4 75 +#define KEY_KP5 76 +#define KEY_KP6 77 +#define KEY_KPPLUS 78 +#define KEY_KP1 79 +#define KEY_KP2 80 +#define KEY_KP3 81 +#define KEY_KP0 82 +#define KEY_KPDOT 83 + +#define KEY_ZENKAKUHANKAKU 85 +#define KEY_102ND 86 +#define KEY_F11 87 +#define KEY_F12 88 +#define KEY_RO 89 +#define KEY_KATAKANA 90 +#define KEY_HIRAGANA 91 +#define KEY_HENKAN 92 +#define KEY_KATAKANAHIRAGANA 93 +#define KEY_MUHENKAN 94 +#define KEY_KPJPCOMMA 95 +#define KEY_KPENTER 96 +#define KEY_RIGHTCTRL 97 +#define KEY_KPSLASH 98 +#define KEY_SYSRQ 99 +#define KEY_RIGHTALT 100 +#define KEY_LINEFEED 101 +#define KEY_HOME 102 +#define KEY_UP 103 +#define KEY_PAGEUP 104 +#define KEY_LEFT 105 +#define KEY_RIGHT 106 +#define KEY_END 107 +#define KEY_DOWN 108 +#define KEY_PAGEDOWN 109 +#define KEY_INSERT 110 +#define KEY_DELETE 111 +#define KEY_MACRO 112 +#define KEY_MUTE 113 +#define KEY_VOLUMEDOWN 114 +#define KEY_VOLUMEUP 115 +#define KEY_POWER 116 /* SC System Power Down */ +#define KEY_KPEQUAL 117 +#define KEY_KPPLUSMINUS 118 +#define KEY_PAUSE 119 + +#define KEY_KPCOMMA 121 +#define KEY_HANGEUL 122 +#define KEY_HANGUEL KEY_HANGEUL +#define KEY_HANJA 123 +#define KEY_YEN 124 +#define KEY_LEFTMETA 125 +#define KEY_RIGHTMETA 126 +#define KEY_COMPOSE 127 + +#define KEY_STOP 128 /* AC Stop */ +#define KEY_AGAIN 129 +#define KEY_PROPS 130 /* AC Properties */ +#define KEY_UNDO 131 /* AC Undo */ +#define KEY_FRONT 132 +#define KEY_COPY 133 /* AC Copy */ +#define KEY_OPEN 134 /* AC Open */ +#define KEY_PASTE 135 /* AC Paste */ +#define KEY_FIND 136 /* AC Search */ +#define KEY_CUT 137 /* AC Cut */ +#define KEY_HELP 138 /* AL Integrated Help Center */ +#define KEY_MENU 139 /* Menu (show menu) */ +#define KEY_CALC 140 /* AL Calculator */ +#define KEY_SETUP 141 +#define KEY_SLEEP 142 /* SC System Sleep */ +#define KEY_WAKEUP 143 /* System Wake Up */ +#define KEY_FILE 144 /* AL Local Machine Browser */ +#define KEY_SENDFILE 145 +#define KEY_DELETEFILE 146 +#define KEY_XFER 147 +#define KEY_PROG1 148 +#define KEY_PROG2 149 +#define KEY_WWW 150 /* AL Internet Browser */ +#define KEY_MSDOS 151 +#define KEY_COFFEE 152 /* AL Terminal Lock/Screensaver */ +#define KEY_SCREENLOCK KEY_COFFEE +#define KEY_DIRECTION 153 +#define KEY_CYCLEWINDOWS 154 +#define KEY_MAIL 155 +#define KEY_BOOKMARKS 156 /* AC Bookmarks */ +#define KEY_COMPUTER 157 +#define KEY_BACK 158 /* AC Back */ +#define KEY_FORWARD 159 /* AC Forward */ +#define KEY_CLOSECD 160 +#define KEY_EJECTCD 161 +#define KEY_EJECTCLOSECD 162 +#define KEY_NEXTSONG 163 +#define KEY_PLAYPAUSE 164 +#define KEY_PREVIOUSSONG 165 +#define KEY_STOPCD 166 +#define KEY_RECORD 167 +#define KEY_REWIND 168 +#define KEY_PHONE 169 /* Media Select Telephone */ +#define KEY_ISO 170 +#define KEY_CONFIG 171 /* AL Consumer Control Configuration */ +#define KEY_HOMEPAGE 172 /* AC Home */ +#define KEY_REFRESH 173 /* AC Refresh */ +#define KEY_EXIT 174 /* AC Exit */ +#define KEY_MOVE 175 +#define KEY_EDIT 176 +#define KEY_SCROLLUP 177 +#define KEY_SCROLLDOWN 178 +#define KEY_KPLEFTPAREN 179 +#define KEY_KPRIGHTPAREN 180 +#define KEY_NEW 181 /* AC New */ +#define KEY_REDO 182 /* AC Redo/Repeat */ + +#define KEY_F13 183 +#define KEY_F14 184 +#define KEY_F15 185 +#define KEY_F16 186 +#define KEY_F17 187 +#define KEY_F18 188 +#define KEY_F19 189 +#define KEY_F20 190 +#define KEY_F21 191 +#define KEY_F22 192 +#define KEY_F23 193 +#define KEY_F24 194 + +#define KEY_PLAYCD 200 +#define KEY_PAUSECD 201 +#define KEY_PROG3 202 +#define KEY_PROG4 203 +#define KEY_SUSPEND 205 +#define KEY_CLOSE 206 /* AC Close */ +#define KEY_PLAY 207 +#define KEY_FASTFORWARD 208 +#define KEY_BASSBOOST 209 +#define KEY_PRINT 210 /* AC Print */ +#define KEY_HP 211 +#define KEY_CAMERA 212 +#define KEY_SOUND 213 +#define KEY_QUESTION 214 +#define KEY_EMAIL 215 +#define KEY_CHAT 216 +#define KEY_SEARCH 217 +#define KEY_CONNECT 218 +#define KEY_FINANCE 219 /* AL Checkbook/Finance */ +#define KEY_SPORT 220 +#define KEY_SHOP 221 +#define KEY_ALTERASE 222 +#define KEY_CANCEL 223 /* AC Cancel */ +#define KEY_BRIGHTNESSDOWN 224 +#define KEY_BRIGHTNESSUP 225 +#define KEY_MEDIA 226 + +#define KEY_SWITCHVIDEOMODE 227 /* Cycle between available video + outputs (Monitor/LCD/TV-out/etc) */ +#define KEY_KBDILLUMTOGGLE 228 +#define KEY_KBDILLUMDOWN 229 +#define KEY_KBDILLUMUP 230 + +#define KEY_SEND 231 /* AC Send */ +#define KEY_REPLY 232 /* AC Reply */ +#define KEY_FORWARDMAIL 233 /* AC Forward Msg */ +#define KEY_SAVE 234 /* AC Save */ +#define KEY_DOCUMENTS 235 + +#define KEY_BATTERY 236 + +#define KEY_BLUETOOTH 237 +#define KEY_WLAN 238 +#define KEY_UWB 239 + +#define KEY_UNKNOWN 240 + +#define KEY_VIDEO_NEXT 241 /* drive next video source */ +#define KEY_VIDEO_PREV 242 /* drive previous video source */ +#define KEY_BRIGHTNESS_CYCLE 243 /* brightness up, after max is min */ +#define KEY_BRIGHTNESS_ZERO 244 /* brightness off, use ambient */ +#define KEY_DISPLAY_OFF 245 /* display device to off state */ + +#define KEY_WIMAX 246 + +/* Range 248 - 255 is reserved for special needs of AT keyboard driver */ + +#define BTN_MISC 0x100 +#define BTN_0 0x100 +#define BTN_1 0x101 +#define BTN_2 0x102 +#define BTN_3 0x103 +#define BTN_4 0x104 +#define BTN_5 0x105 +#define BTN_6 0x106 +#define BTN_7 0x107 +#define BTN_8 0x108 +#define BTN_9 0x109 + +#define BTN_MOUSE 0x110 +#define BTN_LEFT 0x110 +#define BTN_RIGHT 0x111 +#define BTN_MIDDLE 0x112 +#define BTN_SIDE 0x113 +#define BTN_EXTRA 0x114 +#define BTN_FORWARD 0x115 +#define BTN_BACK 0x116 +#define BTN_TASK 0x117 + +#define BTN_JOYSTICK 0x120 +#define BTN_TRIGGER 0x120 +#define BTN_THUMB 0x121 +#define BTN_THUMB2 0x122 +#define BTN_TOP 0x123 +#define BTN_TOP2 0x124 +#define BTN_PINKIE 0x125 +#define BTN_BASE 0x126 +#define BTN_BASE2 0x127 +#define BTN_BASE3 0x128 +#define BTN_BASE4 0x129 +#define BTN_BASE5 0x12a +#define BTN_BASE6 0x12b +#define BTN_DEAD 0x12f + +#define BTN_GAMEPAD 0x130 +#define BTN_A 0x130 +#define BTN_B 0x131 +#define BTN_C 0x132 +#define BTN_X 0x133 +#define BTN_Y 0x134 +#define BTN_Z 0x135 +#define BTN_TL 0x136 +#define BTN_TR 0x137 +#define BTN_TL2 0x138 +#define BTN_TR2 0x139 +#define BTN_SELECT 0x13a +#define BTN_START 0x13b +#define BTN_MODE 0x13c +#define BTN_THUMBL 0x13d +#define BTN_THUMBR 0x13e + +#define BTN_DIGI 0x140 +#define BTN_TOOL_PEN 0x140 +#define BTN_TOOL_RUBBER 0x141 +#define BTN_TOOL_BRUSH 0x142 +#define BTN_TOOL_PENCIL 0x143 +#define BTN_TOOL_AIRBRUSH 0x144 +#define BTN_TOOL_FINGER 0x145 +#define BTN_TOOL_MOUSE 0x146 +#define BTN_TOOL_LENS 0x147 +#define BTN_TOUCH 0x14a +#define BTN_STYLUS 0x14b +#define BTN_STYLUS2 0x14c +#define BTN_TOOL_DOUBLETAP 0x14d +#define BTN_TOOL_TRIPLETAP 0x14e + +#define BTN_WHEEL 0x150 +#define BTN_GEAR_DOWN 0x150 +#define BTN_GEAR_UP 0x151 + +#define KEY_OK 0x160 +#define KEY_SELECT 0x161 +#define KEY_GOTO 0x162 +#define KEY_CLEAR 0x163 +#define KEY_POWER2 0x164 +#define KEY_OPTION 0x165 +#define KEY_INFO 0x166 /* AL OEM Features/Tips/Tutorial */ +#define KEY_TIME 0x167 +#define KEY_VENDOR 0x168 +#define KEY_ARCHIVE 0x169 +#define KEY_PROGRAM 0x16a /* Media Select Program Guide */ +#define KEY_CHANNEL 0x16b +#define KEY_FAVORITES 0x16c +#define KEY_EPG 0x16d +#define KEY_PVR 0x16e /* Media Select Home */ +#define KEY_MHP 0x16f +#define KEY_LANGUAGE 0x170 +#define KEY_TITLE 0x171 +#define KEY_SUBTITLE 0x172 +#define KEY_ANGLE 0x173 +#define KEY_ZOOM 0x174 +#define KEY_MODE 0x175 +#define KEY_KEYBOARD 0x176 +#define KEY_SCREEN 0x177 +#define KEY_PC 0x178 /* Media Select Computer */ +#define KEY_TV 0x179 /* Media Select TV */ +#define KEY_TV2 0x17a /* Media Select Cable */ +#define KEY_VCR 0x17b /* Media Select VCR */ +#define KEY_VCR2 0x17c /* VCR Plus */ +#define KEY_SAT 0x17d /* Media Select Satellite */ +#define KEY_SAT2 0x17e +#define KEY_CD 0x17f /* Media Select CD */ +#define KEY_TAPE 0x180 /* Media Select Tape */ +#define KEY_RADIO 0x181 +#define KEY_TUNER 0x182 /* Media Select Tuner */ +#define KEY_PLAYER 0x183 +#define KEY_TEXT 0x184 +#define KEY_DVD 0x185 /* Media Select DVD */ +#define KEY_AUX 0x186 +#define KEY_MP3 0x187 +#define KEY_AUDIO 0x188 +#define KEY_VIDEO 0x189 +#define KEY_DIRECTORY 0x18a +#define KEY_LIST 0x18b +#define KEY_MEMO 0x18c /* Media Select Messages */ +#define KEY_CALENDAR 0x18d +#define KEY_RED 0x18e +#define KEY_GREEN 0x18f +#define KEY_YELLOW 0x190 +#define KEY_BLUE 0x191 +#define KEY_CHANNELUP 0x192 /* Channel Increment */ +#define KEY_CHANNELDOWN 0x193 /* Channel Decrement */ +#define KEY_FIRST 0x194 +#define KEY_LAST 0x195 /* Recall Last */ +#define KEY_AB 0x196 +#define KEY_NEXT 0x197 +#define KEY_RESTART 0x198 +#define KEY_SLOW 0x199 +#define KEY_SHUFFLE 0x19a +#define KEY_BREAK 0x19b +#define KEY_PREVIOUS 0x19c +#define KEY_DIGITS 0x19d +#define KEY_TEEN 0x19e +#define KEY_TWEN 0x19f +#define KEY_VIDEOPHONE 0x1a0 /* Media Select Video Phone */ +#define KEY_GAMES 0x1a1 /* Media Select Games */ +#define KEY_ZOOMIN 0x1a2 /* AC Zoom In */ +#define KEY_ZOOMOUT 0x1a3 /* AC Zoom Out */ +#define KEY_ZOOMRESET 0x1a4 /* AC Zoom */ +#define KEY_WORDPROCESSOR 0x1a5 /* AL Word Processor */ +#define KEY_EDITOR 0x1a6 /* AL Text Editor */ +#define KEY_SPREADSHEET 0x1a7 /* AL Spreadsheet */ +#define KEY_GRAPHICSEDITOR 0x1a8 /* AL Graphics Editor */ +#define KEY_PRESENTATION 0x1a9 /* AL Presentation App */ +#define KEY_DATABASE 0x1aa /* AL Database App */ +#define KEY_NEWS 0x1ab /* AL Newsreader */ +#define KEY_VOICEMAIL 0x1ac /* AL Voicemail */ +#define KEY_ADDRESSBOOK 0x1ad /* AL Contacts/Address Book */ +#define KEY_MESSENGER 0x1ae /* AL Instant Messaging */ +#define KEY_DISPLAYTOGGLE 0x1af /* Turn display (LCD) on and off */ +#define KEY_SPELLCHECK 0x1b0 /* AL Spell Check */ +#define KEY_LOGOFF 0x1b1 /* AL Logoff */ + +#define KEY_DOLLAR 0x1b2 +#define KEY_EURO 0x1b3 + +#define KEY_FRAMEBACK 0x1b4 /* Consumer - transport controls */ +#define KEY_FRAMEFORWARD 0x1b5 +#define KEY_CONTEXT_MENU 0x1b6 /* GenDesc - system context menu */ +#define KEY_MEDIA_REPEAT 0x1b7 /* Consumer - transport control */ + +#define KEY_DEL_EOL 0x1c0 +#define KEY_DEL_EOS 0x1c1 +#define KEY_INS_LINE 0x1c2 +#define KEY_DEL_LINE 0x1c3 + +#define KEY_FN 0x1d0 +#define KEY_FN_ESC 0x1d1 +#define KEY_FN_F1 0x1d2 +#define KEY_FN_F2 0x1d3 +#define KEY_FN_F3 0x1d4 +#define KEY_FN_F4 0x1d5 +#define KEY_FN_F5 0x1d6 +#define KEY_FN_F6 0x1d7 +#define KEY_FN_F7 0x1d8 +#define KEY_FN_F8 0x1d9 +#define KEY_FN_F9 0x1da +#define KEY_FN_F10 0x1db +#define KEY_FN_F11 0x1dc +#define KEY_FN_F12 0x1dd +#define KEY_FN_1 0x1de +#define KEY_FN_2 0x1df +#define KEY_FN_D 0x1e0 +#define KEY_FN_E 0x1e1 +#define KEY_FN_F 0x1e2 +#define KEY_FN_S 0x1e3 +#define KEY_FN_B 0x1e4 + +#define KEY_BRL_DOT1 0x1f1 +#define KEY_BRL_DOT2 0x1f2 +#define KEY_BRL_DOT3 0x1f3 +#define KEY_BRL_DOT4 0x1f4 +#define KEY_BRL_DOT5 0x1f5 +#define KEY_BRL_DOT6 0x1f6 +#define KEY_BRL_DOT7 0x1f7 +#define KEY_BRL_DOT8 0x1f8 +#define KEY_BRL_DOT9 0x1f9 +#define KEY_BRL_DOT10 0x1fa + +/* We avoid low common keys in module aliases so they don't get huge. */ +#define KEY_MIN_INTERESTING KEY_MUTE +#define KEY_MAX 0x1ff +#define KEY_CNT (KEY_MAX+1) + +/* + * Relative axes + */ + +#define REL_X 0x00 +#define REL_Y 0x01 +#define REL_Z 0x02 +#define REL_RX 0x03 +#define REL_RY 0x04 +#define REL_RZ 0x05 +#define REL_HWHEEL 0x06 +#define REL_DIAL 0x07 +#define REL_WHEEL 0x08 +#define REL_MISC 0x09 +#define REL_MAX 0x0f +#define REL_CNT (REL_MAX+1) + +/* + * Absolute axes + */ + +#define ABS_X 0x00 +#define ABS_Y 0x01 +#define ABS_Z 0x02 +#define ABS_RX 0x03 +#define ABS_RY 0x04 +#define ABS_RZ 0x05 +#define ABS_THROTTLE 0x06 +#define ABS_RUDDER 0x07 +#define ABS_WHEEL 0x08 +#define ABS_GAS 0x09 +#define ABS_BRAKE 0x0a +#define ABS_HAT0X 0x10 +#define ABS_HAT0Y 0x11 +#define ABS_HAT1X 0x12 +#define ABS_HAT1Y 0x13 +#define ABS_HAT2X 0x14 +#define ABS_HAT2Y 0x15 +#define ABS_HAT3X 0x16 +#define ABS_HAT3Y 0x17 +#define ABS_PRESSURE 0x18 +#define ABS_DISTANCE 0x19 +#define ABS_TILT_X 0x1a +#define ABS_TILT_Y 0x1b +#define ABS_TOOL_WIDTH 0x1c +#define ABS_VOLUME 0x20 +#define ABS_MISC 0x28 +#define ABS_MAX 0x3f +#define ABS_CNT (ABS_MAX+1) + +/* + * Switch events + */ + +#define SW_LID 0x00 /* set = lid shut */ +#define SW_TABLET_MODE 0x01 /* set = tablet mode */ +#define SW_HEADPHONE_INSERT 0x02 /* set = inserted */ +#define SW_RFKILL_ALL 0x03 /* rfkill master switch, type "any" + set = radio enabled */ +#define SW_RADIO SW_RFKILL_ALL /* deprecated */ +#define SW_MICROPHONE_INSERT 0x04 /* set = inserted */ +#define SW_DOCK 0x05 /* set = plugged into dock */ +#define SW_MAX 0x0f +#define SW_CNT (SW_MAX+1) + +/* + * Misc events + */ + +#define MSC_SERIAL 0x00 +#define MSC_PULSELED 0x01 +#define MSC_GESTURE 0x02 +#define MSC_RAW 0x03 +#define MSC_SCAN 0x04 +#define MSC_MAX 0x07 +#define MSC_CNT (MSC_MAX+1) + +/* + * LEDs + */ + +#define LED_NUML 0x00 +#define LED_CAPSL 0x01 +#define LED_SCROLLL 0x02 +#define LED_COMPOSE 0x03 +#define LED_KANA 0x04 +#define LED_SLEEP 0x05 +#define LED_SUSPEND 0x06 +#define LED_MUTE 0x07 +#define LED_MISC 0x08 +#define LED_MAIL 0x09 +#define LED_CHARGING 0x0a +#define LED_MAX 0x0f +#define LED_CNT (LED_MAX+1) + +/* + * Autorepeat values + */ + +#define REP_DELAY 0x00 +#define REP_PERIOD 0x01 +#define REP_MAX 0x01 + +/* + * Sounds + */ + +#define SND_CLICK 0x00 +#define SND_BELL 0x01 +#define SND_TONE 0x02 +#define SND_MAX 0x07 +#define SND_CNT (SND_MAX+1) + +/* + * IDs. + */ + +#define ID_BUS 0 +#define ID_VENDOR 1 +#define ID_PRODUCT 2 +#define ID_VERSION 3 + +#define BUS_PCI 0x01 +#define BUS_ISAPNP 0x02 +#define BUS_USB 0x03 +#define BUS_HIL 0x04 +#define BUS_BLUETOOTH 0x05 +#define BUS_VIRTUAL 0x06 + +#define BUS_ISA 0x10 +#define BUS_I8042 0x11 +#define BUS_XTKBD 0x12 +#define BUS_RS232 0x13 +#define BUS_GAMEPORT 0x14 +#define BUS_PARPORT 0x15 +#define BUS_AMIGA 0x16 +#define BUS_ADB 0x17 +#define BUS_I2C 0x18 +#define BUS_HOST 0x19 +#define BUS_GSC 0x1A +#define BUS_ATARI 0x1B + +/* User input interface */ + +#define UINPUT_IOCTL_BASE 'U' + +#define UI_DEV_CREATE _IO(UINPUT_IOCTL_BASE, 1) +#define UI_DEV_DESTROY _IO(UINPUT_IOCTL_BASE, 2) + +#define UI_SET_EVBIT _IOW(UINPUT_IOCTL_BASE, 100, int) +#define UI_SET_KEYBIT _IOW(UINPUT_IOCTL_BASE, 101, int) +#define UI_SET_RELBIT _IOW(UINPUT_IOCTL_BASE, 102, int) +#define UI_SET_ABSBIT _IOW(UINPUT_IOCTL_BASE, 103, int) +#define UI_SET_MSCBIT _IOW(UINPUT_IOCTL_BASE, 104, int) +#define UI_SET_LEDBIT _IOW(UINPUT_IOCTL_BASE, 105, int) +#define UI_SET_SNDBIT _IOW(UINPUT_IOCTL_BASE, 106, int) +#define UI_SET_FFBIT _IOW(UINPUT_IOCTL_BASE, 107, int) +#define UI_SET_PHYS _IOW(UINPUT_IOCTL_BASE, 108, char*) +#define UI_SET_SWBIT _IOW(UINPUT_IOCTL_BASE, 109, int) + +#ifndef NBITS +#define NBITS(x) ((((x) - 1) / (sizeof(long) * 8)) + 1) +#endif + +#define UINPUT_MAX_NAME_SIZE 80 + +struct uinput_id { + uint16_t bustype; + uint16_t vendor; + uint16_t product; + uint16_t version; +}; + +struct uinput_dev { + char name[UINPUT_MAX_NAME_SIZE]; + struct uinput_id id; + int ff_effects_max; + int absmax[ABS_MAX + 1]; + int absmin[ABS_MAX + 1]; + int absfuzz[ABS_MAX + 1]; + int absflat[ABS_MAX + 1]; +}; + +struct uinput_event { + struct timeval time; + uint16_t type; + uint16_t code; + int32_t value; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __UINPUT_H */ -- cgit v1.2.3