summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAdrian Bunk <adrian.bunk@movial.com>2011-06-03 09:17:04 +0000
committerAdrian Bunk <adrian.bunk@movial.com>2011-06-03 09:17:04 +0000
commit799757ccf1d03c33c75bc597cd5ef77741dcb6a7 (patch)
treea8c3be85c730de28b012586591b76301033d3d21 /src
Imported upstream 4.91upstream-4.91upstreampackaging
Diffstat (limited to 'src')
-rw-r--r--src/adapter.c3752
-rw-r--r--src/adapter.h302
-rw-r--r--src/agent.c794
-rw-r--r--src/agent.h77
-rw-r--r--src/attrib-server.c1310
-rw-r--r--src/attrib-server.h35
-rw-r--r--src/bluetooth.conf33
-rw-r--r--src/bluetooth.ver10
-rw-r--r--src/bluetoothd.8.in91
-rw-r--r--src/dbus-common.c254
-rw-r--r--src/dbus-common.h47
-rw-r--r--src/device.c2388
-rw-r--r--src/device.h119
-rw-r--r--src/error.c116
-rw-r--r--src/error.h43
-rw-r--r--src/event.c731
-rw-r--r--src/event.h44
-rwxr-xr-xsrc/genbuiltin17
-rw-r--r--src/glib-helper.c587
-rw-r--r--src/glib-helper.h36
-rw-r--r--src/hcid.h66
-rw-r--r--src/log.c136
-rw-r--r--src/log.h57
-rw-r--r--src/main.c509
-rw-r--r--src/main.conf66
-rw-r--r--src/manager.c431
-rw-r--r--src/manager.h43
-rw-r--r--src/oob.c41
-rw-r--r--src/oob.h32
-rw-r--r--src/oui.c101
-rw-r--r--src/oui.h25
-rw-r--r--src/plugin.c249
-rw-r--r--src/plugin.h47
-rw-r--r--src/ppoll.h16
-rw-r--r--src/rfkill.c173
-rw-r--r--src/sdp-xml.c789
-rw-r--r--src/sdp-xml.h59
-rw-r--r--src/sdpd-database.c346
-rw-r--r--src/sdpd-request.c1094
-rw-r--r--src/sdpd-server.c292
-rw-r--r--src/sdpd-service.c537
-rw-r--r--src/sdpd.h97
-rw-r--r--src/storage.c1345
-rw-r--r--src/storage.h92
-rw-r--r--src/textfile.c492
-rw-r--r--src/textfile.h43
-rw-r--r--src/uinput.h724
47 files changed, 18688 insertions, 0 deletions
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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/uuid.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <dbus/dbus.h>
+#include <glib.h>
+
+#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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <glib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/uuid.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#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 <<Primary Service>> and <<Secondary Service>> 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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+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 @@
+<!-- This configuration file specifies the required security policies
+ for Bluetooth core daemon to work. -->
+
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+
+ <!-- ../system.conf have denied everything, so we just punch some holes -->
+
+ <policy user="root">
+ <allow own="org.bluez"/>
+ <allow send_destination="org.bluez"/>
+ <allow send_interface="org.bluez.Agent"/>
+ <allow send_interface="org.bluez.HandsfreeAgent"/>
+ <allow send_interface="org.bluez.MediaEndpoint"/>
+ <allow send_interface="org.bluez.Watcher"/>
+ </policy>
+
+ <policy at_console="true">
+ <allow send_destination="org.bluez"/>
+ </policy>
+
+ <!-- allow users of lp group (printing subsystem) to
+ communicate with bluetoothd -->
+ <policy group="lp">
+ <allow send_destination="org.bluez"/>
+ </policy>
+
+ <policy context="default">
+ <deny send_destination="org.bluez"/>
+ </policy>
+
+</busconfig>
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 <marcel@holtmann.org>
+ * Copyright (C) 2005-2007 Johan Hedberg <johan.hedberg@nokia.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/uuid.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#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 <marcel@holtmann.org>
+ * Copyright (C) 2007-2008 Fabien Chevalier <fabchevalier@free.fr>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gdbus.h>
+
+#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 <marcel@holtmann.org>
+ * Copyright (C) 2007-2008 Fabien Chevalier <fabchevalier@free.fr>
+ *
+ *
+ * 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 <dbus/dbus.h>
+
+#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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+
+#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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+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 <maxk@qualcomm.com>
+ * Copyright (C) 2002-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <syslog.h>
+
+#include <glib.h>
+
+#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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+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 <maxk@qualcomm.com>
+ * Copyright (C) 2002-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/uuid.h>
+
+#include <glib.h>
+
+#include <dbus/dbus.h>
+
+#include <gdbus.h>
+
+#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 <cap-ng.h>
+#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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include <glib.h>
+
+#include <dbus/dbus.h>
+
+#include <gdbus.h>
+
+#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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <bluetooth/bluetooth.h>
+#include <dbus/dbus.h>
+
+#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 <szymon.janc@tieto.com> 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 <szymon.janc@tieto.com> 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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <dlfcn.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include <glib.h>
+
+#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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+#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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+
+#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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#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, "<nil/>\n");
+ break;
+
+ case SDP_BOOL:
+ appender(data, indent);
+ appender(data, "<boolean value=\"");
+ appender(data, value->val.uint8 ? "true" : "false");
+ appender(data, "\" />\n");
+ break;
+
+ case SDP_UINT8:
+ appender(data, indent);
+ appender(data, "<uint8 value=\"");
+ snprintf(buf, STRBUFSIZE - 1, "0x%02x", value->val.uint8);
+ appender(data, buf);
+ appender(data, "\" />\n");
+ break;
+
+ case SDP_UINT16:
+ appender(data, indent);
+ appender(data, "<uint16 value=\"");
+ snprintf(buf, STRBUFSIZE - 1, "0x%04x", value->val.uint16);
+ appender(data, buf);
+ appender(data, "\" />\n");
+ break;
+
+ case SDP_UINT32:
+ appender(data, indent);
+ appender(data, "<uint32 value=\"");
+ snprintf(buf, STRBUFSIZE - 1, "0x%08x", value->val.uint32);
+ appender(data, buf);
+ appender(data, "\" />\n");
+ break;
+
+ case SDP_UINT64:
+ appender(data, indent);
+ appender(data, "<uint64 value=\"");
+ snprintf(buf, STRBUFSIZE - 1, "0x%016jx", value->val.uint64);
+ appender(data, buf);
+ appender(data, "\" />\n");
+ break;
+
+ case SDP_UINT128:
+ appender(data, indent);
+ appender(data, "<uint128 value=\"");
+
+ for (i = 0; i < 16; i++) {
+ sprintf(&buf[i * 2], "%02x",
+ (unsigned char) value->val.uint128.data[i]);
+ }
+
+ appender(data, buf);
+ appender(data, "\" />\n");
+ break;
+
+ case SDP_INT8:
+ appender(data, indent);
+ appender(data, "<int8 value=\"");
+ snprintf(buf, STRBUFSIZE - 1, "%d", value->val.int8);
+ appender(data, buf);
+ appender(data, "\" />\n");
+ break;
+
+ case SDP_INT16:
+ appender(data, indent);
+ appender(data, "<int16 value=\"");
+ snprintf(buf, STRBUFSIZE - 1, "%d", value->val.int16);
+ appender(data, buf);
+ appender(data, "\" />\n");
+ break;
+
+ case SDP_INT32:
+ appender(data, indent);
+ appender(data, "<int32 value=\"");
+ snprintf(buf, STRBUFSIZE - 1, "%d", value->val.int32);
+ appender(data, buf);
+ appender(data, "\" />\n");
+ break;
+
+ case SDP_INT64:
+ appender(data, indent);
+ appender(data, "<int64 value=\"");
+ snprintf(buf, STRBUFSIZE - 1, "%jd", value->val.int64);
+ appender(data, buf);
+ appender(data, "\" />\n");
+ break;
+
+ case SDP_INT128:
+ appender(data, indent);
+ appender(data, "<int128 value=\"");
+
+ for (i = 0; i < 16; i++) {
+ sprintf(&buf[i * 2], "%02x",
+ (unsigned char) value->val.int128.data[i]);
+ }
+ appender(data, buf);
+
+ appender(data, "\" />\n");
+ break;
+
+ case SDP_UUID16:
+ appender(data, indent);
+ appender(data, "<uuid value=\"");
+ snprintf(buf, STRBUFSIZE - 1, "0x%04x", value->val.uuid.value.uuid16);
+ appender(data, buf);
+ appender(data, "\" />\n");
+ break;
+
+ case SDP_UUID32:
+ appender(data, indent);
+ appender(data, "<uuid value=\"");
+ snprintf(buf, STRBUFSIZE - 1, "0x%08x", value->val.uuid.value.uuid32);
+ appender(data, buf);
+ appender(data, "\" />\n");
+ break;
+
+ case SDP_UUID128:
+ appender(data, indent);
+ appender(data, "<uuid value=\"");
+
+ snprintf(buf, STRBUFSIZE - 1,
+ "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ (unsigned char) value->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, "<text ");
+
+ if (hex) {
+ appender(data, "encoding=\"hex\" ");
+ strBuf = malloc(sizeof(char)
+ * ((value->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, "<url value=\"");
+ strBuf = strndup(value->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, "<sequence>\n");
+
+ convert_raw_data_to_xml(value->val.dataseq,
+ indent_level + 1, data, appender);
+
+ appender(data, indent);
+ appender(data, "</sequence>\n");
+
+ break;
+
+ case SDP_ALT8:
+ case SDP_ALT16:
+ case SDP_ALT32:
+ appender(data, indent);
+
+ appender(data, "<alternate>\n");
+
+ convert_raw_data_to_xml(value->val.dataseq,
+ indent_level + 1, data, appender);
+ appender(data, indent);
+
+ appender(data, "</alternate>\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<attribute id=\"0x%04x\">\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</attribute>\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, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n\n");
+ appender(data, "<record>\n");
+ sdp_list_foreach(rec->attrlist,
+ convert_raw_attr_to_xml_func, &cd);
+ appender(data, "</record>\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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+
+#ifndef __SDP_XML_H
+#define __SDP_XML_H
+
+#include <bluetooth/sdp.h>
+
+#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 <maxk@qualcomm.com>
+ * Copyright (C) 2002-2010 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2002-2003 Stephen Crane <steve.crane@rococosoft.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#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 <maxk@qualcomm.com>
+ * Copyright (C) 2002-2010 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2002-2003 Stephen Crane <steve.crane@rococosoft.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <netinet/in.h>
+
+#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 <maxk@qualcomm.com>
+ * Copyright (C) 2002-2010 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2002-2003 Stephen Crane <steve.crane@rococosoft.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <sys/un.h>
+#include <netinet/in.h>
+
+#include <glib.h>
+
+#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 <maxk@qualcomm.com>
+ * Copyright (C) 2002-2010 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2002-2003 Stephen Crane <steve.crane@rococosoft.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <netinet/in.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+
+#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 <maxk@qualcomm.com>
+ * Copyright (C) 2002-2010 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2002-2003 Stephen Crane <steve.crane@rococosoft.com>
+ *
+ *
+ * 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 <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+
+#ifdef SDP_DEBUG
+#include <syslog.h>
+#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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+
+#include <glib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+
+#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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#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 <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __UINPUT_H
+#define __UINPUT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+
+/* 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 */