summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am59
-rw-r--r--src/adapter.c2680
-rw-r--r--src/adapter.h185
-rw-r--r--src/agent.c733
-rw-r--r--src/agent.h71
-rw-r--r--src/bluetooth.conf37
-rw-r--r--src/dbus-api.txt1401
-rw-r--r--src/dbus-common.c353
-rw-r--r--src/dbus-common.h44
-rw-r--r--src/dbus-database.c219
-rw-r--r--src/dbus-database.h32
-rw-r--r--src/dbus-hci.c2030
-rw-r--r--src/dbus-hci.h76
-rw-r--r--src/dbus-service.c157
-rw-r--r--src/dbus-service.h28
-rw-r--r--src/device.c1100
-rw-r--r--src/device.h58
-rw-r--r--src/hcid.8101
-rw-r--r--src/hcid.conf57
-rw-r--r--src/hcid.conf.5227
-rw-r--r--src/hcid.h204
-rw-r--r--src/kword.c102
-rw-r--r--src/kword.h37
-rw-r--r--src/lexer.l160
-rwxr-xr-xsrc/list-devices52
-rw-r--r--src/main.c965
-rw-r--r--src/manager.c547
-rw-r--r--src/manager.h41
-rw-r--r--src/parser.y360
-rw-r--r--src/plugin.c190
-rw-r--r--src/plugin.h33
-rw-r--r--src/sdpd-database.c304
-rw-r--r--src/sdpd-request.c960
-rw-r--r--src/sdpd-server.c280
-rw-r--r--src/sdpd-service.c677
-rw-r--r--src/sdpd.h93
-rw-r--r--src/security.c1037
-rw-r--r--src/server.c68
-rw-r--r--src/server.h31
-rw-r--r--src/service-did.xml33
-rw-r--r--src/service-ftp.xml37
-rw-r--r--src/service-opp.xml50
-rw-r--r--src/service-record.dtd66
-rw-r--r--src/service-spp.xml25
-rwxr-xr-xsrc/simple-agent112
-rwxr-xr-xsrc/simple-service127
-rw-r--r--src/storage.c784
-rw-r--r--src/telephony.c44
-rw-r--r--src/telephony.h26
-rwxr-xr-xsrc/test-adapter90
-rwxr-xr-xsrc/test-device124
-rwxr-xr-xsrc/test-discovery43
-rwxr-xr-xsrc/test-manager27
53 files changed, 17377 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 00000000..4e8a9318
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,59 @@
+
+if CONFIGFILES
+dbusdir = $(sysconfdir)/dbus-1/system.d
+
+dbus_DATA = bluetooth.conf
+
+confdir = $(sysconfdir)/bluetooth
+
+conf_DATA = hcid.conf
+
+statedir = $(localstatedir)/lib/bluetooth
+
+state_DATA =
+endif
+
+sbin_PROGRAMS = bluetoothd
+
+bluetoothd_SOURCES = main.c hcid.h sdpd.h \
+ sdpd-server.c sdpd-request.c sdpd-service.c \
+ sdpd-database.c security.c storage.c \
+ parser.h parser.y lexer.l kword.c kword.h \
+ server.h server.c manager.h manager.c \
+ adapter.h adapter.c device.h device.c plugin.h plugin.c \
+ dbus-common.c dbus-common.h \
+ dbus-database.c dbus-database.h dbus-service.c dbus-service.h \
+ dbus-hci.h dbus-hci.c \
+ telephony.h telephony.c agent.h agent.c
+
+bluetoothd_LDADD = $(top_builddir)/common/libhelper.a \
+ @GDBUS_LIBS@ @GMODULE_LIBS@ @GLIB_LIBS@ @DBUS_LIBS@ @BLUEZ_LIBS@
+
+if MAINTAINER_MODE
+plugindir = $(abs_top_srcdir)/plugins
+else
+plugindir = $(libdir)/bluetooth/plugins
+endif
+
+AM_CFLAGS = @BLUEZ_CFLAGS@ @DBUS_CFLAGS@ \
+ @GLIB_CFLAGS@ @GMODULE_CFLAGS@ @GDBUS_CFLAGS@ \
+ -DPLUGINDIR=\""$(plugindir)"\"
+
+INCLUDES = -I$(top_srcdir)/common
+
+BUILT_SOURCES = parser.h
+
+if MANPAGES
+man_MANS = hcid.8 hcid.conf.5
+endif
+
+AM_YFLAGS = -d
+
+CLEANFILES = lexer.c parser.c parser.h
+
+EXTRA_DIST = bluetooth.conf hcid.8 hcid.conf.5 hcid.conf dbus-api.txt \
+ list-devices test-discovery test-manager test-adapter test-device \
+ simple-service simple-agent service-record.dtd \
+ service-did.xml service-spp.xml service-opp.xml service-ftp.xml
+
+MAINTAINERCLEANFILES = Makefile.in
diff --git a/src/adapter.c b/src/adapter.c
new file mode 100644
index 00000000..cb2eba0a
--- /dev/null
+++ b/src/adapter.c
@@ -0,0 +1,2680 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "hcid.h"
+#include "sdpd.h"
+
+#include "adapter.h"
+#include "device.h"
+
+#include "textfile.h"
+#include "oui.h"
+#include "dbus-common.h"
+#include "dbus-hci.h"
+#include "dbus-database.h"
+#include "error.h"
+#include "glib-helper.h"
+#include "logging.h"
+#include "agent.h"
+
+#define NUM_ELEMENTS(table) (sizeof(table)/sizeof(const char *))
+
+#define IO_CAPABILITY_DISPLAYONLY 0x00
+#define IO_CAPABILITY_DISPLAYYESNO 0x01
+#define IO_CAPABILITY_KEYBOARDONLY 0x02
+#define IO_CAPABILITY_NOINPUTOUTPUT 0x03
+#define IO_CAPABILITY_INVALID 0xFF
+
+static DBusConnection *connection = NULL;
+
+struct record_list {
+ sdp_list_t *recs;
+ const gchar *addr;
+};
+
+struct mode_req {
+ struct adapter *adapter;
+ DBusConnection *conn; /* Connection reference */
+ DBusMessage *msg; /* Message reference */
+ uint8_t mode; /* Requested mode */
+ guint id; /* Listener id */
+};
+
+static inline DBusMessage *invalid_args(DBusMessage *msg)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments",
+ "Invalid arguments in method call");
+}
+
+static inline DBusMessage *not_available(DBusMessage *msg)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable",
+ "Not Available");
+}
+
+static inline DBusMessage *adapter_not_ready(DBusMessage *msg)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".NotReady",
+ "Adapter is not ready");
+}
+
+static inline DBusMessage *no_such_adapter(DBusMessage *msg)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".NoSuchAdapter",
+ "No such adapter");
+}
+
+static inline DBusMessage *failed_strerror(DBusMessage *msg, int err)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ strerror(err));
+}
+
+static inline DBusMessage *in_progress(DBusMessage *msg, const char *str)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress", str);
+}
+
+static inline DBusMessage *not_in_progress(DBusMessage *msg, const char *str)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".NotInProgress", str);
+}
+
+static inline DBusMessage *not_authorized(DBusMessage *msg)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAuthorized",
+ "Not authorized");
+}
+
+static inline DBusMessage *unsupported_major_class(DBusMessage *msg)
+{
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".UnsupportedMajorClass",
+ "Unsupported Major Class");
+}
+
+static int auth_req_cmp(const void *p1, const void *p2)
+{
+ const struct pending_auth_info *pb1 = p1;
+ const bdaddr_t *bda = p2;
+
+ return bda ? bacmp(&pb1->bdaddr, bda) : -1;
+}
+
+void adapter_auth_request_replied(struct adapter *adapter, bdaddr_t *dba)
+{
+ GSList *l;
+ struct pending_auth_info *auth;
+
+ l = g_slist_find_custom(adapter->auth_reqs, dba, auth_req_cmp);
+ if (!l)
+ return;
+
+ auth = l->data;
+
+ auth->replied = TRUE;
+}
+
+struct pending_auth_info *adapter_find_auth_request(struct adapter *adapter,
+ bdaddr_t *dba)
+{
+ GSList *l;
+
+ l = g_slist_find_custom(adapter->auth_reqs, dba, auth_req_cmp);
+ if (l)
+ return l->data;
+
+ return NULL;
+}
+
+void adapter_remove_auth_request(struct adapter *adapter, bdaddr_t *dba)
+{
+ GSList *l;
+ struct pending_auth_info *auth;
+
+ l = g_slist_find_custom(adapter->auth_reqs, dba, auth_req_cmp);
+ if (!l)
+ return;
+
+ auth = l->data;
+
+ adapter->auth_reqs = g_slist_remove(adapter->auth_reqs, auth);
+
+ g_free(auth);
+}
+
+struct pending_auth_info *adapter_new_auth_request(struct adapter *adapter,
+ bdaddr_t *dba,
+ auth_type_t type)
+{
+ struct pending_auth_info *info;
+
+ debug("hcid_dbus_new_auth_request");
+
+ info = g_new0(struct pending_auth_info, 1);
+
+ bacpy(&info->bdaddr, dba);
+ info->type = type;
+ adapter->auth_reqs = g_slist_append(adapter->auth_reqs, info);
+
+ if (adapter->bonding && !bacmp(dba, &adapter->bonding->bdaddr))
+ adapter->bonding->auth_active = 1;
+
+ return info;
+}
+
+int pending_remote_name_cancel(struct adapter *adapter)
+{
+ struct remote_dev_info *dev, match;
+ GSList *l;
+ int dd, err = 0;
+
+ /* find the pending remote name request */
+ memset(&match, 0, sizeof(struct remote_dev_info));
+ bacpy(&match.bdaddr, BDADDR_ANY);
+ match.name_status = NAME_REQUESTED;
+
+ l = g_slist_find_custom(adapter->found_devices, &match,
+ (GCompareFunc) found_device_cmp);
+ if (!l) /* no pending request */
+ return 0;
+
+ dd = hci_open_dev(adapter->dev_id);
+ if (dd < 0)
+ return -ENODEV;
+
+ dev = l->data;
+
+ if (hci_read_remote_name_cancel(dd, &dev->bdaddr, 1000) < 0) {
+ error("Remote name cancel failed: %s(%d)", strerror(errno), errno);
+ err = -errno;
+ }
+
+ /* free discovered devices list */
+ g_slist_foreach(adapter->found_devices, (GFunc) g_free, NULL);
+ g_slist_free(adapter->found_devices);
+ adapter->found_devices = NULL;
+
+ hci_close_dev(dd);
+ return err;
+}
+
+static int auth_info_agent_cmp(const void *a, const void *b)
+{
+ const struct pending_auth_info *auth = a;
+ const struct agent *agent = b;
+
+ if (auth->agent == agent)
+ return 0;
+
+ return -1;
+}
+
+static void device_agent_removed(struct agent *agent, void *user_data)
+{
+ struct btd_device *device = user_data;
+ struct pending_auth_info *auth;
+ GSList *l;
+ struct adapter *adapter;
+
+ adapter = device_get_adapter(device);
+ device_set_agent(device, NULL);
+
+ l = g_slist_find_custom(adapter->auth_reqs, agent,
+ auth_info_agent_cmp);
+ if (!l)
+ return;
+
+ auth = l->data;
+ auth->agent = NULL;
+}
+
+static struct bonding_request_info *bonding_request_new(DBusConnection *conn,
+ DBusMessage *msg,
+ struct adapter *adapter,
+ const char *address,
+ const char *agent_path,
+ uint8_t capability)
+{
+ struct bonding_request_info *bonding;
+ struct btd_device *device;
+ const char *name = dbus_message_get_sender(msg);
+ const gchar *destination;
+ struct agent *agent;
+
+ debug("bonding_request_new(%s)", address);
+
+ device = adapter_get_device(conn, adapter, address);
+ if (!device)
+ return NULL;
+
+ destination = device_get_address(device);
+ agent = agent_create(adapter, name, agent_path,
+ capability,
+ device_agent_removed,
+ device);
+
+ device_set_agent(device, agent);
+
+ debug("Temporary agent registered for hci%d/%s at %s:%s",
+ adapter->dev_id, destination, name,
+ agent_path);
+
+ bonding = g_new0(struct bonding_request_info, 1);
+
+ bonding->conn = dbus_connection_ref(conn);
+ bonding->msg = dbus_message_ref(msg);
+ bonding->adapter = adapter;
+
+ str2ba(address, &bonding->bdaddr);
+
+ return bonding;
+}
+
+const char *mode2str(uint8_t mode)
+{
+ switch(mode) {
+ case MODE_OFF:
+ return "off";
+ case MODE_CONNECTABLE:
+ return "connectable";
+ case MODE_DISCOVERABLE:
+ return "discoverable";
+ case MODE_LIMITED:
+ return "limited";
+ default:
+ return "unknown";
+ }
+}
+
+static uint8_t on_mode(const char *addr)
+{
+ char mode[14];
+ bdaddr_t sba;
+
+ str2ba(addr, &sba);
+
+ if (read_on_mode(&sba, mode, sizeof(mode)) < 0)
+ return MODE_CONNECTABLE;
+
+ return str2mode(addr, mode);
+}
+
+uint8_t str2mode(const char *addr, 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("limited", mode) == 0)
+ return MODE_LIMITED;
+ else if (strcasecmp("on", mode) == 0)
+ return on_mode(addr);
+ else
+ return MODE_UNKNOWN;
+}
+
+static DBusMessage *set_mode(DBusConnection *conn, DBusMessage *msg,
+ uint8_t new_mode, void *data)
+{
+ struct adapter *adapter = data;
+ uint8_t scan_enable;
+ uint8_t current_scan = adapter->scan_mode;
+ bdaddr_t local;
+ gboolean limited;
+ int err, dd;
+ const char *mode;
+
+ switch(new_mode) {
+ case MODE_OFF:
+ scan_enable = SCAN_DISABLED;
+ break;
+ case MODE_CONNECTABLE:
+ scan_enable = SCAN_PAGE;
+ break;
+ case MODE_DISCOVERABLE:
+ case MODE_LIMITED:
+ scan_enable = (SCAN_PAGE | SCAN_INQUIRY);
+ break;
+ default:
+ return invalid_args(msg);
+ }
+
+ /* Do reverse resolution in case of "on" mode */
+ mode = mode2str(new_mode);
+
+ dd = hci_open_dev(adapter->dev_id);
+ if (dd < 0)
+ return no_such_adapter(msg);
+
+ if (!adapter->up &&
+ (hcid.offmode == HCID_OFFMODE_NOSCAN ||
+ (hcid.offmode == HCID_OFFMODE_DEVDOWN &&
+ scan_enable != SCAN_DISABLED))) {
+ /* Start HCI device */
+ if (ioctl(dd, HCIDEVUP, adapter->dev_id) == 0)
+ goto done; /* on success */
+
+ if (errno != EALREADY) {
+ err = errno;
+ error("Can't init device hci%d: %s (%d)\n",
+ adapter->dev_id, strerror(errno), errno);
+
+ hci_close_dev(dd);
+ return failed_strerror(msg, err);
+ }
+ }
+
+ if (adapter->up && scan_enable == SCAN_DISABLED &&
+ hcid.offmode == HCID_OFFMODE_DEVDOWN) {
+ if (ioctl(dd, HCIDEVDOWN, adapter->dev_id) < 0) {
+ hci_close_dev(dd);
+ return failed_strerror(msg, errno);
+ }
+
+ goto done;
+ }
+
+ limited = (new_mode == MODE_LIMITED ? TRUE : FALSE);
+ err = set_limited_discoverable(dd, adapter->dev.class, limited);
+ if (err < 0) {
+ hci_close_dev(dd);
+ return failed_strerror(msg, -err);
+ }
+
+ if (current_scan != scan_enable) {
+ struct hci_request rq;
+ uint8_t status = 0;
+
+ memset(&rq, 0, sizeof(rq));
+ rq.ogf = OGF_HOST_CTL;
+ rq.ocf = OCF_WRITE_SCAN_ENABLE;
+ rq.cparam = &scan_enable;
+ rq.clen = sizeof(scan_enable);
+ rq.rparam = &status;
+ rq.rlen = sizeof(status);
+ rq.event = EVT_CMD_COMPLETE;
+
+ if (hci_send_req(dd, &rq, 1000) < 0) {
+ err = errno;
+ error("Sending write scan enable command failed: %s (%d)",
+ strerror(errno), errno);
+ hci_close_dev(dd);
+ return failed_strerror(msg, err);
+ }
+
+ if (status) {
+ error("Setting scan enable failed with status 0x%02x",
+ status);
+ hci_close_dev(dd);
+ return failed_strerror(msg, bt_error(status));
+ }
+ } else {
+ /* discoverable or limited */
+ if ((scan_enable & SCAN_INQUIRY) && (new_mode != adapter->mode)) {
+ if (adapter->discov_timeout_id)
+ g_source_remove(adapter->discov_timeout_id);
+
+ if (!adapter->sessions && !adapter->discov_timeout)
+ adapter->discov_timeout_id = g_timeout_add(adapter->discov_timeout * 1000,
+ discov_timeout_handler, adapter);
+ }
+ }
+done:
+ str2ba(adapter->address, &local);
+ write_device_mode(&local, mode);
+
+ hci_close_dev(dd);
+
+ adapter->mode = new_mode;
+
+ return dbus_message_new_method_return(msg);
+}
+
+gint find_session(struct mode_req *req, DBusMessage *msg)
+{
+ const char *name = dbus_message_get_sender(req->msg);
+ const char *sender = dbus_message_get_sender(msg);
+
+ return strcmp(name, sender);
+}
+
+static void confirm_mode_cb(struct agent *agent, DBusError *err, void *data)
+{
+ struct mode_req *req = data;
+ DBusMessage *reply;
+
+ if (err && dbus_error_is_set(err)) {
+ reply = dbus_message_new_error(req->msg, err->name, err->message);
+ dbus_connection_send(req->conn, reply, NULL);
+ dbus_message_unref(reply);
+ goto cleanup;
+ }
+
+ reply = set_mode(req->conn, req->msg, req->mode, req->adapter);
+ dbus_connection_send(req->conn, reply, NULL);
+ dbus_message_unref(reply);
+
+ if (!g_slist_find_custom(req->adapter->sessions, req->msg,
+ (GCompareFunc) find_session))
+ goto cleanup;
+
+ return;
+
+cleanup:
+ dbus_message_unref(req->msg);
+ if (req->id)
+ g_dbus_remove_watch(req->conn, req->id);
+ dbus_connection_unref(req->conn);
+ g_free(req);
+}
+
+static DBusMessage *confirm_mode(DBusConnection *conn, DBusMessage *msg,
+ const char *mode, void *data)
+{
+ struct adapter *adapter = data;
+ struct mode_req *req;
+ int ret;
+
+ if (!adapter->agent)
+ return dbus_message_new_method_return(msg);
+
+ req = g_new0(struct mode_req, 1);
+ req->adapter = adapter;
+ req->conn = dbus_connection_ref(conn);
+ req->msg = dbus_message_ref(msg);
+ req->mode = str2mode(adapter->address, mode);
+
+ ret = agent_confirm_mode_change(adapter->agent, mode, confirm_mode_cb,
+ req);
+ if (ret < 0) {
+ dbus_connection_unref(req->conn);
+ dbus_message_unref(req->msg);
+ g_free(req);
+ return invalid_args(msg);
+ }
+
+ return NULL;
+}
+
+static DBusMessage *set_discoverable_timeout(DBusConnection *conn,
+ DBusMessage *msg,
+ uint32_t timeout,
+ void *data)
+{
+ struct adapter *adapter = data;
+ bdaddr_t bdaddr;
+ const char *path;
+
+ if (adapter->discov_timeout_id) {
+ g_source_remove(adapter->discov_timeout_id);
+ adapter->discov_timeout_id = 0;
+ }
+
+ if ((timeout != 0) && (adapter->scan_mode & SCAN_INQUIRY))
+ adapter->discov_timeout_id = g_timeout_add(timeout * 1000,
+ discov_timeout_handler,
+ adapter);
+
+ adapter->discov_timeout = timeout;
+
+ str2ba(adapter->address, &bdaddr);
+ write_discoverable_timeout(&bdaddr, timeout);
+
+ path = dbus_message_get_path(msg);
+
+ dbus_connection_emit_property_changed(conn, path,
+ ADAPTER_INTERFACE,
+ "DiscoverableTimeout",
+ DBUS_TYPE_UINT32, &timeout);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static void update_ext_inquiry_response(int dd, struct hci_dev *dev)
+{
+ uint8_t fec = 0, data[240];
+
+ if (!(dev->features[6] & LMP_EXT_INQ))
+ return;
+
+ memset(data, 0, sizeof(data));
+
+ if (dev->ssp_mode > 0)
+ create_ext_inquiry_response((char *) dev->name, data);
+
+ if (hci_write_ext_inquiry_response(dd, fec, data, 2000) < 0)
+ error("Can't write extended inquiry response: %s (%d)",
+ strerror(errno), errno);
+}
+
+static int adapter_set_name(struct adapter *adapter, const char *name)
+{
+ struct hci_dev *dev = &adapter->dev;
+ int dd, err;
+ bdaddr_t bdaddr;
+
+ str2ba(adapter->address, &bdaddr);
+
+ write_local_name(&bdaddr, (char *) name);
+
+ if (!adapter->up)
+ return 0;
+
+ dd = hci_open_dev(adapter->dev_id);
+ if (dd < 0) {
+ err = errno;
+ error("Can't open device hci%d: %s (%d)",
+ adapter->dev_id, strerror(err), err);
+ return -err;
+ }
+
+ if (hci_write_local_name(dd, name, 5000) < 0) {
+ err = errno;
+ error("Can't write name for hci%d: %s (%d)",
+ adapter->dev_id, strerror(err), err);
+ hci_close_dev(dd);
+ return -err;
+ }
+
+ strncpy((char *) dev->name, name, 248);
+
+ update_ext_inquiry_response(dd, dev);
+
+ hci_close_dev(dd);
+
+ return 0;
+}
+
+static DBusMessage *set_name(DBusConnection *conn, DBusMessage *msg,
+ const char *name, void *data)
+{
+ struct adapter *adapter = data;
+ int ecode;
+ const char *path;
+
+ if (!g_utf8_validate(name, -1, NULL)) {
+ error("Name change failed: the supplied name isn't valid UTF-8");
+ return invalid_args(msg);
+ }
+
+ ecode = adapter_set_name(adapter, name);
+ if (ecode < 0)
+ return failed_strerror(msg, -ecode);
+
+ path = dbus_message_get_path(msg);
+
+ dbus_connection_emit_property_changed(conn, path,
+ ADAPTER_INTERFACE,
+ "Name", DBUS_TYPE_STRING,
+ &name);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static void reply_authentication_failure(struct bonding_request_info *bonding)
+{
+ DBusMessage *reply;
+ int status;
+
+ status = bonding->hci_status ?
+ bonding->hci_status : HCI_AUTHENTICATION_FAILURE;
+
+ reply = new_authentication_return(bonding->msg, status);
+ if (reply) {
+ dbus_connection_send(bonding->conn, reply, NULL);
+ dbus_message_unref(reply);
+ }
+}
+
+struct btd_device *adapter_find_device(struct 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;
+}
+
+struct btd_device *adapter_create_device(DBusConnection *conn,
+ struct adapter *adapter, const char *address)
+{
+ struct btd_device *device;
+
+ debug("adapter_create_device(%s)", address);
+
+ device = device_create(conn, adapter, address);
+ if (!device)
+ return NULL;
+
+ device_set_temporary(device, TRUE);
+
+ adapter->devices = g_slist_append(adapter->devices, device);
+
+ return device;
+}
+
+static DBusMessage *remove_bonding(DBusConnection *conn, DBusMessage *msg,
+ const char *address, void *data)
+{
+ struct adapter *adapter = data;
+ struct btd_device *device;
+ char filename[PATH_MAX + 1];
+ char *str;
+ bdaddr_t src, dst;
+ GSList *l;
+ int dev, err;
+ gboolean paired;
+
+ str2ba(adapter->address, &src);
+ str2ba(address, &dst);
+
+ dev = hci_open_dev(adapter->dev_id);
+ if (dev < 0 && msg)
+ return no_such_adapter(msg);
+
+ create_name(filename, PATH_MAX, STORAGEDIR, adapter->address,
+ "linkkeys");
+
+ /* textfile_del doesn't return an error when the key is not found */
+ str = textfile_caseget(filename, address);
+ paired = str ? TRUE : FALSE;
+ g_free(str);
+
+ if (!paired && msg) {
+ hci_close_dev(dev);
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".DoesNotExist",
+ "Bonding does not exist");
+ }
+
+ /* Delete the link key from storage */
+ if (textfile_casedel(filename, address) < 0 && msg) {
+ hci_close_dev(dev);
+ err = errno;
+ return failed_strerror(msg, err);
+ }
+
+ /* Delete the link key from the Bluetooth chip */
+ hci_delete_stored_link_key(dev, &dst, 0, 1000);
+
+ /* find the connection */
+ l = g_slist_find_custom(adapter->active_conn, &dst,
+ active_conn_find_by_bdaddr);
+ if (l) {
+ struct active_conn_info *con = l->data;
+ /* Send the HCI disconnect command */
+ if ((hci_disconnect(dev, htobs(con->handle),
+ HCI_OE_USER_ENDED_CONNECTION, 500) < 0)
+ && msg){
+ int err = errno;
+ error("Disconnect failed");
+ hci_close_dev(dev);
+ return failed_strerror(msg, err);
+ }
+ }
+
+ hci_close_dev(dev);
+
+ device = adapter_find_device(adapter, address);
+ if (!device)
+ goto proceed;
+
+ if (paired) {
+ gboolean paired = FALSE;
+
+ const gchar *dev_path = device_get_path(device);
+
+ dbus_connection_emit_property_changed(conn, dev_path,
+ DEVICE_INTERFACE, "Paired",
+ DBUS_TYPE_BOOLEAN, &paired);
+ }
+
+proceed:
+ if(!msg)
+ goto done;
+
+ return dbus_message_new_method_return(msg);
+
+done:
+ return NULL;
+}
+
+
+void adapter_remove_device(DBusConnection *conn, struct adapter *adapter,
+ struct btd_device *device)
+{
+ bdaddr_t src;
+ const gchar *destination = device_get_address(device);
+ const gchar *dev_path = device_get_path(device);
+ struct agent *agent;
+
+ str2ba(adapter->address, &src);
+ delete_entry(&src, "profiles", destination);
+
+ remove_bonding(conn, NULL, destination, adapter);
+
+ if (!device_is_temporary(device)) {
+ 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) {
+ agent_destroy(agent, FALSE);
+ device_set_agent(device, NULL);
+ }
+
+ adapter->devices = g_slist_remove(adapter->devices, device);
+
+ device_remove(conn, device);
+}
+
+struct btd_device *adapter_get_device(DBusConnection *conn,
+ struct adapter *adapter, const gchar *address)
+{
+ struct btd_device *device;
+
+ debug("adapter_get_device(%s)", address);
+
+ if (!adapter)
+ return NULL;
+
+ device = adapter_find_device(adapter, address);
+ if (device)
+ return device;
+
+ return adapter_create_device(conn, adapter, address);
+}
+
+void remove_pending_device(struct adapter *adapter)
+{
+ struct btd_device *device;
+ char address[18];
+
+ ba2str(&adapter->bonding->bdaddr, address);
+ device = adapter_find_device(adapter, address);
+ if (!device)
+ return;
+
+ if (device_is_temporary(device))
+ adapter_remove_device(adapter->bonding->conn, adapter, device);
+}
+
+static gboolean create_bonding_conn_complete(GIOChannel *io, GIOCondition cond,
+ struct adapter *adapter)
+{
+ struct hci_request rq;
+ auth_requested_cp cp;
+ evt_cmd_status rp;
+ struct l2cap_conninfo cinfo;
+ socklen_t len;
+ int sk, dd, ret;
+
+ if (!adapter->bonding) {
+ /* If we come here it implies a bug somewhere */
+ debug("create_bonding_conn_complete: no pending bonding!");
+ g_io_channel_close(io);
+ g_io_channel_unref(io);
+ return FALSE;
+ }
+
+ if (cond & G_IO_NVAL) {
+ DBusMessage *reply;
+ reply = new_authentication_return(adapter->bonding->msg, 0x09);
+ g_dbus_send_message(adapter->bonding->conn, reply);
+ goto cleanup;
+ }
+
+ if (cond & (G_IO_HUP | G_IO_ERR)) {
+ debug("Hangup or error on bonding IO channel");
+
+ if (!adapter->bonding->auth_active)
+ error_connection_attempt_failed(adapter->bonding->conn,
+ adapter->bonding->msg,
+ ENETDOWN);
+ else
+ reply_authentication_failure(adapter->bonding);
+
+ goto failed;
+ }
+
+ sk = g_io_channel_unix_get_fd(io);
+
+ len = sizeof(ret);
+ if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &ret, &len) < 0) {
+ error("Can't get socket error: %s (%d)",
+ strerror(errno), errno);
+ error_failed_errno(adapter->bonding->conn, adapter->bonding->msg,
+ errno);
+ goto failed;
+ }
+
+ if (ret != 0) {
+ if (adapter->bonding->auth_active)
+ reply_authentication_failure(adapter->bonding);
+ else
+ error_connection_attempt_failed(adapter->bonding->conn,
+ adapter->bonding->msg,
+ ret);
+ goto failed;
+ }
+
+ len = sizeof(cinfo);
+ if (getsockopt(sk, SOL_L2CAP, L2CAP_CONNINFO, &cinfo, &len) < 0) {
+ error("Can't get connection info: %s (%d)",
+ strerror(errno), errno);
+ error_failed_errno(adapter->bonding->conn, adapter->bonding->msg,
+ errno);
+ goto failed;
+ }
+
+ dd = hci_open_dev(adapter->dev_id);
+ if (dd < 0) {
+ DBusMessage *reply = no_such_adapter(adapter->bonding->msg);
+ g_dbus_send_message(adapter->bonding->conn, reply);
+ goto failed;
+ }
+
+ memset(&rp, 0, sizeof(rp));
+
+ memset(&cp, 0, sizeof(cp));
+ cp.handle = htobs(cinfo.hci_handle);
+
+ memset(&rq, 0, sizeof(rq));
+ rq.ogf = OGF_LINK_CTL;
+ rq.ocf = OCF_AUTH_REQUESTED;
+ rq.cparam = &cp;
+ rq.clen = AUTH_REQUESTED_CP_SIZE;
+ rq.rparam = &rp;
+ rq.rlen = EVT_CMD_STATUS_SIZE;
+ rq.event = EVT_CMD_STATUS;
+
+ if (hci_send_req(dd, &rq, 500) < 0) {
+ error("Unable to send HCI request: %s (%d)",
+ strerror(errno), errno);
+ error_failed_errno(adapter->bonding->conn, adapter->bonding->msg,
+ errno);
+ hci_close_dev(dd);
+ goto failed;
+ }
+
+ if (rp.status) {
+ error("HCI_Authentication_Requested failed with status 0x%02x",
+ rp.status);
+ error_failed_errno(adapter->bonding->conn, adapter->bonding->msg,
+ bt_error(rp.status));
+ hci_close_dev(dd);
+ goto failed;
+ }
+
+ hci_close_dev(dd);
+
+ adapter->bonding->auth_active = 1;
+
+ adapter->bonding->io_id = g_io_add_watch(io,
+ G_IO_NVAL | G_IO_HUP | G_IO_ERR,
+ (GIOFunc) create_bonding_conn_complete,
+ adapter);
+
+ return FALSE;
+
+failed:
+ g_io_channel_close(io);
+ remove_pending_device(adapter);
+
+cleanup:
+ g_dbus_remove_watch(adapter->bonding->conn,
+ adapter->bonding->listener_id);
+ bonding_request_free(adapter->bonding);
+ adapter->bonding = NULL;
+
+ return FALSE;
+}
+
+static void cancel_auth_request(struct pending_auth_info *auth, int dev_id)
+{
+ int dd;
+
+ if (auth->replied)
+ return;
+
+ dd = hci_open_dev(dev_id);
+ if (dd < 0) {
+ error("hci_open_dev: %s (%d)", strerror(errno), errno);
+ return;
+ }
+
+ switch (auth->type) {
+ case AUTH_TYPE_PINCODE:
+ hci_send_cmd(dd, OGF_LINK_CTL, OCF_PIN_CODE_NEG_REPLY,
+ 6, &auth->bdaddr);
+ break;
+ case AUTH_TYPE_CONFIRM:
+ hci_send_cmd(dd, OGF_LINK_CTL, OCF_USER_CONFIRM_NEG_REPLY,
+ 6, &auth->bdaddr);
+ break;
+ case AUTH_TYPE_PASSKEY:
+ hci_send_cmd(dd, OGF_LINK_CTL, OCF_USER_PASSKEY_NEG_REPLY,
+ 6, &auth->bdaddr);
+ break;
+ case AUTH_TYPE_NOTIFY:
+ /* User Notify doesn't require any reply */
+ break;
+ }
+
+ auth->replied = TRUE;
+
+ hci_close_dev(dd);
+}
+
+static void create_bond_req_exit(void *user_data)
+{
+ struct adapter *adapter = user_data;
+ struct pending_auth_info *auth;
+
+ debug("CreateConnection requestor exited before bonding was completed");
+
+ auth = adapter_find_auth_request(adapter, &adapter->bonding->bdaddr);
+ if (auth) {
+ cancel_auth_request(auth, adapter->dev_id);
+ if (auth->agent)
+ agent_cancel(auth->agent);
+ adapter_remove_auth_request(adapter, &adapter->bonding->bdaddr);
+ }
+
+ remove_pending_device(adapter);
+
+ g_io_channel_close(adapter->bonding->io);
+ if (adapter->bonding->io_id)
+ g_source_remove(adapter->bonding->io_id);
+ bonding_request_free(adapter->bonding);
+ adapter->bonding = NULL;
+}
+
+static DBusMessage *create_bonding(DBusConnection *conn, DBusMessage *msg,
+ const char *address, const char *agent_path,
+ uint8_t capability, void *data)
+{
+ char filename[PATH_MAX + 1];
+ char *str;
+ struct adapter *adapter = data;
+ struct bonding_request_info *bonding;
+ bdaddr_t bdaddr;
+ int sk;
+
+ str2ba(address, &bdaddr);
+
+ /* check if there is a pending discover: requested by D-Bus/non clients */
+ if (adapter->discov_active)
+ return in_progress(msg, "Discover in progress");
+
+ pending_remote_name_cancel(adapter);
+
+ if (adapter->bonding)
+ return in_progress(msg, "Bonding in progress");
+
+ if (adapter_find_auth_request(adapter, &bdaddr))
+ return in_progress(msg, "Bonding in progress");
+
+ /* check if a link key already exists */
+ create_name(filename, PATH_MAX, STORAGEDIR, adapter->address,
+ "linkkeys");
+
+ str = textfile_caseget(filename, address);
+ if (str) {
+ free(str);
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".AlreadyExists",
+ "Bonding already exists");
+ }
+
+ sk = l2raw_connect(adapter->address, &bdaddr);
+ if (sk < 0)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".ConnectionAttemptFailed",
+ "Connection attempt failed");
+
+ bonding = bonding_request_new(conn, msg, adapter, address, agent_path,
+ capability);
+ if (!bonding) {
+ close(sk);
+ return NULL;
+ }
+
+ bonding->io = g_io_channel_unix_new(sk);
+ bonding->io_id = g_io_add_watch(bonding->io,
+ G_IO_OUT | G_IO_NVAL | G_IO_HUP | G_IO_ERR,
+ (GIOFunc) create_bonding_conn_complete,
+ adapter);
+
+ bonding->listener_id = g_dbus_add_disconnect_watch(conn,
+ dbus_message_get_sender(msg),
+ create_bond_req_exit, adapter,
+ NULL);
+
+ adapter->bonding = bonding;
+
+ return NULL;
+}
+
+static void periodic_discover_req_exit(void *user_data)
+{
+ struct adapter *adapter = user_data;
+
+ debug("PeriodicDiscovery requestor exited");
+
+ /* Cleanup the discovered devices list and send the cmd to exit from
+ * periodic inquiry or cancel remote name request. The return value can
+ * be ignored. */
+
+ cancel_periodic_discovery(adapter);
+}
+
+static DBusMessage *adapter_start_periodic(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ periodic_inquiry_cp cp;
+ struct hci_request rq;
+ struct adapter *adapter = data;
+ uint8_t lap[3] = { 0x33, 0x8b, 0x9e };
+ uint8_t status;
+ int dd;
+
+ if (!adapter->up)
+ return adapter_not_ready(msg);
+
+ if (dbus_message_is_method_call(msg, ADAPTER_INTERFACE,
+ "StartPeriodicDiscovery")) {
+ if (!dbus_message_has_signature(msg,
+ DBUS_TYPE_INVALID_AS_STRING))
+ return invalid_args(msg);
+ }
+
+ if (adapter->discov_active || adapter->pdiscov_active)
+ return in_progress(msg, "Discover in progress");
+
+ pending_remote_name_cancel(adapter);
+
+ dd = hci_open_dev(adapter->dev_id);
+ if (dd < 0)
+ return no_such_adapter(msg);
+
+ memset(&cp, 0, sizeof(cp));
+ memcpy(&cp.lap, lap, 3);
+ cp.max_period = htobs(24);
+ cp.min_period = htobs(16);
+ cp.length = 0x08;
+ cp.num_rsp = 0x00;
+
+ memset(&rq, 0, sizeof(rq));
+ rq.ogf = OGF_LINK_CTL;
+ rq.ocf = OCF_PERIODIC_INQUIRY;
+ rq.cparam = &cp;
+ rq.clen = PERIODIC_INQUIRY_CP_SIZE;
+ rq.rparam = &status;
+ rq.rlen = sizeof(status);
+ rq.event = EVT_CMD_COMPLETE;
+
+ if (hci_send_req(dd, &rq, 1000) < 0) {
+ int err = errno;
+ error("Unable to start periodic inquiry: %s (%d)",
+ strerror(errno), errno);
+ hci_close_dev(dd);
+ return failed_strerror(msg, err);
+ }
+
+ if (status) {
+ error("HCI_Periodic_Inquiry_Mode failed with status 0x%02x",
+ status);
+ hci_close_dev(dd);
+ return failed_strerror(msg, bt_error(status));
+ }
+
+ adapter->pdiscov_requestor = g_strdup(dbus_message_get_sender(msg));
+
+ if (adapter->pdiscov_resolve_names)
+ adapter->discov_type = PERIODIC_INQUIRY | RESOLVE_NAME;
+ else
+ adapter->discov_type = PERIODIC_INQUIRY;
+
+ hci_close_dev(dd);
+
+ /* track the request owner to cancel it automatically if the owner
+ * exits */
+ adapter->pdiscov_listener = g_dbus_add_disconnect_watch(conn,
+ dbus_message_get_sender(msg),
+ periodic_discover_req_exit,
+ adapter, NULL);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *adapter_stop_periodic(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct adapter *adapter = data;
+ int err;
+
+ if (!adapter->up)
+ return adapter_not_ready(msg);
+
+ if (!adapter->pdiscov_active)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".NotAuthorized",
+ "Not authorized");
+ /*
+ * Cleanup the discovered devices list and send the cmd to exit
+ * from periodic inquiry mode or cancel remote name request.
+ */
+ err = cancel_periodic_discovery(adapter);
+ if (err < 0) {
+ if (err == -ENODEV)
+ return no_such_adapter(msg);
+
+ else
+ return failed_strerror(msg, -err);
+ }
+
+ return dbus_message_new_method_return(msg);
+}
+
+static void discover_devices_req_exit(void *user_data)
+{
+ struct adapter *adapter = user_data;
+
+ debug("DiscoverDevices requestor exited");
+
+ /* Cleanup the discovered devices list and send the command to cancel
+ * inquiry or cancel remote name request. The return can be ignored. */
+ cancel_discovery(adapter);
+}
+
+static DBusMessage *adapter_discover_devices(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ inquiry_cp cp;
+ evt_cmd_status rp;
+ struct hci_request rq;
+ struct adapter *adapter = data;
+ uint8_t lap[3] = { 0x33, 0x8b, 0x9e };
+ int dd;
+
+ if (!adapter->up)
+ return adapter_not_ready(msg);
+
+ if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING))
+ return invalid_args(msg);
+
+ if (adapter->discov_active)
+ return in_progress(msg, "Discover in progress");
+
+ pending_remote_name_cancel(adapter);
+
+ if (adapter->bonding)
+ return in_progress(msg, "Bonding in progress");
+
+ dd = hci_open_dev(adapter->dev_id);
+ if (dd < 0)
+ return no_such_adapter(msg);
+
+ memset(&cp, 0, sizeof(cp));
+ memcpy(&cp.lap, lap, 3);
+ cp.length = 0x08;
+ cp.num_rsp = 0x00;
+
+ memset(&rq, 0, sizeof(rq));
+ rq.ogf = OGF_LINK_CTL;
+ rq.ocf = OCF_INQUIRY;
+ rq.cparam = &cp;
+ rq.clen = INQUIRY_CP_SIZE;
+ rq.rparam = &rp;
+ rq.rlen = EVT_CMD_STATUS_SIZE;
+ rq.event = EVT_CMD_STATUS;
+
+ if (hci_send_req(dd, &rq, 500) < 0) {
+ int err = errno;
+ error("Unable to start inquiry: %s (%d)",
+ strerror(errno), errno);
+ hci_close_dev(dd);
+ return failed_strerror(msg, err);
+ }
+
+ if (rp.status) {
+ error("HCI_Inquiry command failed with status 0x%02x",
+ rp.status);
+ hci_close_dev(dd);
+ return failed_strerror(msg, bt_error(rp.status));
+ }
+
+ adapter->discov_type |= (STD_INQUIRY | RESOLVE_NAME);
+
+ adapter->discov_requestor = g_strdup(dbus_message_get_sender(msg));
+
+ hci_close_dev(dd);
+
+ /* track the request owner to cancel it automatically if the owner
+ * exits */
+ adapter->discov_listener = g_dbus_add_disconnect_watch(conn,
+ dbus_message_get_sender(msg),
+ discover_devices_req_exit,
+ adapter, NULL);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *adapter_cancel_discovery(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct adapter *adapter = data;
+ int err;
+
+ if (!adapter->up)
+ return adapter_not_ready(msg);
+
+ if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING))
+ return invalid_args(msg);
+
+ /* is there discover pending? or discovery cancel was requested
+ * previously */
+ if (!adapter->discov_active || adapter->discovery_cancel)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".NotAuthorized",
+ "Not Authorized");
+
+ /* only the discover requestor can cancel the inquiry process */
+ if (!adapter->discov_requestor ||
+ strcmp(adapter->discov_requestor, dbus_message_get_sender(msg)))
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".NotAuthorized",
+ "Not Authorized");
+
+ /* Cleanup the discovered devices list and send the cmd to cancel
+ * inquiry or cancel remote name request */
+ err = cancel_discovery(adapter);
+ if (err < 0) {
+ if (err == -ENODEV)
+ return no_such_adapter(msg);
+ else
+ return failed_strerror(msg, -err);
+ }
+
+ /* Reply before send DiscoveryCompleted */
+ adapter->discovery_cancel = dbus_message_ref(msg);
+
+ return NULL;
+}
+
+struct remote_device_list_t {
+ GSList *list;
+ time_t time;
+};
+
+static DBusMessage *get_properties(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct adapter *adapter = data;
+ const char *property;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ bdaddr_t ba;
+ char str[249];
+
+ if (check_address(adapter->address) < 0)
+ return adapter_not_ready(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 = adapter->address;
+ dbus_message_iter_append_dict_entry(&dict, "Address",
+ DBUS_TYPE_STRING, &property);
+
+ /* Name */
+ memset(str, 0, sizeof(str));
+ property = str;
+ str2ba(adapter->address, &ba);
+
+ if (!read_local_name(&ba, str))
+ dbus_message_iter_append_dict_entry(&dict, "Name",
+ DBUS_TYPE_STRING, &property);
+
+ /* Mode */
+ property = mode2str(adapter->mode);
+
+ dbus_message_iter_append_dict_entry(&dict, "Mode",
+ DBUS_TYPE_STRING, &property);
+
+ /* DiscoverableTimeout */
+ dbus_message_iter_append_dict_entry(&dict, "DiscoverableTimeout",
+ DBUS_TYPE_UINT32, &adapter->discov_timeout);
+
+ /* PeriodicDiscovery */
+ dbus_message_iter_append_dict_entry(&dict, "PeriodicDiscovery",
+ DBUS_TYPE_BOOLEAN, &adapter->pdiscov_active);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static DBusMessage *set_property(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct adapter *adapter = data;
+ DBusMessageIter iter;
+ DBusMessageIter sub;
+ const char *property;
+
+ if (!dbus_message_iter_init(msg, &iter))
+ return invalid_args(msg);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+ return 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 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 invalid_args(msg);
+ dbus_message_iter_get_basic(&sub, &name);
+
+ return set_name(conn, msg, name, data);
+ } else if (g_str_equal("DiscoverableTimeout", property)) {
+ uint32_t timeout;
+
+ if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT32)
+ return invalid_args(msg);
+ dbus_message_iter_get_basic(&sub, &timeout);
+
+ return set_discoverable_timeout(conn, msg, timeout, data);
+ } else if (g_str_equal("PeriodicDiscovery", property)) {
+ dbus_bool_t value;
+
+ if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
+ return invalid_args(msg);
+ dbus_message_iter_get_basic(&sub, &value);
+
+ if (value)
+ return adapter_start_periodic(conn, msg, data);
+ else
+ return adapter_stop_periodic(conn, msg, data);
+ } else if (g_str_equal("Mode", property)) {
+ const char *mode;
+
+ if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING)
+ return invalid_args(msg);
+
+ dbus_message_iter_get_basic(&sub, &mode);
+
+ adapter->global_mode = str2mode(adapter->address, mode);
+
+ if (adapter->global_mode == adapter->mode)
+ return dbus_message_new_method_return(msg);
+
+ if (adapter->sessions && adapter->global_mode < adapter->mode)
+ return confirm_mode(conn, msg, mode, data);
+
+ return set_mode(conn, msg, str2mode(adapter->address, mode),
+ data);
+ }
+
+ return invalid_args(msg);
+}
+
+static void session_exit(void *data)
+{
+ struct mode_req *req = data;
+ struct adapter *adapter = req->adapter;
+
+ adapter->sessions = g_slist_remove(adapter->sessions, req);
+
+ if (!adapter->sessions) {
+ debug("Falling back to '%s' mode", mode2str(adapter->global_mode));
+ /* FIXME: fallback to previous mode
+ set_mode(req->conn, req->msg, adapter->global_mode, adapter);
+ */
+ }
+ dbus_connection_unref(req->conn);
+ dbus_message_unref(req->msg);
+ g_free(req);
+}
+
+static DBusMessage *request_mode(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ const char *mode;
+ struct adapter *adapter = data;
+ struct mode_req *req;
+ uint8_t new_mode;
+ int ret;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &mode,
+ DBUS_TYPE_INVALID))
+ return invalid_args(msg);
+
+ new_mode = str2mode(adapter->address, mode);
+ if (new_mode != MODE_CONNECTABLE && new_mode != MODE_DISCOVERABLE)
+ return invalid_args(msg);
+
+ if (!adapter->agent)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "No agent registered");
+
+ if (g_slist_find_custom(adapter->sessions, msg,
+ (GCompareFunc) find_session))
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "Mode already requested");
+
+ req = g_new0(struct mode_req, 1);
+ req->adapter = adapter;
+ req->conn = dbus_connection_ref(conn);
+ req->msg = dbus_message_ref(msg);
+ req->mode = new_mode;
+ req->id = g_dbus_add_disconnect_watch(conn,
+ dbus_message_get_sender(msg),
+ session_exit, req, NULL);
+
+ if (!adapter->sessions)
+ adapter->global_mode = adapter->mode;
+ adapter->sessions = g_slist_append(adapter->sessions, req);
+
+ /* No need to change mode */
+ if (adapter->mode >= new_mode)
+ return dbus_message_new_method_return(msg);
+
+ ret = agent_confirm_mode_change(adapter->agent, mode, confirm_mode_cb,
+ req);
+ if (ret < 0) {
+ dbus_message_unref(req->msg);
+ g_dbus_remove_watch(req->conn, req->id);
+ dbus_connection_unref(req->conn);
+ g_free(req);
+ return invalid_args(msg);
+ }
+
+ return NULL;
+}
+
+static DBusMessage *release_mode(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct adapter *adapter = data;
+ GSList *l;
+
+ l = g_slist_find_custom(adapter->sessions, msg,
+ (GCompareFunc) find_session);
+ if (!l)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "No Mode to release");
+
+ session_exit(l->data);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *list_devices(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct 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 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;
+
+ if (device_is_temporary(device))
+ continue;
+
+ 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 *create_device(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct adapter *adapter = data;
+ struct btd_device *device;
+ const gchar *address;
+
+ if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address,
+ DBUS_TYPE_INVALID) == FALSE)
+ return invalid_args(msg);
+
+ if (check_address(address) < 0)
+ return invalid_args(msg);
+
+ if (adapter_find_device(adapter, address))
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".AlreadyExists",
+ "Device already exists");
+
+ debug("create_device(%s)", address);
+
+ device = device_create(conn, adapter, address);
+ if (!device)
+ return NULL;
+
+ device_set_temporary(device, FALSE);
+
+ device_browse(device, conn, msg, NULL);
+
+ adapter->devices = g_slist_append(adapter->devices, device);
+
+ return NULL;
+}
+
+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, "NoInputOutput"))
+ return IO_CAPABILITY_NOINPUTOUTPUT;
+ return IO_CAPABILITY_INVALID;
+}
+
+static DBusMessage *create_paired_device(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ const gchar *address, *agent_path, *capability;
+ uint8_t cap;
+
+ 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 invalid_args(msg);
+
+ if (check_address(address) < 0)
+ return invalid_args(msg);
+
+ cap = parse_io_capability(capability);
+ if (cap == IO_CAPABILITY_INVALID)
+ return invalid_args(msg);
+
+ return create_bonding(conn, msg, address, agent_path, cap, data);
+}
+
+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 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 invalid_args(msg);
+
+ l = g_slist_find_custom(adapter->devices,
+ path, (GCompareFunc) device_path_cmp);
+ if (!l)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".DoesNotExist",
+ "Device does not exist");
+ 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");
+
+ adapter_remove_device(conn, adapter, device);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *find_device(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct 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 invalid_args(msg);
+
+ l = g_slist_find_custom(adapter->devices,
+ address, (GCompareFunc) device_address_cmp);
+ if (!l)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".DoesNotExist",
+ "Device does not exist");
+
+ device = l->data;
+
+ if (device_is_temporary(device))
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".DoesNotExist",
+ "Device creation in progress");
+
+ 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 adapter *adapter)
+{
+ struct pending_auth_info *auth;
+ GSList *l;
+
+ adapter->agent = NULL;
+
+ l = g_slist_find_custom(adapter->auth_reqs, agent,
+ auth_info_agent_cmp);
+ if (!l)
+ return;
+
+ auth = l->data;
+ auth->agent = NULL;
+}
+
+static DBusMessage *register_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ const char *path, *name, *capability;
+ struct agent *agent;
+ struct 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 g_dbus_create_error(msg,
+ ERROR_INTERFACE ".AlreadyExists",
+ "Agent already exists");
+
+ cap = parse_io_capability(capability);
+ if (cap == IO_CAPABILITY_INVALID)
+ return 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 g_dbus_create_error(msg,
+ ERROR_INTERFACE ".Failed",
+ "Failed to create a new agent");
+
+ adapter->agent = agent;
+
+ debug("Agent registered for hci%d at %s:%s", adapter->dev_id, name,
+ path);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *unregister_agent(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ const char *path, *name;
+ struct 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 g_dbus_create_error(msg,
+ ERROR_INTERFACE ".DoesNotExist",
+ "No such agent");
+
+ agent_destroy(adapter->agent, FALSE);
+ adapter->agent = NULL;
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *add_service_record(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct adapter *adapter = data;
+ DBusMessage *reply;
+ const char *sender, *record;
+ dbus_uint32_t handle;
+ bdaddr_t src;
+ int err;
+
+ if (dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &record, DBUS_TYPE_INVALID) == FALSE)
+ return NULL;
+
+ sender = dbus_message_get_sender(msg);
+ str2ba(adapter->address, &src);
+ err = add_xml_record(conn, sender, &src, record, &handle);
+ if (err < 0)
+ return failed_strerror(msg, err);
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_append_args(reply, DBUS_TYPE_UINT32, &handle,
+ DBUS_TYPE_INVALID);
+
+ return reply;
+}
+
+static DBusMessage *update_service_record(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct adapter *adapter = data;
+ bdaddr_t src;
+
+ str2ba(adapter->address, &src);
+
+ return update_xml_record(conn, msg, &src);
+}
+
+static DBusMessage *remove_service_record(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ dbus_uint32_t handle;
+ const char *sender;
+
+ if (dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &handle,
+ DBUS_TYPE_INVALID) == FALSE)
+ return NULL;
+
+ sender = dbus_message_get_sender(msg);
+
+ if (remove_record(conn, sender, handle) < 0)
+ return not_available(msg);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *request_authorization(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ /* FIXME implement the request */
+
+ return NULL;
+}
+
+static DBusMessage *cancel_authorization(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ /* FIXME implement cancel request */
+
+ return dbus_message_new_method_return(msg);
+}
+
+/* BlueZ 4.0 API */
+static GDBusMethodTable adapter_methods[] = {
+ { "GetProperties", "", "a{sv}",get_properties },
+ { "SetProperty", "sv", "", set_property,
+ G_DBUS_METHOD_FLAG_ASYNC},
+ { "RequestMode", "s", "", request_mode,
+ G_DBUS_METHOD_FLAG_ASYNC},
+ { "ReleaseMode", "", "", release_mode },
+ { "DiscoverDevices", "", "", adapter_discover_devices},
+ { "CancelDiscovery", "", "", adapter_cancel_discovery,
+ G_DBUS_METHOD_FLAG_ASYNC},
+ { "ListDevices", "", "ao", list_devices },
+ { "CreateDevice", "s", "o", create_device,
+ G_DBUS_METHOD_FLAG_ASYNC},
+ { "CreatePairedDevice", "sos", "o", create_paired_device,
+ G_DBUS_METHOD_FLAG_ASYNC},
+ { "RemoveDevice", "o", "", remove_device },
+ { "FindDevice", "s", "o", find_device },
+ { "RegisterAgent", "os", "", register_agent },
+ { "UnregisterAgent", "o", "", unregister_agent },
+ { "AddServiceRecord", "s", "u", add_service_record },
+ { "UpdateServiceRecord","us", "", update_service_record },
+ { "RemoveServiceRecord","u", "", remove_service_record },
+ { "RequestAuthorization","su", "", request_authorization,
+ G_DBUS_METHOD_FLAG_ASYNC},
+ { "CancelAuthorization","", "", cancel_authorization },
+ { }
+};
+
+static GDBusSignalTable adapter_signals[] = {
+ { "DiscoveryStarted", "" },
+ { "DiscoveryCompleted", "" },
+ { "DeviceCreated", "o" },
+ { "DeviceRemoved", "o" },
+ { "DeviceFound", "sa{sv}" },
+ { "PropertyChanged", "sv" },
+ { "DeviceDisappeared", "s" },
+ { }
+};
+
+dbus_bool_t adapter_init(DBusConnection *conn,
+ const char *path, struct adapter *adapter)
+{
+ if (!connection)
+ connection = conn;
+
+ return g_dbus_register_interface(conn, path,
+ ADAPTER_INTERFACE, adapter_methods,
+ adapter_signals, NULL, adapter, NULL);
+}
+
+dbus_bool_t adapter_cleanup(DBusConnection *conn, const char *path)
+{
+ return g_dbus_unregister_interface(conn, path, ADAPTER_INTERFACE);
+}
+
+static inline uint8_t get_inquiry_mode(struct hci_dev *dev)
+{
+ if (dev->features[6] & LMP_EXT_INQ)
+ return 2;
+
+ if (dev->features[3] & LMP_RSSI_INQ)
+ return 1;
+
+ if (dev->manufacturer == 11 &&
+ dev->hci_rev == 0x00 && dev->lmp_subver == 0x0757)
+ return 1;
+
+ if (dev->manufacturer == 15) {
+ if (dev->hci_rev == 0x03 && dev->lmp_subver == 0x6963)
+ return 1;
+ if (dev->hci_rev == 0x09 && dev->lmp_subver == 0x6963)
+ return 1;
+ if (dev->hci_rev == 0x00 && dev->lmp_subver == 0x6965)
+ return 1;
+ }
+
+ if (dev->manufacturer == 31 &&
+ dev->hci_rev == 0x2005 && dev->lmp_subver == 0x1805)
+ return 1;
+
+ return 0;
+}
+
+static int device_read_bdaddr(uint16_t dev_id, const char *address)
+{
+ int dd, err;
+ bdaddr_t bdaddr;
+
+ dd = hci_open_dev(dev_id);
+ if (dd < 0) {
+ err = errno;
+ error("Can't open device hci%d: %s (%d)",
+ dev_id, strerror(err), err);
+ return -err;
+ }
+
+ str2ba(address, &bdaddr);
+ if (hci_read_bd_addr(dd, &bdaddr, 2000) < 0) {
+ err = errno;
+ error("Can't read address for hci%d: %s (%d)",
+ dev_id, strerror(err), err);
+ hci_close_dev(dd);
+ return -err;
+ }
+
+ hci_close_dev(dd);
+
+ return 0;
+}
+
+static int adapter_setup(struct adapter *adapter, int dd)
+{
+ struct hci_dev *dev = &adapter->dev;
+ uint8_t events[8] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x00 };
+ uint8_t inqmode;
+ bdaddr_t bdaddr;
+ int err;
+ char name[249];
+
+ if (dev->hci_rev > 1) {
+ if (dev->features[5] & LMP_SNIFF_SUBR)
+ events[5] |= 0x20;
+
+ if (dev->features[5] & LMP_PAUSE_ENC)
+ events[5] |= 0x80;
+
+ if (dev->features[6] & LMP_EXT_INQ)
+ events[5] |= 0x40;
+
+ if (dev->features[6] & LMP_NFLUSH_PKTS)
+ events[7] |= 0x01;
+
+ if (dev->features[7] & LMP_LSTO)
+ events[6] |= 0x80;
+
+ if (dev->features[6] & LMP_SIMPLE_PAIR) {
+ events[6] |= 0x01; /* IO Capability Request */
+ events[6] |= 0x02; /* IO Capability Response */
+ events[6] |= 0x04; /* User Confirmation Request */
+ events[6] |= 0x08; /* User Passkey Request */
+ events[6] |= 0x10; /* Remote OOB Data Request */
+ events[6] |= 0x20; /* Simple Pairing Complete */
+ events[7] |= 0x04; /* User Passkey Notification */
+ events[7] |= 0x08; /* Keypress Notification */
+ events[7] |= 0x10; /* Remote Host Supported Features Notification */
+ }
+
+ hci_send_cmd(dd, OGF_HOST_CTL, OCF_SET_EVENT_MASK,
+ sizeof(events), events);
+ }
+
+ str2ba(adapter->address, &bdaddr);
+ if (read_local_name(&bdaddr, name) == 0) {
+ memcpy(dev->name, name, 248);
+ hci_write_local_name(dd, name, 5000);
+ }
+
+ update_ext_inquiry_response(dd, dev);
+
+ inqmode = get_inquiry_mode(dev);
+ if (inqmode < 1)
+ return 0;
+
+ if (hci_write_inquiry_mode(dd, inqmode, 2000) < 0) {
+ err = errno;
+ error("Can't write inquiry mode for %s: %s (%d)",
+ adapter->path, strerror(err), err);
+ hci_close_dev(dd);
+ return -err;
+ }
+
+ return 0;
+}
+
+static int active_conn_append(GSList **list, bdaddr_t *bdaddr,
+ uint16_t handle)
+{
+ struct active_conn_info *dev;
+
+ dev = g_new0(struct active_conn_info, 1);
+
+ bacpy(&dev->bdaddr, bdaddr);
+ dev->handle = handle;
+
+ *list = g_slist_append(*list, dev);
+ return 0;
+}
+
+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;
+ int size, i, len;
+ uint8_t *pdata;
+ char tmp[3] = "";
+
+ if (strstr(key, addr) == NULL)
+ return;
+
+ size = strlen(value)/2;
+ pdata = g_malloc0(size);
+
+ for (i = 0; i < size; i++) {
+ memcpy(tmp, value + (i*2), 2);
+ pdata[i] = (uint8_t) strtol(tmp, NULL, 16);
+ }
+
+ rec = sdp_extract_pdu(pdata, &len);
+ free(pdata);
+
+ rec_list->recs = sdp_list_append(rec_list->recs, rec);
+}
+
+static void create_stored_device_from_profiles(char *key, char *value,
+ void *user_data)
+{
+ char filename[PATH_MAX + 1];
+ struct adapter *adapter = user_data;
+ GSList *uuids = bt_string2list(value);
+ struct btd_device *device;
+ const gchar *src;
+ struct record_list rec_list;
+
+ if (g_slist_find_custom(adapter->devices,
+ key, (GCompareFunc) device_address_cmp))
+ return;
+
+ device = device_create(connection, adapter, key);
+ if (!device)
+ return;
+
+ device_set_temporary(device, FALSE);
+ adapter->devices = g_slist_append(adapter->devices, device);
+
+ src = adapter->address;
+ rec_list.addr = device_get_address(device);
+ rec_list.recs = NULL;
+
+ create_name(filename, PATH_MAX, STORAGEDIR, src, "sdp");
+ textfile_foreach(filename, create_stored_records_from_keys, &rec_list);
+
+ device_probe_drivers(device, uuids, rec_list.recs);
+
+ if (rec_list.recs != NULL)
+ sdp_list_free(rec_list.recs, (sdp_free_func_t) sdp_record_free);
+
+ g_slist_free(uuids);
+}
+
+static void create_stored_device_from_linkkeys(char *key, char *value,
+ void *user_data)
+{
+ struct 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);
+ if (device) {
+ device_set_temporary(device, FALSE);
+ adapter->devices = g_slist_append(adapter->devices, device);
+ }
+}
+
+static void load_devices(struct adapter *adapter)
+{
+ char filename[PATH_MAX + 1];
+
+ create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "profiles");
+ textfile_foreach(filename, create_stored_device_from_profiles, adapter);
+
+ create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "linkkeys");
+ textfile_foreach(filename, create_stored_device_from_linkkeys, adapter);
+}
+
+
+static void adapter_up(struct adapter *adapter, int dd)
+{
+ struct hci_conn_list_req *cl = NULL;
+ struct hci_conn_info *ci;
+ const char *mode;
+ int i;
+
+ adapter->up = 1;
+ adapter->discov_timeout = get_discoverable_timeout(adapter->dev_id);
+ adapter->discov_type = DISCOVER_TYPE_NONE;
+
+ adapter->scan_mode = get_startup_scan(adapter->dev_id);
+ hci_send_cmd(dd, OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE,
+ 1, &adapter->scan_mode);
+
+ adapter->mode = get_startup_mode(adapter->dev_id);
+ if (adapter->mode == MODE_LIMITED)
+ set_limited_discoverable(dd, adapter->dev.class, TRUE);
+
+ /*
+ * retrieve the active connections: address the scenario where
+ * the are active connections before the daemon've started
+ */
+
+ cl = g_malloc0(10 * sizeof(*ci) + sizeof(*cl));
+
+ cl->dev_id = adapter->dev_id;
+ cl->conn_num = 10;
+ ci = cl->conn_info;
+
+ if (ioctl(dd, HCIGETCONNLIST, cl) == 0) {
+ for (i = 0; i < cl->conn_num; i++, ci++)
+ active_conn_append(&adapter->active_conn,
+ &ci->bdaddr, ci->handle);
+ }
+ g_free(cl);
+
+ mode = mode2str(adapter->mode);
+
+ dbus_connection_emit_property_changed(connection, adapter->path,
+ ADAPTER_INTERFACE, "Mode",
+ DBUS_TYPE_STRING, &mode);
+
+ load_devices(adapter);
+}
+
+int adapter_start(struct adapter *adapter)
+{
+ struct hci_dev *dev = &adapter->dev;
+ struct hci_dev_info di;
+ struct hci_version ver;
+ uint8_t features[8];
+ int dd, err;
+ char name[249];
+
+ if (hci_devinfo(adapter->dev_id, &di) < 0)
+ return -errno;
+
+ if (hci_test_bit(HCI_RAW, &di.flags)) {
+ dev->ignore = 1;
+ return -1;
+ }
+
+ if (bacmp(&di.bdaddr, BDADDR_ANY))
+ ba2str(&di.bdaddr, adapter->address);
+ else {
+ int err = device_read_bdaddr(adapter->dev_id, adapter->address);
+ if (err < 0)
+ return err;
+ }
+ memcpy(dev->features, di.features, 8);
+
+ dd = hci_open_dev(adapter->dev_id);
+ if (dd < 0) {
+ err = errno;
+ error("Can't open adapter %s: %s (%d)",
+ adapter->path, strerror(err), err);
+ return -err;
+ }
+
+ if (hci_read_local_version(dd, &ver, 1000) < 0) {
+ err = errno;
+ error("Can't read version info for %s: %s (%d)",
+ adapter->path, strerror(err), err);
+ hci_close_dev(dd);
+ return -err;
+ }
+
+ dev->hci_rev = ver.hci_rev;
+ dev->lmp_ver = ver.lmp_ver;
+ dev->lmp_subver = ver.lmp_subver;
+ dev->manufacturer = ver.manufacturer;
+
+ if (hci_read_local_features(dd, features, 1000) < 0) {
+ err = errno;
+ error("Can't read features for %s: %s (%d)",
+ adapter->path, strerror(err), err);
+ hci_close_dev(dd);
+ return -err;
+ }
+
+ memcpy(dev->features, features, 8);
+
+ if (hci_read_class_of_dev(dd, dev->class, 1000) < 0) {
+ err = errno;
+ error("Can't read class of adapter on %s: %s (%d)",
+ adapter->path, strerror(err), err);
+ hci_close_dev(dd);
+ return -err;
+ }
+
+ if (hci_read_local_name(dd, sizeof(name), name, 2000) < 0) {
+ err = errno;
+ error("Can't read local name on %s: %s (%d)",
+ adapter->path, strerror(err), err);
+ hci_close_dev(dd);
+ return -err;
+ }
+
+ memcpy(dev->name, name, 248);
+
+ if (!(features[6] & LMP_SIMPLE_PAIR))
+ goto setup;
+
+ if (hcid_dbus_use_experimental()) {
+ if (ioctl(dd, HCIGETAUTHINFO, NULL) < 0 && errno != EINVAL)
+ hci_write_simple_pairing_mode(dd, 0x01, 2000);
+ }
+
+ if (hci_read_simple_pairing_mode(dd, &dev->ssp_mode, 1000) < 0) {
+ err = errno;
+ error("Can't read simple pairing mode on %s: %s (%d)",
+ adapter->path, strerror(err), err);
+ hci_close_dev(dd);
+ return -err;
+ }
+
+setup:
+ if (hci_test_bit(HCI_INQUIRY, &di.flags))
+ adapter->discov_active = 1;
+ else
+ adapter->discov_active = 0;
+
+ adapter_setup(adapter, dd);
+ adapter_up(adapter, dd);
+
+ hci_close_dev(dd);
+
+ info("Adapter %s has been enabled", adapter->path);
+
+ return 0;
+}
+
+static void reply_pending_requests(struct adapter *adapter)
+{
+ DBusMessage *reply;
+
+ if (!adapter)
+ return;
+
+ /* pending bonding */
+ if (adapter->bonding) {
+ reply = new_authentication_return(adapter->bonding->msg,
+ HCI_OE_USER_ENDED_CONNECTION);
+ g_dbus_send_message(connection, reply);
+ remove_pending_device(adapter);
+
+ g_dbus_remove_watch(adapter->bonding->conn,
+ adapter->bonding->listener_id);
+
+ if (adapter->bonding->io_id)
+ g_source_remove(adapter->bonding->io_id);
+ g_io_channel_close(adapter->bonding->io);
+ bonding_request_free(adapter->bonding);
+ adapter->bonding = NULL;
+ }
+
+ /* If there is a pending reply for discovery cancel */
+ if (adapter->discovery_cancel) {
+ reply = dbus_message_new_method_return(adapter->discovery_cancel);
+ dbus_connection_send(connection, reply, NULL);
+ dbus_message_unref(reply);
+ dbus_message_unref(adapter->discovery_cancel);
+ adapter->discovery_cancel = NULL;
+ }
+
+ if (adapter->discov_active) {
+ /* Send discovery completed signal if there isn't name
+ * to resolve */
+ g_dbus_emit_signal(connection, adapter->path,
+ ADAPTER_INTERFACE, "DiscoveryCompleted",
+ DBUS_TYPE_INVALID);
+
+ /* Cancel inquiry initiated by D-Bus client */
+ if (adapter->discov_requestor)
+ cancel_discovery(adapter);
+ }
+
+ if (adapter->pdiscov_active) {
+ /* Stop periodic inquiry initiated by D-Bus client */
+ if (adapter->pdiscov_requestor)
+ cancel_periodic_discovery(adapter);
+ }
+}
+
+
+int adapter_stop(struct adapter *adapter)
+{
+ const char *mode = "off";
+
+ /* 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);
+
+ if (adapter->discov_requestor) {
+ g_dbus_remove_watch(connection, adapter->discov_listener);
+ adapter->discov_listener = 0;
+ g_free(adapter->discov_requestor);
+ adapter->discov_requestor = NULL;
+ }
+
+ if (adapter->pdiscov_requestor) {
+ g_dbus_remove_watch(connection, adapter->pdiscov_listener);
+ adapter->pdiscov_listener = 0;
+ g_free(adapter->pdiscov_requestor);
+ adapter->pdiscov_requestor = NULL;
+ }
+
+ if (adapter->found_devices) {
+ g_slist_foreach(adapter->found_devices, (GFunc) g_free, NULL);
+ g_slist_free(adapter->found_devices);
+ adapter->found_devices = NULL;
+ }
+
+ if (adapter->oor_devices) {
+ g_slist_foreach(adapter->oor_devices, (GFunc) free, NULL);
+ g_slist_free(adapter->oor_devices);
+ adapter->oor_devices = NULL;
+ }
+
+ if (adapter->auth_reqs) {
+ g_slist_foreach(adapter->auth_reqs, (GFunc) g_free, NULL);
+ g_slist_free(adapter->auth_reqs);
+ adapter->auth_reqs = NULL;
+ }
+
+ if (adapter->active_conn) {
+ g_slist_foreach(adapter->active_conn, (GFunc) g_free, NULL);
+ g_slist_free(adapter->active_conn);
+ adapter->active_conn = NULL;
+ }
+
+ dbus_connection_emit_property_changed(connection, adapter->path,
+ ADAPTER_INTERFACE, "Mode",
+ DBUS_TYPE_STRING, &mode);
+
+ adapter->up = 0;
+ adapter->scan_mode = SCAN_DISABLED;
+ adapter->mode = MODE_OFF;
+ adapter->discov_active = 0;
+ adapter->pdiscov_active = 0;
+ adapter->pinq_idle = 0;
+ adapter->discov_type = DISCOVER_TYPE_NONE;
+
+ info("Adapter %s has been disabled", adapter->path);
+
+ return 0;
+}
+
+int adapter_update(struct adapter *adapter)
+{
+ struct hci_dev *dev = &adapter->dev;
+ int dd;
+
+ if (dev->ignore)
+ return 0;
+
+ dd = hci_open_dev(adapter->dev_id);
+ if (dd < 0) {
+ int err = errno;
+ error("Can't open adapter %s: %s (%d)",
+ adapter->path, strerror(err), err);
+ return -err;
+ }
+
+ update_ext_inquiry_response(dd, dev);
+
+ hci_close_dev(dd);
+
+ return 0;
+}
+
+int adapter_get_class(struct adapter *adapter, uint8_t *cls)
+{
+ struct hci_dev *dev = &adapter->dev;
+
+ memcpy(cls, dev->class, 3);
+
+ return 0;
+}
+
+int adapter_set_class(struct adapter *adapter, uint8_t *cls)
+{
+ struct hci_dev *dev = &adapter->dev;
+
+ memcpy(dev->class, cls, 3);
+
+ return 0;
+}
+
+int adapter_update_ssp_mode(struct adapter *adapter, int dd, uint8_t mode)
+{
+ struct hci_dev *dev = &adapter->dev;
+
+ dev->ssp_mode = mode;
+
+ update_ext_inquiry_response(dd, dev);
+
+ hci_close_dev(dd);
+
+ return 0;
+}
+
+struct adapter *adapter_create(int id)
+{
+ char path[MAX_PATH_LENGTH];
+ struct adapter *adapter;
+
+ snprintf(path, sizeof(path), "/hci%d", id);
+
+ adapter = g_try_new0(struct adapter, 1);
+ if (!adapter) {
+ error("Failed to alloc memory to D-Bus path register data (%s)",
+ path);
+ return NULL;
+ }
+
+ adapter->dev_id = id;
+ adapter->pdiscov_resolve_names = 1;
+ adapter->path = g_strdup(path);
+
+ return adapter;
+}
+
+uint16_t adapter_get_dev_id(struct adapter *adapter)
+{
+ return adapter->dev_id;
+}
+
+const gchar *adapter_get_path(struct adapter *adapter)
+{
+ if (!adapter)
+ return NULL;
+
+ return adapter->path;
+}
+
+const gchar *adapter_get_address(struct adapter *adapter)
+{
+ if (!adapter)
+ return NULL;
+
+ return adapter->address;
+}
+
+void adapter_free(struct adapter *adapter)
+{
+ if (!adapter)
+ return;
+
+ g_free(adapter->path);
+ g_free(adapter);
+
+ return;
+}
+gboolean discov_timeout_handler(void *data)
+{
+ struct adapter *adapter = data;
+ struct hci_request rq;
+ int dd;
+ uint8_t scan_enable = adapter->scan_mode;
+ uint8_t status = 0;
+ gboolean retval = TRUE;
+ uint16_t dev_id = adapter->dev_id;
+
+ scan_enable &= ~SCAN_INQUIRY;
+
+ dd = hci_open_dev(dev_id);
+ if (dd < 0) {
+ error("HCI device open failed: hci%d", dev_id);
+ return TRUE;
+ }
+
+ memset(&rq, 0, sizeof(rq));
+ rq.ogf = OGF_HOST_CTL;
+ rq.ocf = OCF_WRITE_SCAN_ENABLE;
+ rq.cparam = &scan_enable;
+ rq.clen = sizeof(scan_enable);
+ rq.rparam = &status;
+ rq.rlen = sizeof(status);
+ rq.event = EVT_CMD_COMPLETE;
+
+ if (hci_send_req(dd, &rq, 1000) < 0) {
+ error("Sending write scan enable command to hci%d failed: %s (%d)",
+ dev_id, strerror(errno), errno);
+ goto failed;
+ }
+ if (status) {
+ error("Setting scan enable failed with status 0x%02x", status);
+ goto failed;
+ }
+
+ set_limited_discoverable(dd, adapter->dev.class, FALSE);
+
+ adapter_remove_discov_timeout(adapter);
+ retval = FALSE;
+
+failed:
+ if (dd >= 0)
+ hci_close_dev(dd);
+
+ return retval;
+}
+
+void adapter_set_discov_timeout(struct adapter *adapter, guint interval)
+{
+ if (!adapter)
+ return;
+
+ if (adapter->discov_timeout_id) {
+ error("Timeout already added for adapter %s", adapter->path);
+ return;
+ }
+
+ adapter->discov_timeout_id = g_timeout_add(interval, discov_timeout_handler, adapter);
+}
+
+void adapter_remove_discov_timeout(struct adapter *adapter)
+{
+ if (!adapter)
+ return;
+
+ if(adapter->discov_timeout_id == 0)
+ return;
+
+ g_source_remove(adapter->discov_timeout_id);
+ adapter->discov_timeout_id = 0;
+}
+
+void adapter_set_scan_mode(struct adapter *adapter, uint8_t scan_mode)
+{
+ if (!adapter)
+ return;
+
+ adapter->scan_mode = scan_mode;
+}
+
+uint8_t adapter_get_scan_mode(struct adapter *adapter)
+{
+ return adapter->scan_mode;
+}
diff --git a/src/adapter.h b/src/adapter.h
new file mode 100644
index 00000000..27da5cfa
--- /dev/null
+++ b/src/adapter.h
@@ -0,0 +1,185 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 ADAPTER_INTERFACE "org.bluez.Adapter"
+
+#define INVALID_DEV_ID 0xFFFF
+
+#define BONDING_TIMEOUT 45000 /* 45 sec */
+
+#define DC_PENDING_TIMEOUT 2000 /* 2 secs */
+
+/* Discover types */
+#define DISCOVER_TYPE_NONE 0x00
+#define STD_INQUIRY 0x01
+#define PERIODIC_INQUIRY 0x02
+
+/* Actions executed after inquiry complete */
+#define RESOLVE_NAME 0x10
+
+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_SENT /* D-Bus signal RemoteNameUpdated sent */
+} name_status_t;
+
+typedef enum {
+ AUTH_TYPE_PINCODE,
+ AUTH_TYPE_PASSKEY,
+ AUTH_TYPE_CONFIRM,
+ AUTH_TYPE_NOTIFY,
+} auth_type_t;
+
+struct remote_dev_info {
+ bdaddr_t bdaddr;
+ int8_t rssi;
+ name_status_t name_status;
+};
+
+struct bonding_request_info {
+ DBusConnection *conn;
+ DBusMessage *msg;
+ struct adapter *adapter;
+ bdaddr_t bdaddr;
+ GIOChannel *io;
+ guint io_id;
+ guint listener_id;
+ int hci_status;
+ int cancel;
+ int auth_active;
+};
+
+struct pending_auth_info {
+ auth_type_t type;
+ bdaddr_t bdaddr;
+ gboolean replied; /* If we've already replied to the request */
+ struct agent *agent; /* Agent associated with the request */
+};
+
+struct active_conn_info {
+ bdaddr_t bdaddr;
+ uint16_t handle;
+};
+
+struct hci_dev {
+ int ignore;
+
+ uint8_t features[8];
+ uint8_t lmp_ver;
+ uint16_t lmp_subver;
+ uint16_t hci_rev;
+ uint16_t manufacturer;
+
+ uint8_t ssp_mode;
+ uint8_t name[248];
+ uint8_t class[3];
+};
+
+struct adapter {
+ uint16_t dev_id;
+ int up;
+ char *path; /* adapter object path */
+ char address[18]; /* adapter Bluetooth Address */
+ guint discov_timeout_id; /* discoverable timeout id */
+ uint32_t discov_timeout; /* discoverable time(msec) */
+ 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 */
+ int discov_active; /* standard discovery active: includes name resolution step */
+ int pdiscov_active; /* periodic discovery active */
+ int pinq_idle; /* tracks the idle time for periodic inquiry */
+ int discov_type; /* type requested */
+ int pdiscov_resolve_names; /* Resolve names when doing periodic discovery */
+ GSList *found_devices;
+ GSList *oor_devices; /* out of range device list */
+ char *pdiscov_requestor; /* periodic discovery requestor unique name */
+ guint pdiscov_listener;
+ char *discov_requestor; /* discovery requestor unique name */
+ guint discov_listener;
+ DBusMessage *discovery_cancel; /* discovery cancel message request */
+ GSList *passkey_agents;
+ struct agent *agent; /* For the new API */
+ GSList *active_conn;
+ struct bonding_request_info *bonding;
+ GSList *auth_reqs; /* Received and replied HCI
+ authentication requests */
+ GSList *devices; /* Devices structure pointers */
+ GSList *sessions; /* Request Mode sessions */
+
+ struct hci_dev dev; /* hci info */
+};
+
+dbus_bool_t adapter_init(DBusConnection *conn,
+ const char *path, struct adapter *adapter);
+
+int adapter_start(struct adapter *adapter);
+
+int adapter_stop(struct adapter *adapter);
+
+int adapter_update(struct adapter *adapter);
+
+int adapter_get_class(struct adapter *adapter, uint8_t *cls);
+
+int adapter_set_class(struct adapter *adapter, uint8_t *cls);
+
+int adapter_update_ssp_mode(struct adapter *adapter, int dd, uint8_t mode);
+
+dbus_bool_t adapter_cleanup(DBusConnection *conn, const char *path);
+
+struct btd_device *adapter_get_device(DBusConnection *conn,
+ struct adapter *adapter, const gchar *address);
+
+struct btd_device *adapter_find_device(struct adapter *adapter, const char *dest);
+
+void adapter_remove_device(DBusConnection *conn, struct adapter *adapter,
+ struct btd_device *device);
+struct btd_device *adapter_create_device(DBusConnection *conn,
+ struct adapter *adapter, const char *address);
+
+const char *mode2str(uint8_t mode);
+
+uint8_t str2mode(const char *addr, const char *mode);
+
+int pending_remote_name_cancel(struct adapter *adapter);
+
+void remove_pending_device(struct adapter *adapter);
+
+void adapter_auth_request_replied(struct adapter *adapter, bdaddr_t *dba);
+struct pending_auth_info *adapter_find_auth_request(struct adapter *adapter,
+ bdaddr_t *dba);
+void adapter_remove_auth_request(struct adapter *adapter, bdaddr_t *dba);
+struct pending_auth_info *adapter_new_auth_request(struct adapter *adapter,
+ bdaddr_t *dba,
+ auth_type_t type);
+struct adapter *adapter_create(int id);
+uint16_t adapter_get_dev_id(struct adapter *adapter);
+const gchar *adapter_get_path(struct adapter *adapter);
+const gchar *adapter_get_address(struct adapter *adapter);
+void adapter_free(struct adapter *adapter);
+void adapter_set_discov_timeout(struct adapter *adapter, guint interval);
+void adapter_remove_discov_timeout(struct adapter *adapter);
+void adapter_set_scan_mode(struct adapter *adapter, uint8_t scan_mode);
+uint8_t adapter_get_scan_mode(struct adapter *adapter);
diff --git a/src/agent.c b/src/agent.c
new file mode 100644
index 00000000..3cae00a5
--- /dev/null
+++ b/src/agent.c
@@ -0,0 +1,733 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2008 Nokia Corporation
+ * Copyright (C) 2004-2008 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/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "hcid.h"
+#include "dbus-common.h"
+#include "error.h"
+#include "adapter.h"
+#include "dbus-hci.h"
+#include "device.h"
+#include "agent.h"
+
+#define REQUEST_TIMEOUT (60 * 1000) /* 60 seconds */
+#define AGENT_TIMEOUT (10 * 60 * 1000) /* 10 minutes */
+
+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 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;
+ DBusPendingCall *call;
+ void *cb;
+ void *user_data;
+};
+
+static DBusConnection *connection = NULL;
+
+static void agent_release(struct agent *agent)
+{
+ DBusMessage *message;
+
+ debug("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;
+ }
+
+ dbus_message_set_no_reply(message, TRUE);
+
+ dbus_connection_send(connection, message, NULL);
+
+ dbus_message_unref(message);
+}
+
+static int send_cancel_request(struct agent_request *req)
+{
+ DBusMessage *message;
+
+ 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;
+ }
+
+ dbus_message_set_no_reply(message, TRUE);
+
+ dbus_connection_send(connection, message, NULL);
+
+ dbus_message_unref(message);
+
+ return 0;
+}
+
+static void agent_request_free(struct agent_request *req)
+{
+ if (req->call)
+ dbus_pending_call_unref(req->call);
+ if (req->agent && req->agent->request)
+ req->agent->request = NULL;
+ g_free(req);
+}
+
+static void agent_exited(void *user_data)
+{
+ struct agent *agent = user_data;
+
+ debug("Agent exited without calling Unregister");
+
+ agent_destroy(agent, TRUE);
+}
+
+static 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 adapter *adapter, const char *name,
+ const char *path, uint8_t capability,
+ agent_remove_cb cb, void *remove_cb_data)
+{
+ struct agent *agent;
+
+ if (adapter->agent && g_str_equal(adapter->agent->name, name))
+ return NULL;
+
+ 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;
+}
+
+int agent_destroy(struct agent *agent, gboolean exited)
+{
+ agent->exited = exited;
+ agent_free(agent);
+ return 0;
+}
+
+static struct agent_request *agent_request_new(struct agent *agent,
+ agent_request_type_t type,
+ void *cb,
+ void *user_data)
+{
+ 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;
+
+ 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);
+ agent->request = NULL;
+
+ return 0;
+}
+
+static DBusPendingCall *agent_call_authorize(struct agent *agent,
+ const char *device_path,
+ const char *uuid)
+{
+ DBusMessage *message;
+ DBusPendingCall *call;
+
+ message = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.Agent", "Authorize");
+ if (!message) {
+ error("Couldn't allocate D-Bus message");
+ return NULL;
+ }
+
+ dbus_message_append_args(message,
+ DBUS_TYPE_OBJECT_PATH, &device_path,
+ DBUS_TYPE_STRING, &uuid,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_connection_send_with_reply(connection, message,
+ &call, REQUEST_TIMEOUT) == FALSE) {
+ error("D-Bus send failed");
+ dbus_message_unref(message);
+ return NULL;
+ }
+
+ dbus_message_unref(message);
+ return call;
+}
+
+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)) {
+
+ error("Agent replied with an error: %s, %s",
+ err.name, err.message);
+
+ cb(agent, &err, req->user_data);
+ 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);
+}
+
+int agent_authorize(struct agent *agent,
+ const char *path,
+ const char *uuid,
+ agent_cb cb,
+ void *user_data)
+{
+ struct agent_request *req;
+
+ if (agent->request)
+ return -EBUSY;
+
+ req = agent_request_new(agent, AGENT_REQUEST_AUTHORIZE, cb, user_data);
+
+ req->call = agent_call_authorize(agent, path, uuid);
+ if (!req->call) {
+ agent_request_free(req);
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+ }
+
+ dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL);
+ agent->request = req;
+
+ debug("authorize request was sent for %s", path);
+
+ return 0;
+}
+
+static DBusPendingCall *pincode_request_new(struct agent *agent,
+ const char *device_path,
+ dbus_bool_t numeric)
+{
+ DBusMessage *message;
+ DBusPendingCall *call;
+
+ message = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.Agent", "RequestPinCode");
+ if (message == NULL) {
+ error("Couldn't allocate D-Bus message");
+ return NULL;
+ }
+
+ dbus_message_append_args(message, DBUS_TYPE_OBJECT_PATH, &device_path,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_connection_send_with_reply(connection, message,
+ &call, REQUEST_TIMEOUT) == FALSE) {
+ error("D-Bus send failed");
+ dbus_message_unref(message);
+ return NULL;
+ }
+
+ dbus_message_unref(message);
+ return call;
+}
+
+static void pincode_reply(DBusPendingCall *call, void *user_data)
+{
+ struct agent_request *req = user_data;
+ struct agent *agent = req->agent;
+ struct adapter *adapter = agent->adapter;
+ agent_pincode_cb cb = req->cb;
+ DBusMessage *message;
+ DBusError err;
+ bdaddr_t sba;
+ size_t len;
+ char *pin;
+ const gchar *source = adapter_get_address(adapter);
+
+ /* 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)) {
+ 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 passkey length from handler");
+ 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;
+ }
+
+ str2ba(source, &sba);
+
+ set_pin_length(&sba, len);
+
+ cb(agent, NULL, pin, req->user_data);
+
+done:
+ if (message)
+ dbus_message_unref(message);
+
+ dbus_pending_call_cancel(req->call);
+ agent_request_free(req);
+}
+
+int agent_request_pincode(struct agent *agent, struct btd_device *device,
+ agent_pincode_cb cb, void *user_data)
+{
+ struct agent_request *req;
+ const gchar *dev_path = device_get_path(device);
+
+ if (agent->request)
+ return -EBUSY;
+
+ req = agent_request_new(agent, AGENT_REQUEST_PINCODE, cb, user_data);
+
+ req->call = pincode_request_new(agent, dev_path, FALSE);
+ if (!req->call)
+ goto failed;
+
+ dbus_pending_call_set_notify(req->call, pincode_reply, req, NULL);
+
+ agent->request = req;
+
+ return 0;
+
+failed:
+ g_free(req);
+ return -1;
+}
+
+static DBusPendingCall *confirm_mode_change_request_new(struct agent *agent,
+ const char *mode)
+{
+ DBusMessage *message;
+ DBusPendingCall *call;
+
+ message = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.Agent", "ConfirmModeChange");
+ if (message == NULL) {
+ error("Couldn't allocate D-Bus message");
+ return NULL;
+ }
+
+ dbus_message_append_args(message,
+ DBUS_TYPE_STRING, &mode,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_connection_send_with_reply(connection, message,
+ &call, REQUEST_TIMEOUT) == FALSE) {
+ error("D-Bus send failed");
+ dbus_message_unref(message);
+ return NULL;
+ }
+
+ dbus_message_unref(message);
+
+ return call;
+}
+
+int agent_confirm_mode_change(struct agent *agent, const char *new_mode,
+ agent_cb cb, void *user_data)
+{
+ struct agent_request *req;
+
+ if (agent->request)
+ return -EBUSY;
+
+ debug("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);
+
+ req->call = confirm_mode_change_request_new(agent, new_mode);
+ if (!req->call)
+ goto failed;
+
+ dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL);
+
+ agent->request = req;
+
+ return 0;
+
+failed:
+ agent_request_free(req);
+ return -1;
+}
+
+static DBusPendingCall *passkey_request_new(struct agent *agent,
+ const char *device_path)
+{
+ DBusMessage *message;
+ DBusPendingCall *call;
+
+ message = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.Agent", "RequestPasskey");
+ if (message == NULL) {
+ error("Couldn't allocate D-Bus message");
+ return NULL;
+ }
+
+ dbus_message_append_args(message, DBUS_TYPE_OBJECT_PATH, &device_path,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_connection_send_with_reply(connection, message,
+ &call, REQUEST_TIMEOUT) == FALSE) {
+ error("D-Bus send failed");
+ dbus_message_unref(message);
+ return NULL;
+ }
+
+ dbus_message_unref(message);
+ return call;
+}
+
+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)) {
+ 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_free(req);
+}
+
+int agent_request_passkey(struct agent *agent, struct btd_device *device,
+ agent_passkey_cb cb, void *user_data)
+{
+ struct agent_request *req;
+ const gchar *dev_path = device_get_path(device);
+
+ if (agent->request)
+ return -EBUSY;
+
+ debug("Calling Agent.RequestPasskey: name=%s, path=%s",
+ agent->name, agent->path);
+
+ req = agent_request_new(agent, AGENT_REQUEST_PASSKEY, cb, user_data);
+
+ req->call = passkey_request_new(agent, dev_path);
+ if (!req->call)
+ goto failed;
+
+ dbus_pending_call_set_notify(req->call, passkey_reply, req, NULL);
+
+ agent->request = req;
+
+ return 0;
+
+failed:
+ agent_request_free(req);
+ return -1;
+}
+
+static DBusPendingCall *confirmation_request_new(struct agent *agent,
+ const char *device_path,
+ uint32_t passkey)
+{
+ DBusMessage *message;
+ DBusPendingCall *call;
+
+ message = dbus_message_new_method_call(agent->name, agent->path,
+ "org.bluez.Agent", "RequestConfirmation");
+ if (message == NULL) {
+ error("Couldn't allocate D-Bus message");
+ return NULL;
+ }
+
+ dbus_message_append_args(message,
+ DBUS_TYPE_OBJECT_PATH, &device_path,
+ DBUS_TYPE_UINT32, &passkey,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_connection_send_with_reply(connection, message,
+ &call, REQUEST_TIMEOUT) == FALSE) {
+ error("D-Bus send failed");
+ dbus_message_unref(message);
+ return NULL;
+ }
+
+ dbus_message_unref(message);
+
+ return call;
+}
+
+int agent_request_confirmation(struct agent *agent, struct btd_device *device,
+ uint32_t passkey, agent_cb cb,
+ void *user_data)
+{
+ struct agent_request *req;
+ const gchar *dev_path = device_get_path(device);
+
+ if (agent->request)
+ return -EBUSY;
+
+ debug("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);
+
+ req->call = confirmation_request_new(agent, dev_path, passkey);
+ if (!req->call)
+ goto failed;
+
+ dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL);
+
+ agent->request = req;
+
+ return 0;
+
+failed:
+ agent_request_free(req);
+ return -1;
+}
+
+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;
+}
+
+void agent_exit(void)
+{
+ dbus_connection_unref(connection);
+}
+
+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 00000000..291e7a15
--- /dev/null
+++ b/src/agent.h
@@ -0,0 +1,71 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2008 Nokia Corporation
+ * Copyright (C) 2004-2008 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 adapter *adapter, const char *name,
+ const char *path, uint8_t capability,
+ agent_remove_cb cb, void *remove_cb_data);
+
+int agent_destroy(struct agent *agent, gboolean exited);
+
+int agent_authorize(struct agent *agent, const char *path,
+ const char *uuid, agent_cb cb, void *user_data);
+
+int agent_request_pincode(struct agent *agent, struct btd_device *device,
+ agent_pincode_cb cb, void *user_data);
+
+int agent_confirm_mode_change(struct agent *agent, const char *new_mode,
+ agent_cb cb, void *user_data);
+
+int agent_request_passkey(struct agent *agent, struct btd_device *device,
+ agent_passkey_cb cb, void *user_data);
+
+int agent_request_confirmation(struct agent *agent, struct btd_device *device,
+ uint32_t passkey, agent_cb cb,
+ void *user_data);
+
+int agent_display_passkey(struct agent *agent, struct btd_device *device,
+ uint32_t passkey);
+
+int agent_cancel(struct agent *agent);
+
+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/bluetooth.conf b/src/bluetooth.conf
new file mode 100644
index 00000000..88545fac
--- /dev/null
+++ b/src/bluetooth.conf
@@ -0,0 +1,37 @@
+<!-- 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"/>
+ </policy>
+
+ <policy at_console="true">
+ <allow send_path="/"/>
+ <allow send_path="/org/bluez"/>
+
+ <allow send_destination="org.bluez.Manager"/>
+ <allow receive_sender="org.bluez.Manager"/>
+
+ <allow send_destination="org.bluez.Adapter"/>
+ <allow receive_sender="org.bluez.Adapter"/>
+
+ <allow send_destination="org.bluez.Device"/>
+ <allow receive_sender="org.bluez.Device"/>
+
+ <allow send_destination="org.bluez.Service"/>
+ <allow receive_sender="org.bluez.Service"/>
+
+ <allow send_destination="org.bluez.Database"/>
+ <allow receive_sender="org.bluez.Database"/>
+
+ <allow send_destination="org.bluez.Security"/>
+ <allow receive_sender="org.bluez.Security"/>
+ </policy>
+
+</busconfig>
diff --git a/src/dbus-api.txt b/src/dbus-api.txt
new file mode 100644
index 00000000..622477be
--- /dev/null
+++ b/src/dbus-api.txt
@@ -0,0 +1,1401 @@
+D-Bus API description for BlueZ
+*******************************
+
+Copyright (C) 2004-2007 Marcel Holtmann <marcel@holtmann.org>
+Copyright (C) 2005-2006 Johan Hedberg <johan.hedberg@nokia.com>
+Copyright (C) 2005-2006 Claudio Takahasi <claudio.takahasi@indt.org.br>
+Copyright (C) 2005-2006 Eduardo Rocha <eduardo.rocha@indt.org.br>
+
+
+Constant definitions
+====================
+
+The class of device definition from the Bluetooth specification divides into
+three different parts. It the major class, the minor class and the service
+classes. The D-Bus interface will always use string constants to identify
+any of these classes.
+
+Service classes positioning, networking, rendering, capturing,
+ object transfer, audio, telephony, information
+
+Major classes miscellaneous, computer, phone, access point,
+ audio/video, peripheral, imaging, wearable, toy,
+ uncategorized
+
+Minor classes computer uncategorized, desktop, server, laptop, handheld,
+ palm, wearable
+
+Minor classes phone uncategorized, cellular, cordless, smart phone,
+ modem, isdn
+
+Minor classes access point fully, 1-17 percent, 17-33 percent,
+ 33-50 percent, 50-67 percent, 67-83 percent,
+ 83-99 percent, not available
+
+Minor classes audio video uncategorized, headset, handsfree,microphone,
+ loudspeaker, headphones, portable audio, car audio,
+ set-top box, hifi audio, vcr, video camera, camcorder,
+ video monitor, video display and loudspeaker,
+ video conferencing, gaming/toy, unknown
+
+Minor classes peripheral uncategorized, keyboard, pointing, combo
+
+Minor classes imaging display, camera, scanner, printer
+
+Minor classes wearable wrist watch, pager, jacket, helmet, glasses
+
+Minor classes toy robot, vehicle, doll, controller, game
+
+Error hierarchy
+===============
+
+Interface org.bluez.Error
+
+Shared Errors (Can be thrown by hcid or any bluetooth service)
+
+ DeviceUnreachable
+
+ The remote device is either powered down or out of range.
+
+ AlreadyConnected
+ A connection request has been received on an already
+ connected device.
+
+ ConnectionAttemptFailed
+
+ An unexpected error (other than DeviceUnreachable) error
+ has occured while attempting a connection to a device.
+
+ NotConnected
+ The remote device is not connected, while the method call
+ would expect it to be, or is not in the expected state to
+ perform the action.
+
+ InProgress
+
+ Error returned if an operation is in progress. Since
+ this is a generic error that can be used in various
+ situations, the error message should be more clear
+ about what is in progress. For example "Bonding in
+ progress".
+
+ InvalidArguments
+
+ The DBUS request does not contain the right number of
+ arguments with the right type, or the arguments are there
+ but their value is wrong, or does not makes sense in the
+ current context.
+
+ OutOfMemory
+
+ Error returned when a memory allocation via malloc()
+ fails. This error is similar to ENOMEM.
+
+ NotAvailable
+
+ Error returned when a specified record is not
+ available.
+
+ NotSupported
+
+ The remote device does not support the expected
+ feature.
+
+ AlreadyExists
+ One of the requested elements already exists
+
+ DoesNotExist
+ One of the requested elements does not exist
+
+ Canceled
+ The operation was canceled.
+
+ Failed
+
+ This is a the most generic error.
+ It is thrown when something unexpected happens.
+
+
+Hcid specific Errors (Can be thrown by hcid only)
+
+ NotReady
+
+ Error returned when the adapter is DOWN.
+
+ UnknwownMethod
+
+ This is an experimental method.
+
+ NotAuthorized
+
+ Error returned when the caller of a method is not
+ authorized. This might happen if a caller tries to
+ terminate a connection that it hasn't created.
+
+ Rejected
+
+ NoSuchAdapter
+
+ Error returned when the requested adapter doesn't
+ exists. This error is similar to ENODEV.
+
+ NoSuchService
+
+ RequestDeferred
+
+ NotInProgress
+
+ UnsupportedMajorClass
+
+ AuthenticationCanceled
+
+ AuthenticationFailed
+
+ AuthenticationTimeout
+
+ AuthenticationRejected
+
+ RepeatedAttempts
+
+Manager hierarchy
+=================
+
+Service org.bluez
+Interface org.bluez.Manager
+Object path /org/bluez
+
+Methods uint32 InterfaceVersion()
+
+ Returns the current interface version. At the moment
+ only version 0 is supported.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+
+ string DefaultAdapter()
+
+ Returns object path for the default adapter.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.NoSuchAdapter
+
+ string FindAdapter(string pattern)
+
+ Returns object path for the specified adapter. Valid
+ patterns are "hci0" or "00:11:22:33:44:55".
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.NoSuchAdapter
+
+ array{string} ListAdapters()
+
+ Returns list of adapter object paths under /org/bluez
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.Failed
+ org.bluez.Error.OutOfMemory
+
+ string FindService(string pattern)
+
+ Returns object path for the specified service. Valid
+ patterns are the unqiue identifier or a bus name.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.NoSuchService
+
+ array{string} ListServices()
+
+ Returns list of object paths of current services.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+
+ string ActivateService(string pattern)
+
+ Returns the unqiue bus id of the specified service.
+ Valid patterns are the same as for FindService(). If
+ the service is not running it will be started.
+
+Signals void AdapterAdded(string path)
+
+ Parameter is object path of added adapter.
+
+ void AdapterRemoved(string path)
+
+ Parameter is object path of removed adapter.
+
+ void DefaultAdapterChanged(string path)
+
+ Parameter is object path of the new default adapter,
+ or an empty string if there is no available adapters.
+
+ void ServiceAdded(string path)
+
+ Parameter is object path of registered service agent.
+
+ void ServiceRemoved(string path)
+
+ Parameter is object path of unregistered service agent.
+
+
+Database hierarchy
+==================
+
+Service org.bluez
+Interface org.bluez.Database
+Object path /org/bluez
+
+Methods uint32 AddServiceRecord(array{byte})
+
+ Adds a new service record and returns the assigned
+ record handle.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.Failed
+
+ uint32 AddServiceRecordFromXML(string record)
+
+ Adds a new service record and returns the assigned
+ record handle.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.Failed
+
+ void UpdateServiceRecord(uint32 handle, array{byte})
+
+ Updates a given service record.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.NotAvailable
+ org.bluez.Error.Failed
+
+ void UpdateServiceRecordFromXML(uint32 handle, string record)
+
+ Updates a given service record provided in the
+ XML format.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.NotAvailable
+ org.bluez.Error.Failed
+
+ void RemoveServiceRecord(uint32 handle)
+
+ Remove a service record identified by its handle.
+
+ It is only possible to remove service records that
+ where added by the current connection.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.NotAuthorized
+ org.bluez.Error.DoesNotExist
+ org.bluez.Error.Failed
+
+
+Adapter hierarchy
+=================
+
+Service org.bluez
+Interface org.bluez.Adapter
+Object path /org/bluez/{hci0,hci1,...}
+
+Methods dict GetInfo()
+
+ Returns the properties of the local adapter.
+
+ Possible errors: org.bluez.Error.NotReady
+
+ string GetAddress()
+
+ Returns the device address for a given path.
+
+ Example: "00:11:22:33:44:55"
+
+ Possible errors: org.bluez.Error.NotReady
+
+ string GetVersion()
+
+ Returns the version of the Bluetooth chip. This version
+ is compiled from the LMP version. In case of EDR the
+ features attribute must be checked.
+
+ Example: "Bluetooth 2.0 + EDR"
+
+ Possible errors: none
+
+ string GetRevision()
+
+ Returns the revision of the Bluetooth chip. This is a
+ vendor specific value and in most cases it represents
+ the firmware version. This might derive from the HCI
+ revision and LMP subversion values or via extra vendor
+ specific commands.
+
+ In case the revision of a chip is not available. This
+ method should return the LMP subversion value as a
+ string.
+
+ Example: "HCI 19.2"
+
+ Possible errors: org.bluez.Error.Failed
+
+ string GetManufacturer()
+
+ Returns the manufacturer of the Bluetooth chip. If
+ the company id is not know the sting "Company ID %d"
+ where %d should be replaced with the numeric value
+ from the manufacturer field.
+
+ Example: "Cambridge Silicon Radio"
+
+ Possible errors: org.bluez.Error.Failed
+
+ string GetCompany()
+
+ Returns the company name from the OUI database of the
+ Bluetooth device address. This function will need a
+ valid and up-to-date oui.txt from the IEEE. This value
+ will be different from the manufacturer string in the
+ most cases.
+
+ If the oui.txt file is not present or the OUI part of
+ the BD_ADDR is not listed, it should return the
+ string "OUI %s" where %s is the actual OUI.
+
+ Example: "Apple Computer"
+
+ Possible errors: org.bluez.Error.Failed
+
+ array{string} ListAvailableModes()
+
+ Returns a list of available modes the adapter can
+ be switched into.
+
+ string GetMode()
+
+ Returns the current mode of a adapter.
+
+ Valid modes: "off", "connectable", "discoverable",
+ "limited".
+
+ Possible errors: none
+
+ void SetMode(string mode)
+
+ Sets mode of the adapter. See GetMode for valid strings
+ for the mode parameter. In addition to the valid strings
+ for GetMode, this method also supports a special
+ parameter value "on" which will change the mode to the
+ previous non-off mode (or do nothing if the current
+ mode isn't "off").
+
+ Possible errors: org.bluez.Error.NoSuchAdapter
+ org.bluez.Error.Failed
+
+ uint32 GetDiscoverableTimeout()
+
+ Returns the discoverable timeout in seconds. A value
+ of zero means that the timeout is disabled and it will
+ stay in discoverable/limited mode forever.
+
+ The default value for the discoverable timeout should
+ be 180 seconds (3 minutes).
+
+ Possible errors: none
+
+ void SetDiscoverableTimeout(uint32 timeout)
+
+ Sets the discoverable timeout in seconds. A value of
+ zero disables the timeout and the adapter would be
+ always discoverable/limited.
+
+ Changing this value doesn't set the adapter into
+ discoverable/limited mode. The SetMode method must be used.
+
+ Possible errors: org.bluez.Error.NotReady
+ org.bluez.Error.InvalidArguments
+
+ boolean IsConnectable()
+
+ Returns true if the local adapter is connectable and
+ false if it is switched off.
+
+ It is also possible to use GetMode to retrieve this
+ information.
+
+ Possible errors: none
+
+ boolean IsDiscoverable()
+
+ Returns true if the local adapter is discoverable/limited
+ and false if it is only connectable or switched off.
+
+ It is also possible to use GetMode to retrieve this
+ information.
+
+ Possible errors: none
+
+ boolean IsConnected(string address)
+
+ Return true if the local adapter is connected to
+ the remote device.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+
+ array{string} ListConnections()
+
+ Returns a list with addresses of currently connected
+ remote devices.
+
+ Possible errors: none
+
+ string GetMajorClass()
+
+ Returns the current major class value for this
+ system. Currently, only "computer" is supported.
+ For the other values, unsupported major class
+ error is returned.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.UnsupportedMajorClass
+
+ array{string} ListAvailableMinorClasses()
+
+ Returns a list of available minor classes for the
+ currently used major class. At the moment this should
+ only return a list of minor classes if the major
+ class is set to "computer".
+
+ If the major class is not "computer" an error should
+ be returned.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.UnsupportedMajorClass
+
+ string GetMinorClass()
+
+ Returns the current minor class value for this
+ system where the default major class is "computer".
+
+ If the major class is not "computer" an error should
+ be returned.
+
+ Valid values: "uncategorized", "desktop", "server",
+ "laptop", "handheld", "palm", "wearable"
+
+ The default value is "uncategorized".
+
+ Possible errors:org.bluez.Error.InvalidArguments
+ org.bluez.Error.UnsupportedMajorClass
+
+ void SetMinorClass(string minor)
+
+ Sets the local minor class and on success it sends
+ a MinorClassChanged signal.
+
+ If the major class is not "computer" an error should
+ be returned.
+
+ Possible errors: org.bluez.Error.NotReady
+ org.bluez.Error.InvalidArguments
+ org.bluez.Error.NoSuchAdapter
+ org.bluez.Error.Failed
+ org.bluez.Error.UnsupportedMajorClass
+
+ array{string} GetServiceClasses()
+
+ Returns the current set of service classes.
+
+ In the case no service classes are set (when no
+ service has been registered) an empty list should
+ be returned.
+
+ Valid values: "positioning", "networking", "rendering",
+ "capturing", "object transfer", "audio",
+ "telephony", "information"
+
+ Possible errors: org.bluez.Error.NotReady
+ org.bluez.Error.NoSuchAdapter
+ org.bluez.Error.Failed
+
+ string GetName()
+
+ Returns the local adapter name (friendly name) in UTF-8.
+
+ Possible errors: org.bluez.Error.Failed
+
+ void SetName(string name)
+
+ Sets the local adapter name. If EIR is supported by
+ the local hardware this modifies also the extended
+ response data value.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.Failed
+
+ Questions: What to do (in case of EIR) if one
+ low-level API call fails.
+
+ dict GetRemoteInfo(string address)
+
+ Returns the properties for a remote device.
+
+ string GetRemoteVersion(string address)
+
+ Get the version info for a remote device. This request
+ returns always this information based on its cached
+ data. The base for this string is the LMP version
+ value and the features for EDR support.
+
+ Not available can be received if the remote device was
+ not contacted(connected) previously. Remote data is
+ automatically retrieved in the first connection.
+
+ Example: "Bluetooth 2.0 + EDR"
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.NotAvailable
+
+ string GetRemoteRevision(string address)
+
+ Get the revision of the Bluetooth chip. This is a
+ vendor specific value and in most cases it represents
+ the firmware version. This derives only from the LMP
+ subversion value.
+
+ Example: "HCI 19.2"
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.NotAvailable
+
+ string GetRemoteManufacturer(string address)
+
+ Get the manufacturer of the chip for a remote device.
+
+ Example: "Nokia Mobile Phones"
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.NotAvailable
+
+ string GetRemoteCompany(string address)
+
+ Get the company name from the OUI database of the
+ Bluetooth device address. This function will need a
+ valid and up-to-date oui.txt from the IEEE. This value
+ will be different from the manufacturer string in the
+ most cases.
+
+ Example: "Microsoft Corporation"
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.NotAvailable
+
+ string GetRemoteMajorClass(string address)
+
+ Get the major device class of the specified device.
+
+ Example: "computer"
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.NotAvailable
+
+ string GetRemoteMinorClass(string address)
+
+ Get the minor device class of the specified device.
+
+ Example: "laptop"
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.NotAvailable
+
+ array{string} GetRemoteServiceClasses(string address)
+
+ Get the service classes of the specified device.
+
+ Example: ["networking", "object transfer"]
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.NotAvailable
+
+ uint32 GetRemoteClass(string address)
+
+ Get the remote major, minor, and service classes
+ encoded as 32 bit integer.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.NotAvailable
+
+ array{byte} GetRemoteFeatures(string address)
+
+ Get the remote features encoded as bit mask.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.NotAvailable
+
+ string GetRemoteName(string address)
+
+ Get the remote device's name. This request returns always
+ a cached name. The service daemon is responsible for
+ updating the cache.
+
+ NotAvailable error is returned if the name is not in
+ the cache. But if there is a discovery running, then
+ this function will return RequestDeferred. In this
+ case the service daemon will queue the request and
+ it will try to resolve the name at the next possible
+ opportunity. On success a RemoteNameUpdated signal will
+ be send and if a failure happens it will be indicated by
+ a RemoteNameFailed signal.
+
+ If this is an empty string, the UI might want to
+ display the BD_ADDR instead.
+
+ Example: "00:11:22:33:44:55", "Nokia 770"
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.NotAvailable
+ org.bluez.Error.NotReady
+ org.bluez.Error.RequestDeferred
+
+ string GetRemoteAlias(string address)
+
+ Returns alias name for remote device. If this is
+ an empty string value, the UI should show the
+ remote name instead.
+
+ An alias should supersede the remote name.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.NotAvailable
+
+ void SetRemoteAlias(string address, string alias)
+
+ Sets alias name for remote device. If alias name is
+ empty, then no alias is set.
+
+ On success the SetRemoteAlias method will produce a
+ RemoteAliasChanged signal which applications can use
+ to update their current display of the remote device
+ name.
+
+ Possible errors: org.bluez.Error.Failed
+ org.bluez.Error.InvalidArguments
+
+ void ClearRemoteAlias(string address)
+
+ Resets alias name for remote device. If there is no
+ alias set for the device this method will silently
+ succeed, but no RemoteAliasCleared signal has to be
+ sent in this case.
+
+ On success the ClearRemoteAlias method will produce
+ a RemoteAliasCleared signal.
+
+ Possible errors: org.bluez.Error.Failed
+ org.bluez.Error.InvalidArguments
+
+ string LastSeen(string address)
+
+ Returns the date and time when the adapter has been
+ seen by a discover procedure.
+
+ Example: "2006-02-08 12:00:00 GMT"
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.NotAvailable
+
+ Question: Can we find a better name?
+
+ string LastUsed(string address)
+
+ Returns the date and time of the last time when the
+ adapter has been connected.
+
+ Example: "2006-02-08 12:00:00 GMT"
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.NotAvailable
+
+ Question: Can we find a better name?
+
+ void DisconnectRemoteDevice(string address)
+
+ This method disconnects a specific remote device by
+ terminating the low-level ACL connection. The use of
+ this method should be restricted to administrator
+ use.
+
+ A RemoteDeviceDisconnectRequested signal will be
+ sent and the actual disconnection will only happen 2
+ seconds later. This enables upper-level applications
+ to terminate their connections gracefully before the
+ ACL connection is terminated.
+
+ Possible errors: org.bluez.Error.NotReady
+ org.bluez.Error.Failed
+ org.bluez.Error.NoSuchAdapter
+ org.bluez.Error.InvalidArguments
+ org.bluez.Error.NotConnected
+ org.bluez.Error.InProgress
+
+ void CreateBonding(string address)
+
+ This method creates a bonding with a remote device.
+
+ If a link key for this adapter already exists, this
+ procedure should fail instead of trying to create a
+ new pairing.
+
+ If no connection to the remote device exists, a
+ low-level ACL connection must be created.
+
+ This function will block and the calling application
+ should take care of setting are higher timeout. This
+ might be needed in case of a page timeout from the
+ low-level HCI commands.
+
+ In case of success it will send a BondingCreated
+ signal.
+
+ Possible errors: org.bluez.Error.NotReady
+ org.bluez.Error.Failed
+ org.bluez.Error.InvalidArguments
+ org.bluez.Error.AlreadyExists
+ org.bluez.Error.InProgress
+ org.bluez.Error.NoSuchAdapter
+ org.bluez.Error.ConnectionAttemptFailed
+ org.bluez.Error.AuthenticationFailed
+ org.bluez.Error.AuthenticationTimeout
+ org.bluez.Error.AuthenticationRejected
+ org.bluez.Error.AuthenticationCanceled
+
+ void CancelBondingProcess(string address)
+
+ This method will cancel the CreateBonding process.
+
+ The CreateBonding method will return
+ AuthenticationCanceled to signal that an attempt to
+ create a bonding has been canceled.
+
+ Possible errors: org.bluez.Error.NotReady
+ org.bluez.Error.Failed
+ org.bluez.Error.InvalidArguments
+ org.bluez.Error.NotInProgress
+ org.bluez.Error.NotAuthorized
+
+ void RemoveBonding(string address)
+
+ This method removes the bonding with a remote device.
+
+ For security reasons this includes removing the actual
+ link key and also disconnecting any open connections
+ for the remote device.
+
+ If the link key was stored on the Bluetooth chip, it
+ must be removed from there, too.
+
+ After deleting the link key this method will send a
+ BondingRemoved signal.
+
+ Possible errors: org.bluez.Error.NotReady
+ org.bluez.Error.Failed
+ org.bluez.Error.InvalidArguments
+ org.bluez.Error.NoSuchAdapter
+ org.bluez.Error.DoesNotExist
+
+ boolean HasBonding(string address)
+
+ Returns true if the remote device is bonded and false
+ if no link key is available.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+
+ array{string} ListBondings()
+
+ List device addresses of currently bonded adapter.
+
+ Possible errors: none
+
+ uint8 GetPinCodeLength(string address)
+
+ Returns the PIN code length that was used in the
+ pairing process.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.DoesNotExist
+
+ uint8 GetEncryptionKeySize(string address)
+
+ Returns the currently used encryption key size.
+
+ This method will fail if no connection to the address
+ has been established.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.Failed
+
+ void SetTrusted(string address)
+
+ Marks the remote device as trusted. Authorization
+ request will automatically succeed.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.AlreadyExists
+
+ boolean IsTrusted(string address)
+
+ Returns true if the user is trusted or false otherwise.
+ The address parameter must match one of the remote
+ devices of the service.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+
+ void RemoveTrust(string address)
+
+ Marks the remote device as not trusted.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.DoesNotExist
+
+ array{string} ListTrusts()
+
+ Returns a list of remote devices that are trusted.
+
+ void DiscoverDevices()
+
+ This method starts the device discovery procedure. This
+ includes an inquiry procedure and remote device name
+ resolving.
+
+ On start up this process will generate a DiscoveryStarted
+ signal and then return RemoteDeviceFound and also
+ RemoteNameUpdated signals. If the procedure has been
+ finished an DiscoveryCompleted signal will be sent.
+
+ Possible errors: org.bluez.Error.NotReady
+ org.bluez.Error.Failed
+ org.bluez.Error.InProgress
+ org.bluez.Error.NoSuchAdapter
+
+ void DiscoverDevicesWithoutNameResolving()
+
+ This method starts the device discovery procedure. This
+ includes an inquiry and an optional remote device name
+ resolving. The remote names can be retrieved with
+ GetRemoteName and in the case a name doesn't exist it
+ will be queued for later resolving and GetRemoteName
+ will return an error.
+
+ While this procedure is running every found device
+ will be returned with RemoteDeviceFound. While
+ DiscoverDevices() automatically resolves unknown
+ devices names and sends RemoteNameUpdated in this
+ case it will only happen if GetRemoteName has been
+ called and no previously stored name is available.
+
+ Possible errors: org.bluez.Error.NotReady
+ org.bluez.Error.Failed
+ org.bluez.Error.InProgress
+ org.bluez.Error.NoSuchAdapter
+
+ void CancelDiscovery()
+
+ This method will cancel any previous DiscoverDevices
+ or DiscoverDevicesWithoutNameResolving actions.
+
+ Possible errors: org.bluez.Error.NotReady
+ org.bluez.Error.Failed
+ org.bluez.Error.NotAuthorized
+ org.bluez.Error.NoSuchAdapter
+
+ void StartPeriodicDiscovery()
+
+ This method starts a periodic discovery.
+
+ Possible errors: org.bluez.error.NotReady
+ org.bluez.Error.Failed
+ org.bluez.Error.InProgress
+ org.bluez.Error.NoSuchAdapter
+
+ void StopPeriodicDiscovery()
+
+ This method stops a periodic discovery. If the
+ adapter is not in the periodic inquiry mode an
+ error(not authorized) is returned. Everyone can
+ request exit from this mode, it is not restricted
+ to start requestor.
+
+ Possible errors: org.bluez.Error.NotReady
+ org.bluez.Error.Failed
+ org.bluez.Error.NotAuthorized
+ org.bluez.Error.NoSuchAdapter
+
+ boolean IsPeriodicDiscovery()
+
+ Returns true if the periodic inquiry is active and
+ false if it is switched off.
+
+ Possible errors: none
+
+ void SetPeriodicDiscoveryNameResolving(boolean resolve_names)
+
+ Enable or disable automatic remote name resolving for
+ periodic discovery.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+
+ boolean GetPeriodicDiscoveryNameResolving()
+
+ Check if automatic remote name resolving is enabled or not
+ for periodic discovery.
+
+ Possible error: org.bluez.Error.InvalidArguments
+
+ array{uint32} GetRemoteServiceHandles(string address, string match)
+
+ This method will request the SDP database of a remote
+ device and retrieve the service record handles. To
+ request service browse send an empty match string.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.InProgress
+ org.bluez.Error.ConnectionAttemptFailed
+ org.bluez.Error.Failed
+
+ array{byte} GetRemoteServiceRecord(string address, uint32 handle)
+
+ This method will request the SDP database of a remote
+ device for a service record and return the binary
+ stream of it.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.InProgress
+ org.bluez.Error.Failed
+
+ string GetRemoteServiceRecordAsXML(string address, uint32 handle)
+
+ This method will request the SDP database of a remote
+ device for a service record and return its data in XML
+ format.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.InProgress
+ org.bluez.Error.Failed
+
+ array{string} GetRemoteServiceIdentifiers(string address)
+
+ This method will request the SDP database of a remote
+ device for all supported services. The identifiers are
+ returned in UUID 128 string format.
+
+ Possible errors: org.bluez.Error.InProgress
+ org.bluez.Error.Failed
+
+ void FinishRemoteServiceTransaction(string address)
+
+ This method will finish all SDP transaction for that
+ given address. In general this call is not needed,
+ but in cases of resources restricted devices it
+ is useful to call this to finish the SDP transaction
+ before proceeded with profile specific connections.
+
+ array{string} ListRemoteDevices()
+
+ List addresses of all known remote devices (bonded,
+ trusted and used).
+
+ Possible errors: none
+
+ array{string} ListRecentRemoteDevices(string date)
+
+ List addresses of all bonded, trusted, seen or used remote
+ devices since date. Bonded and trusted devices are always
+ included(the date informed is not applied).
+
+ date format is "YYYY-MM-DD HH:MM:SS GMT"
+
+ Possible errors: none
+
+Signals void ModeChanged(string mode)
+
+ If the current mode is changed with SetMode this signal
+ will inform about the new mode.
+
+ This signal can also be triggered by low-level HCI
+ commands.
+
+ void DiscoverableTimeoutChanged(uint32 timeout)
+
+ After changing the discoverable timeout this signal
+ provide the new timeout value.
+
+ void MinorClassChanged(string minor)
+
+ After changing the minor class with SetMinorClass this
+ signal will provide the new class value.
+
+ void NameChanged(string name)
+
+ After changing the local adapter name with SetName this
+ signal will provide the new name.
+
+ This signal can also be triggered by low-level HCI
+ commands.
+
+ void DiscoveryStarted()
+
+ This signal indicates that a device discovery
+ procedure has been started.
+
+ void DiscoveryCompleted()
+
+ This signal indicates that a device discovery
+ procedure has been completed.
+
+ void PeriodicDiscoveryStarted()
+
+ This signal indicates that a periodic discovery
+ procedure has been started.
+
+ void PeriodicDiscoveryStopped()
+
+ This signal indicates that a periodic discovery
+ procedure has been completed.
+
+ void RemoteDeviceFound(string address, uint32 class, int16 rssi)
+
+ This signal will be send every time an inquiry result
+ has been found by the service daemon. In general they
+ only appear during a device discovery.
+
+ void RemoteDeviceDisappeared(string address)
+
+ This signal will be send when an inquiry session for
+ a periodic discovery finishes and previously found
+ devices are no longer in range or visible.
+
+ void RemoteClassUpdated(string address, uint32 class)
+
+ This signal will be send every time the remote class
+ of device has been changed. This happens for example
+ after a remote connection attempt. This signal will
+ not be send if the class of device hasn't changed
+ compared to cached one.
+
+ void RemoteNameUpdated(string address, string name)
+
+ This signal will be send every time the service daemon
+ detect a new name for a remote device.
+
+ void RemoteIdentifiersUpdated(string address, array{string identifiers})
+
+ This signal is sent to indicate the provided services of a given
+ remote device. It will be sent after GetRemoteServiceIdentifiers
+ calls. This signal has at least one identifier and it does not
+ contain repeated entries.
+
+ void RemoteNameFailed(string address)
+
+ This signal will be sent every time the service daemon
+ tries to resolve a remote and this fails.
+
+ void RemoteNameRequested(string address)
+
+ This signal will be sent every time the service daemon
+ tries to resolve a remote name during discovery.
+
+ void RemoteAliasChanged(string address, string alias)
+
+ After changing an alias with SetRemoteAlias this
+ signal will indicate the new alias.
+
+ void RemoteAliasCleared(string address)
+
+ After removing an alias with ClearRemoteAlias this
+ signal will indicate that the alias is no longer
+ valid.
+
+ void RemoteDeviceConnected(string address)
+
+ This signal will be send if a low level connection
+ between two devices has been created.
+
+ void RemoteDeviceDisconnectRequested(string address)
+
+ This signal will be sent when a low level
+ disconnection to a remote device has been requested.
+ The actual disconnection will happen 2 seconds later.
+
+ void RemoteDeviceDisconnected(string address)
+
+ This signal will be send if a low level connection
+ between two devices has been terminated.
+
+ void BondingCreated(string address)
+
+ Signals that a successful bonding has been created.
+
+ void BondingRemoved(string address)
+
+ Signals that a bonding was removed.
+
+ void TrustAdded(string address)
+
+ Sent when SetTrusted() is called.
+
+ void TrustRemoved(string address)
+
+ Sent when RemoveTrust() is called.
+
+Service hierarchy
+=================
+
+Service org.bluez
+Interface org.bluez.Service
+Object path path from org.bluez.Manager.ListServices()
+
+Methods dict GetInfo()
+
+ Returns the service properties.
+
+ string GetIdentifier()
+
+ This method returns the service identifier.
+
+ string GetName()
+
+ This method returns the service name.
+
+ string GetDescription()
+
+ This method returns the service description.
+
+ string GetBusName() [experimental]
+
+ Returns the unique bus name of the service if it has
+ been started.
+
+ Possible errors: org.bluez.Error.NotAvailable
+
+ void Start()
+
+ This method tells the system to start the
+ service.
+
+ void Stop()
+
+ This method tells the system to stop the
+ service.
+
+ boolean IsRunning()
+
+ Returns true if the service has been started and
+ is currently active. Otherwise, it returns false.
+
+ boolean IsExternal()
+
+ Returns true if the service was registered using the
+ Database.RegisterService method instead of a .service
+ file. The Start and Stop methods are not applicable to
+ external services and will return an error.
+
+ array{string} ListTrusts() [experimental]
+
+ Returns a list of remote devices that are trusted
+ for the service.
+
+ void SetTrusted(string address) [experimental]
+
+ Marks the user as trusted.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.AlreadyExists
+
+ boolean IsTrusted(string address) [experimental]
+
+ Returns true if the user is trusted or false otherwise.
+ The address parameter must match one of the
+ current users of the service.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+
+ void RemoveTrust(string address) [experimental]
+
+ Marks the user as not trusted.
+
+ Possible errors: org.bluez.Error.InvalidArguments
+ org.bluez.Error.DoesNotExist
+
+Signals void Started()
+
+ The object path of this signal contains which service
+ was started.
+
+ void Stopped()
+
+ The object path of this signal contains which service
+ was stopped.
+
+ void TrustAdded(string address)
+
+ Sent when SetTrusted() is called.
+
+ void TrustRemoved(string address)
+
+ Sent when RemoveTrust() is called.
+
+
+Security hierarchy
+==================
+
+Service org.bluez
+Interface org.bluez.Security
+Object path /org/bluez or /org/bluez/{hci0,hci1,...}
+
+Methods void RegisterDefaultPasskeyAgent(string path)
+
+ This registers the default passkey agent. It can
+ register a passkey for all adapters or for a
+ specific device depending on with object path has
+ been used.
+
+ The path parameter defines the object path of the
+ passkey agent that will be called when a passkey
+ needs to be entered.
+
+ If an application disconnects from the bus all
+ registered passkey agent will be removed.
+
+ Possible errors: org.bluez.Error.AlreadyExists
+
+ void UnregisterDefaultPasskeyAgent(string path)
+
+ This unregisters a default passkey agent that has
+ been previously registered. The object path and
+ the path parameter must match the same values that
+ has been used on registration.
+
+ Possible errors: org.bluez.Error.DoesNotExist
+
+ void RegisterPasskeyAgent(string path, string address)
+
+ This registers the application passkey agent that
+ will be used for any application specific passkey
+ tasks.
+
+ The path parameter defines the object path of the
+ passkey agent that will be called when a passkey
+ needs to be entered. The address defines the remote
+ device that it will answer passkey requests for.
+
+ If an application disconnects from the bus all
+ registered passkey agent will be removed. It will
+ also be unregistered after a timeout and if the
+ pairing succeeds or fails. The application has to
+ take care of that it reregisters the passkey agent.
+
+ Possible errors: org.bluez.Error.AlreadyExists
+
+ void UnregisterPasskeyAgent(string path, string address)
+
+ This unregisters a passkey agent that has been
+ previously registered. The object path and the path
+ and address parameter must match the same values
+ that has been used on registration.
+
+ The method is actually only needed if an application
+ wants to removed the passkey agent and don't wanna
+ wait for the automatic timeout.
+
+ Possible errors: org.bluez.Error.DoesNotExist
+
+ void RegisterDefaultAuthorizationAgent(string path)
+
+ This registers the default authorization agent. It can
+ register an authorization agent for all adapters or
+ for a specific one depending on which object path has
+ been used.
+
+ The path parameter defines the object path of the
+ authorization agent that will be called when an
+ authorization request needs to be answered.
+
+ void UnregisterDefaultAuthorizationAgent(string path)
+
+ This unregisters a default authorization agent that has
+ been previously registered. The path parameter must
+ match the same value that has been used on
+ registration.
+
+
+PasskeyAgent hierarchy
+======================
+
+Service unique name
+Interface org.bluez.PasskeyAgent
+Object path freely definable
+
+Methods string Request(string path, string address)
+
+ This method gets called when the service daemon
+ needs to get the passkey for an authentication. The
+ return value is actual passkey. It is a 1 to 16
+ byte PIN code in UTF-8 format.
+
+ The first argument contains the path of the local
+ adapter and the second one the remote address.
+
+ Possible errors: org.bluez.Error.Rejected
+ org.bluez.Error.Canceled
+
+ void Cancel(string path, string address)
+
+ This method gets called to indicate that the
+ authentication request failed before a reply was
+ returned by the Request method.
+
+ void Release()
+
+ This method gets called when the service daemon
+ unregisters a passkey agent. An agent can use
+ it to do cleanup tasks. There is no need to
+ unregister the agent, because when this method
+ gets called it has already been unregistered.
+
+
+AuthorizationAgent hierarchy (experimental)
+===========================================
+
+Service unique name
+Interface org.bluez.AuthorizationAgent
+Object path freely definable
+
+Methods void Authorize(string adapter_path, string address,
+ string service_path, string uuid)
+
+ This method gets called when the service daemon wants
+ to get an authorization for accessing a service. This
+ method should return if the remote user is granted
+ access or an error otherwise.
+
+ The adapter_path parameter is the object path of the
+ local adapter. The address, service_path and action
+ parameters correspond to the remote device address,
+ the object path of the service and the uuid of the
+ profile.
+
+ Possible errors: org.bluez.Error.Rejected
+ org.bluez.Error.Canceled
+
+ void Cancel(string adapter_path, string address,
+ string service_path, string uuid)
+
+ This method cancels a previous authorization request.
+ The adapter_path, address, service_path and uuid
+ parameters must match the same values that have been
+ used when the Authorize() method was called.
+
+ void Release()
+
+ This method gets called when the service daemon
+ unregisters an authorization agent. An agent can
+ use it to do cleanup tasks. There is no need to
+ unregister the agent, because when this method
+ gets called it has already been unregistered.
diff --git a/src/dbus-common.c b/src/dbus-common.c
new file mode 100644
index 00000000..02edd683
--- /dev/null
+++ b/src/dbus-common.c
@@ -0,0 +1,353 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 <fcntl.h>
+#include <sys/ioctl.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "hcid.h"
+#include "manager.h"
+#include "adapter.h"
+#include "dbus-hci.h"
+#include "dbus-database.h"
+#include "dbus-common.h"
+
+#define BLUEZ_NAME "org.bluez"
+
+#define RECONNECT_RETRY_TIMEOUT 5000
+
+static int experimental = 0;
+
+int l2raw_connect(const char *local, const bdaddr_t *remote)
+{
+ struct sockaddr_l2 addr;
+ long arg;
+ int sk;
+
+ sk = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP);
+ if (sk < 0) {
+ error("Can't create socket: %s (%d)", strerror(errno), errno);
+ return sk;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.l2_family = AF_BLUETOOTH;
+ str2ba(local, &addr.l2_bdaddr);
+
+ if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ error("Can't bind socket: %s (%d)", strerror(errno), errno);
+ goto failed;
+ }
+
+ arg = fcntl(sk, F_GETFL);
+ if (arg < 0) {
+ error("Can't get file flags: %s (%d)", strerror(errno), errno);
+ goto failed;
+ }
+
+ arg |= O_NONBLOCK;
+ if (fcntl(sk, F_SETFL, arg) < 0) {
+ error("Can't set file flags: %s (%d)", strerror(errno), errno);
+ goto failed;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.l2_family = AF_BLUETOOTH;
+ bacpy(&addr.l2_bdaddr, remote);
+
+ if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ if (errno == EAGAIN || errno == EINPROGRESS)
+ return sk;
+ error("Can't connect socket: %s (%d)", strerror(errno), errno);
+ goto failed;
+ }
+
+ return sk;
+
+failed:
+ close(sk);
+ return -1;
+}
+
+void hcid_dbus_set_experimental(void)
+{
+ experimental = 1;
+}
+
+int hcid_dbus_use_experimental(void)
+{
+ return experimental;
+}
+
+static gboolean system_bus_reconnect(void *data)
+{
+ DBusConnection *conn = get_dbus_connection();
+ struct hci_dev_list_req *dl = NULL;
+ struct hci_dev_req *dr;
+ int sk, i;
+ gboolean ret_val = TRUE;
+
+ if (conn) {
+ if (dbus_connection_get_is_connected(conn))
+ return FALSE;
+ }
+
+ if (hcid_dbus_init() < 0)
+ return TRUE;
+
+ /* Create and bind HCI socket */
+ sk = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
+ if (sk < 0) {
+ error("Can't open HCI socket: %s (%d)",
+ strerror(errno), errno);
+ return TRUE;
+ }
+
+ dl = g_malloc0(HCI_MAX_DEV * sizeof(*dr) + sizeof(*dl));
+
+ dl->dev_num = HCI_MAX_DEV;
+ dr = dl->dev_req;
+
+ if (ioctl(sk, HCIGETDEVLIST, (void *) dl) < 0) {
+ info("Can't get device list: %s (%d)",
+ strerror(errno), errno);
+ goto failed;
+ }
+
+ /* reset the default device */
+ manager_set_default_adapter(-1);
+
+ for (i = 0; i < dl->dev_num; i++, dr++)
+ manager_register_adapter(dr->dev_id);
+
+ ret_val = FALSE;
+
+failed:
+ if (sk >= 0)
+ close(sk);
+
+ g_free(dl);
+
+ return ret_val;
+}
+
+static void disconnect_callback(void *user_data)
+{
+ set_dbus_connection(NULL);
+
+ g_timeout_add(RECONNECT_RETRY_TIMEOUT,
+ system_bus_reconnect, NULL);
+}
+
+void hcid_dbus_unregister(void)
+{
+ DBusConnection *conn = get_dbus_connection();
+ char **children;
+ int i;
+ uint16_t dev_id;
+
+ if (!conn || !dbus_connection_get_is_connected(conn))
+ return;
+
+ /* Unregister all paths in Adapter path hierarchy */
+ if (!dbus_connection_list_registered(conn, "/", &children))
+ return;
+
+ for (i = 0; children[i]; i++) {
+ char path[MAX_PATH_LENGTH];
+ struct adapter *adapter;
+
+ if (children[i][0] != 'h')
+ continue;
+
+ snprintf(path, sizeof(path), "/%s", children[i]);
+
+ adapter = manager_find_adapter_by_path(path);
+ if (!adapter)
+ continue;
+
+ dev_id = adapter_get_dev_id(adapter);
+ manager_unregister_adapter(dev_id);
+ }
+
+ dbus_free_string_array(children);
+}
+
+void hcid_dbus_exit(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);
+}
+
+int hcid_dbus_init(void)
+{
+ DBusConnection *conn;
+
+ conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, BLUEZ_NAME, NULL);
+ if (!conn)
+ return -1;
+
+ if (g_dbus_set_disconnect_function(conn, disconnect_callback,
+ NULL, NULL) == FALSE) {
+ dbus_connection_unref(conn);
+ return -1;
+ }
+
+ if (!manager_init(conn, "/"))
+ return -1;
+
+ set_dbus_connection(conn);
+
+ return 0;
+}
+
+static void dbus_message_iter_append_variant(DBusMessageIter *iter,
+ int type, void *val)
+{
+ DBusMessageIter value;
+ DBusMessageIter array;
+ char *sig;
+
+ switch (type) {
+ case DBUS_TYPE_STRING:
+ sig = DBUS_TYPE_STRING_AS_STRING;
+ break;
+ case DBUS_TYPE_BYTE:
+ sig = DBUS_TYPE_BYTE_AS_STRING;
+ break;
+ case DBUS_TYPE_INT16:
+ sig = DBUS_TYPE_INT16_AS_STRING;
+ break;
+ case DBUS_TYPE_UINT16:
+ sig = DBUS_TYPE_UINT16_AS_STRING;
+ break;
+ case DBUS_TYPE_INT32:
+ sig = DBUS_TYPE_INT32_AS_STRING;
+ break;
+ case DBUS_TYPE_UINT32:
+ sig = DBUS_TYPE_UINT32_AS_STRING;
+ break;
+ case DBUS_TYPE_BOOLEAN:
+ sig = DBUS_TYPE_BOOLEAN_AS_STRING;
+ break;
+ case DBUS_TYPE_ARRAY:
+ sig = DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_STRING_AS_STRING;
+ break;
+ case DBUS_TYPE_OBJECT_PATH:
+ sig = DBUS_TYPE_OBJECT_PATH_AS_STRING;
+ break;
+ default:
+ error("Could not append variant with type %d", type);
+ return;
+ }
+
+ dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, sig, &value);
+
+ if (type == DBUS_TYPE_ARRAY) {
+ int i;
+ const char ***str_array = val;
+
+ dbus_message_iter_open_container(&value, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_STRING_AS_STRING, &array);
+
+ for (i = 0; (*str_array)[i]; i++)
+ dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING,
+ &((*str_array)[i]));
+
+ dbus_message_iter_close_container(&value, &array);
+ } else
+ dbus_message_iter_append_basic(&value, type, val);
+
+ dbus_message_iter_close_container(iter, &value);
+}
+
+void dbus_message_iter_append_dict_entry(DBusMessageIter *dict,
+ const char *key, int type, void *val)
+{
+ DBusMessageIter entry;
+
+ dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY,
+ NULL, &entry);
+
+ dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
+
+ dbus_message_iter_append_variant(&entry, type, val);
+
+ dbus_message_iter_close_container(dict, &entry);
+}
+
+dbus_bool_t dbus_connection_emit_property_changed(DBusConnection *conn,
+ const char *path,
+ const char *interface,
+ const char *name,
+ int type, void *value)
+{
+ DBusMessage *signal;
+ DBusMessageIter iter;
+ gboolean ret;
+
+ 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);
+ dbus_message_iter_append_variant(&iter, type, value);
+
+ ret = dbus_connection_send(conn, signal, NULL);
+
+ dbus_message_unref(signal);
+ return ret;
+}
diff --git a/src/dbus-common.h b/src/dbus-common.h
new file mode 100644
index 00000000..8e009f70
--- /dev/null
+++ b/src/dbus-common.h
@@ -0,0 +1,44 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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
+
+int str2uuid(uuid_t *uuid, const char *string);
+
+int l2raw_connect(const char *local, const bdaddr_t *remote);
+
+#define check_address(address) bachk(address)
+
+void hcid_dbus_exit(void);
+int hcid_dbus_init(void);
+void hcid_dbus_unregister(void);
+
+void dbus_message_iter_append_dict_entry(DBusMessageIter *dict,
+ const char *key, int type, void *val);
+
+dbus_bool_t dbus_connection_emit_property_changed(DBusConnection *conn,
+ const char *path,
+ const char *interface,
+ const char *name,
+ int type, void *value);
diff --git a/src/dbus-database.c b/src/dbus-database.c
new file mode 100644
index 00000000..46b1cbde
--- /dev/null
+++ b/src/dbus-database.c
@@ -0,0 +1,219 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <gdbus.h>
+
+#include "hcid.h"
+#include "sdpd.h"
+#include "sdp-xml.h"
+#include "manager.h"
+#include "adapter.h"
+#include "dbus-hci.h"
+#include "dbus-common.h"
+#include "error.h"
+#include "dbus-database.h"
+
+static GSList *records = NULL;
+
+struct record_data {
+ uint32_t handle;
+ char *sender;
+ guint listener_id;
+};
+
+static struct record_data *find_record(uint32_t handle, const char *sender)
+{
+ GSList *list;
+
+ for (list = records; list; list = list->next) {
+ struct record_data *data = list->data;
+ if (handle == data->handle && !strcmp(sender, data->sender))
+ return data;
+ }
+
+ return NULL;
+}
+
+static void exit_callback(void *user_data)
+{
+ struct record_data *user_record = user_data;
+
+ debug("remove record");
+
+ records = g_slist_remove(records, user_record);
+
+ remove_record_from_server(user_record->handle);
+
+ g_free(user_record->sender);
+ g_free(user_record);
+}
+
+static inline DBusMessage *invalid_arguments(DBusMessage *msg)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments",
+ "Invalid arguments in method call");
+}
+
+static inline DBusMessage *not_available(DBusMessage *msg)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable",
+ "Not Available");
+}
+
+static inline DBusMessage *failed(DBusMessage *msg)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", "Failed");
+}
+
+int add_xml_record(DBusConnection *conn, const char *sender, bdaddr_t *src,
+ const char *record, dbus_uint32_t *handle)
+{
+ struct record_data *user_record;
+ sdp_record_t *sdp_record;
+
+ sdp_record = sdp_xml_parse_record(record, strlen(record));
+ if (!sdp_record) {
+ error("Parsing of XML service record failed");
+ return -EIO;
+ }
+
+ if (add_record_to_server(src, sdp_record) < 0) {
+ error("Failed to register service record");
+ sdp_record_free(sdp_record);
+ return -EIO;
+ }
+
+ user_record = g_new0(struct record_data, 1);
+
+ user_record->handle = sdp_record->handle;
+
+ user_record->sender = g_strdup(sender);
+
+ records = g_slist_append(records, user_record);
+
+ user_record->listener_id = g_dbus_add_disconnect_watch(conn, sender,
+ exit_callback, user_record, NULL);
+
+ debug("listener_id %d", user_record->listener_id);
+
+ *handle = user_record->handle;
+
+ return 0;
+}
+
+static DBusMessage *update_record(DBusConnection *conn, DBusMessage *msg,
+ bdaddr_t *src, dbus_uint32_t handle, sdp_record_t *sdp_record)
+{
+ int err;
+
+ if (remove_record_from_server(handle) < 0) {
+ sdp_record_free(sdp_record);
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".NotAvailable",
+ "Not Available");
+ }
+
+ sdp_record->handle = handle;
+ err = add_record_to_server(src, sdp_record);
+ if (err < 0) {
+ sdp_record_free(sdp_record);
+ error("Failed to update the service record");
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".Failed",
+ strerror(EIO));
+ }
+
+ return dbus_message_new_method_return(msg);
+}
+
+DBusMessage *update_xml_record(DBusConnection *conn,
+ DBusMessage *msg, bdaddr_t *src)
+{
+ struct record_data *user_record;
+ sdp_record_t *sdp_record;
+ const char *record;
+ dbus_uint32_t handle;
+ int len;
+
+ if (dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_UINT32, &handle,
+ DBUS_TYPE_STRING, &record,
+ DBUS_TYPE_INVALID) == FALSE)
+ return NULL;
+
+ len = (record ? strlen(record) : 0);
+ if (len == 0)
+ return invalid_arguments(msg);
+
+ user_record = find_record(handle, dbus_message_get_sender(msg));
+ if (!user_record)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".NotAvailable",
+ "Not Available");
+
+ sdp_record = sdp_xml_parse_record(record, len);
+ if (!sdp_record) {
+ error("Parsing of XML service record failed");
+ sdp_record_free(sdp_record);
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".Failed",
+ strerror(EIO));
+ }
+
+ return update_record(conn, msg, src, handle, sdp_record);
+}
+
+int remove_record(DBusConnection *conn, const char *sender,
+ dbus_uint32_t handle)
+{
+ struct record_data *user_record;
+
+ debug("remove record 0x%x", handle);
+
+ user_record = find_record(handle, sender);
+ if (!user_record)
+ return -1;
+
+ debug("listner_id %d", user_record->listener_id);
+
+ g_dbus_remove_watch(conn, user_record->listener_id);
+
+ exit_callback(user_record);
+
+ return 0;
+}
diff --git a/src/dbus-database.h b/src/dbus-database.h
new file mode 100644
index 00000000..2f5606fc
--- /dev/null
+++ b/src/dbus-database.h
@@ -0,0 +1,32 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 DATABASE_INTERFACE "org.bluez.Database"
+
+int add_xml_record(DBusConnection *conn, const char *sender, bdaddr_t *src,
+ const char *record, dbus_uint32_t *handle);
+DBusMessage *update_xml_record(DBusConnection *conn,
+ DBusMessage *msg, bdaddr_t *src);
+int remove_record(DBusConnection *conn, const char *sender,
+ dbus_uint32_t handle);
diff --git a/src/dbus-hci.c b/src/dbus-hci.c
new file mode 100644
index 00000000..e499aca2
--- /dev/null
+++ b/src/dbus-hci.c
@@ -0,0 +1,2030 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "hcid.h"
+#include "textfile.h"
+#include "manager.h"
+#include "adapter.h"
+#include "device.h"
+#include "error.h"
+#include "glib-helper.h"
+#include "dbus-common.h"
+#include "agent.h"
+#include "dbus-hci.h"
+
+static DBusConnection *connection = NULL;
+
+void bonding_request_free(struct bonding_request_info *bonding)
+{
+ struct btd_device *device;
+ char address[18];
+ struct agent *agent;
+
+ if (!bonding)
+ return;
+
+ 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);
+
+ ba2str(&bonding->bdaddr, address);
+
+ device = adapter_find_device(bonding->adapter, address);
+ agent = device_get_agent(device);
+
+ if (device && agent) {
+ agent_destroy(agent, FALSE);
+ device_set_agent(device, NULL);
+ }
+
+ g_free(bonding);
+}
+
+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;
+}
+
+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;
+}
+
+int found_device_add(GSList **list, bdaddr_t *bdaddr, int8_t rssi,
+ name_status_t name_status)
+{
+ struct remote_dev_info *dev, match;
+ GSList *l;
+
+ memset(&match, 0, sizeof(struct remote_dev_info));
+ bacpy(&match.bdaddr, bdaddr);
+ match.name_status = NAME_ANY;
+
+ /* ignore repeated entries */
+ l = g_slist_find_custom(*list, &match, (GCompareFunc) found_device_cmp);
+ if (l) {
+ /* device found, update the attributes */
+ dev = l->data;
+
+ if (rssi != 0)
+ dev->rssi = rssi;
+
+ /* Get remote name can be received while inquiring.
+ * Keep in mind that multiple inquiry result events can
+ * be received from the same remote device.
+ */
+ if (name_status != NAME_NOT_REQUIRED)
+ dev->name_status = name_status;
+
+ *list = g_slist_sort(*list, (GCompareFunc) dev_rssi_cmp);
+
+ return -EALREADY;
+ }
+
+ dev = g_new0(struct remote_dev_info, 1);
+
+ bacpy(&dev->bdaddr, bdaddr);
+ dev->rssi = rssi;
+ dev->name_status = name_status;
+
+ *list = g_slist_insert_sorted(*list, dev, (GCompareFunc) dev_rssi_cmp);
+
+ return 0;
+}
+
+static int found_device_remove(GSList **list, bdaddr_t *bdaddr)
+{
+ struct remote_dev_info *dev, match;
+ GSList *l;
+
+ memset(&match, 0, sizeof(struct remote_dev_info));
+ bacpy(&match.bdaddr, bdaddr);
+
+ l = g_slist_find_custom(*list, &match, (GCompareFunc) found_device_cmp);
+ if (!l)
+ return -1;
+
+ dev = l->data;
+ *list = g_slist_remove(*list, dev);
+ g_free(dev);
+
+ return 0;
+}
+
+int active_conn_find_by_bdaddr(const void *data, const void *user_data)
+{
+ const struct active_conn_info *con = data;
+ const bdaddr_t *bdaddr = user_data;
+
+ return bacmp(&con->bdaddr, bdaddr);
+}
+
+static int active_conn_find_by_handle(const void *data, const void *user_data)
+{
+ const struct active_conn_info *dev = data;
+ const uint16_t *handle = user_data;
+
+ if (dev->handle == *handle)
+ return 0;
+
+ return -1;
+}
+
+static int active_conn_append(GSList **list, bdaddr_t *bdaddr,
+ uint16_t handle)
+{
+ struct active_conn_info *dev;
+
+ dev = g_new0(struct active_conn_info, 1);
+
+ bacpy(&dev->bdaddr, bdaddr);
+ dev->handle = handle;
+
+ *list = g_slist_append(*list, dev);
+ return 0;
+}
+
+DBusMessage *new_authentication_return(DBusMessage *msg, uint8_t status)
+{
+ switch (status) {
+ case 0x00: /* success */
+ return dbus_message_new_method_return(msg);
+
+ case 0x04: /* page timeout */
+ case 0x08: /* 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 */
+ 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 adapter_mode_changed(struct adapter *adapter, uint8_t scan_mode)
+{
+ const char *mode;
+ const gchar *path = adapter_get_path(adapter);
+
+ adapter_set_scan_mode(adapter, scan_mode);
+
+ switch (scan_mode) {
+ case SCAN_DISABLED:
+ mode = "off";
+ adapter->mode = MODE_OFF;
+ break;
+ case SCAN_PAGE:
+ mode = "connectable";
+ adapter->mode = MODE_CONNECTABLE;
+ break;
+ case (SCAN_PAGE | SCAN_INQUIRY):
+
+ if (adapter->discov_timeout != 0)
+ adapter_set_discov_timeout(adapter, adapter->discov_timeout * 1000);
+
+ if (adapter->mode == MODE_LIMITED) {
+ mode = "limited";
+ } else {
+ adapter->mode = MODE_DISCOVERABLE;
+ mode = "discoverable";
+ }
+ break;
+ case SCAN_INQUIRY:
+ /* Address the scenario where another app changed the scan mode */
+ if (adapter->discov_timeout != 0)
+ adapter_set_discov_timeout(adapter, adapter->discov_timeout * 1000);
+
+ /* ignore, this event should not be sent*/
+ default:
+ /* ignore, reserved */
+ return;
+ }
+
+ dbus_connection_emit_property_changed(connection, path,
+ ADAPTER_INTERFACE, "Mode",
+ DBUS_TYPE_STRING, &mode);
+}
+
+/*****************************************************************
+ *
+ * Section reserved to HCI commands confirmation handling and low
+ * level events(eg: device attached/dettached.
+ *
+ *****************************************************************/
+
+static void pincode_cb(struct agent *agent, DBusError *err, const char *pincode,
+ struct btd_device *device)
+{
+ struct adapter *adapter = device_get_adapter(device);
+ pin_code_reply_cp pr;
+ bdaddr_t sba, dba;
+ size_t len;
+ int dev;
+ struct pending_auth_info *auth;
+ const gchar *destination = device_get_address(device);
+ uint16_t dev_id = adapter_get_dev_id(adapter);
+ const gchar *source = adapter_get_address(adapter);
+
+ /* No need to reply anything if the authentication already failed */
+ if (adapter->bonding && adapter->bonding->hci_status)
+ return;
+
+ dev = hci_open_dev(dev_id);
+ if (dev < 0) {
+ error("hci_open_dev(%d): %s (%d)", dev_id,
+ strerror(errno), errno);
+ return;
+ }
+
+ str2ba(source, &sba);
+ str2ba(destination, &dba);
+
+ auth = adapter_find_auth_request(adapter, &dba);
+
+ if (err) {
+ hci_send_cmd(dev, OGF_LINK_CTL,
+ OCF_PIN_CODE_NEG_REPLY, 6, &dba);
+ goto done;
+ }
+
+ len = strlen(pincode);
+
+ set_pin_length(&sba, len);
+
+ memset(&pr, 0, sizeof(pr));
+ bacpy(&pr.bdaddr, &dba);
+ memcpy(pr.pin_code, pincode, len);
+ pr.pin_len = len;
+ hci_send_cmd(dev, OGF_LINK_CTL, OCF_PIN_CODE_REPLY, PIN_CODE_REPLY_CP_SIZE, &pr);
+
+done:
+ if (auth) {
+ auth->replied = TRUE;
+ auth->agent = NULL;
+ }
+ hci_close_dev(dev);
+}
+
+int hcid_dbus_request_pin(int dev, bdaddr_t *sba, struct hci_conn_info *ci)
+{
+ char addr[18];
+ struct adapter *adapter;
+ struct btd_device *device;
+ struct agent *agent;
+ int ret;
+
+ adapter = manager_find_adapter(sba);
+ if (!adapter) {
+ error("No matching adapter found");
+ return -1;
+ }
+
+ ba2str(&ci->bdaddr, addr);
+
+ device = adapter_find_device(adapter, addr);
+
+ if (device)
+ agent = device_get_agent(device);
+
+ if (!agent)
+ agent = adapter->agent;
+
+ if (!agent)
+ return -EPERM;
+
+ if (!device) {
+ device = adapter_create_device(connection, adapter, addr);
+ if (!device)
+ return -ENODEV;
+ }
+
+ ret = agent_request_pincode(agent, device,
+ (agent_pincode_cb) pincode_cb,
+ device);
+ if (ret == 0) {
+ struct pending_auth_info *auth;
+ auth = adapter_new_auth_request(adapter, &ci->bdaddr,
+ AUTH_TYPE_PINCODE);
+ auth->agent = agent;
+ }
+
+ return ret;
+}
+
+static void confirm_cb(struct agent *agent, DBusError *err, void *user_data)
+{
+ struct btd_device *device = user_data;
+ struct adapter *adapter = device_get_adapter(device);
+ user_confirm_reply_cp cp;
+ int dd;
+ struct pending_auth_info *auth;
+ const gchar *destination = device_get_address(device);
+ uint16_t dev_id = adapter_get_dev_id(adapter);
+
+ /* No need to reply anything if the authentication already failed */
+ if (adapter->bonding && adapter->bonding->hci_status)
+ return;
+
+ dd = hci_open_dev(dev_id);
+ if (dd < 0) {
+ error("Unable to open hci%d", dev_id);
+ return;
+ }
+
+ memset(&cp, 0, sizeof(cp));
+ str2ba(destination, &cp.bdaddr);
+
+ auth = adapter_find_auth_request(adapter, &cp.bdaddr);
+
+ if (err)
+ hci_send_cmd(dd, OGF_LINK_CTL, OCF_USER_CONFIRM_NEG_REPLY,
+ USER_CONFIRM_REPLY_CP_SIZE, &cp);
+ else
+ hci_send_cmd(dd, OGF_LINK_CTL, OCF_USER_CONFIRM_REPLY,
+ USER_CONFIRM_REPLY_CP_SIZE, &cp);
+
+ if (auth) {
+ auth->replied = TRUE;
+ auth->agent = FALSE;
+ }
+
+ hci_close_dev(dd);
+}
+
+static void passkey_cb(struct agent *agent, DBusError *err, uint32_t passkey,
+ void *user_data)
+{
+ struct btd_device *device = user_data;
+ struct adapter *adapter = device_get_adapter(device);
+ user_passkey_reply_cp cp;
+ bdaddr_t dba;
+ int dd;
+ struct pending_auth_info *auth;
+ const gchar *destination = device_get_address(device);
+ uint16_t dev_id = adapter_get_dev_id(adapter);
+
+ /* No need to reply anything if the authentication already failed */
+ if (adapter->bonding && adapter->bonding->hci_status)
+ return;
+
+ dd = hci_open_dev(dev_id);
+ if (dd < 0) {
+ error("Unable to open hci%d", dev_id);
+ return;
+ }
+
+ str2ba(destination, &dba);
+
+ memset(&cp, 0, sizeof(cp));
+ bacpy(&cp.bdaddr, &dba);
+ cp.passkey = passkey;
+
+ auth = adapter_find_auth_request(adapter, &dba);
+
+ if (err)
+ hci_send_cmd(dd, OGF_LINK_CTL,
+ OCF_USER_PASSKEY_NEG_REPLY, 6, &dba);
+ else
+ hci_send_cmd(dd, OGF_LINK_CTL, OCF_USER_PASSKEY_REPLY,
+ USER_PASSKEY_REPLY_CP_SIZE, &cp);
+
+ if (auth) {
+ auth->replied = TRUE;
+ auth->agent = NULL;
+ }
+
+ hci_close_dev(dd);
+}
+
+static int get_auth_requirements(bdaddr_t *local, bdaddr_t *remote,
+ uint8_t *auth)
+{
+ struct hci_auth_info_req req;
+ char addr[18];
+ int err, dd, dev_id;
+
+ ba2str(local, addr);
+
+ dev_id = hci_devid(addr);
+ if (dev_id < 0)
+ return dev_id;
+
+ dd = hci_open_dev(dev_id);
+ if (dd < 0)
+ return dd;
+
+ memset(&req, 0, sizeof(req));
+ bacpy(&req.bdaddr, remote);
+
+ err = ioctl(dd, HCIGETAUTHINFO, (unsigned long) &req);
+ if (err < 0) {
+ debug("HCIGETAUTHINFO failed: %s (%d)",
+ strerror(errno), errno);
+ hci_close_dev(dd);
+ return err;
+ }
+
+ hci_close_dev(dd);
+
+ if (auth)
+ *auth = req.type;
+
+ return 0;
+}
+
+int hcid_dbus_user_confirm(bdaddr_t *sba, bdaddr_t *dba, uint32_t passkey)
+{
+ struct adapter *adapter;
+ struct btd_device *device;
+ struct agent *agent;
+ char addr[18];
+ uint8_t type;
+ struct pending_auth_info *auth;
+ uint16_t dev_id;
+
+ adapter = manager_find_adapter(sba);
+ if (!adapter) {
+ error("No matching adapter found");
+ return -1;
+ }
+
+ dev_id = adapter_get_dev_id(adapter);
+
+ if (get_auth_requirements(sba, dba, &type) < 0) {
+ int dd;
+
+ dd = hci_open_dev(dev_id);
+ if (dd < 0) {
+ error("Unable to open hci%d", dev_id);
+ return -1;
+ }
+
+ hci_send_cmd(dd, OGF_LINK_CTL,
+ OCF_USER_CONFIRM_NEG_REPLY, 6, dba);
+
+ hci_close_dev(dd);
+
+ return 0;
+ }
+
+ ba2str(dba, addr);
+
+ device = adapter_get_device(connection, adapter, addr);
+ if (!device) {
+ error("Device creation failed");
+ return -1;
+ }
+
+ /* If no MITM protection required, auto-accept */
+ if (!(device_get_auth(device) & 0x01) && !(type & 0x01)) {
+ int dd;
+
+ dd = hci_open_dev(dev_id);
+ if (dd < 0) {
+ error("Unable to open hci%d", dev_id);
+ return -1;
+ }
+
+ hci_send_cmd(dd, OGF_LINK_CTL,
+ OCF_USER_CONFIRM_REPLY, 6, dba);
+
+ hci_close_dev(dd);
+
+ return 0;
+ }
+
+ agent = device_get_agent(device);
+
+ if (!agent)
+ agent = adapter->agent;
+
+ if (!agent) {
+ error("No agent available for user confirm request");
+ return -1;
+ }
+
+ if (agent_request_confirmation(agent, device, passkey,
+ confirm_cb, device) < 0) {
+ error("Requesting passkey failed");
+ return -1;
+ }
+
+ auth = adapter_new_auth_request(adapter, dba, AUTH_TYPE_CONFIRM);
+ auth->agent = agent;
+
+ return 0;
+}
+
+int hcid_dbus_user_passkey(bdaddr_t *sba, bdaddr_t *dba)
+{
+ struct adapter *adapter;
+ struct btd_device *device;
+ struct agent *agent;
+ char addr[18];
+ struct pending_auth_info *auth;
+
+ adapter = manager_find_adapter(sba);
+ if (!adapter) {
+ error("No matching adapter found");
+ return -1;
+ }
+
+ ba2str(dba, addr);
+
+ device = adapter_get_device(connection, adapter, addr);
+
+ if (device)
+ agent = device_get_agent(device);
+
+ if (!agent)
+ agent = adapter->agent;
+
+ if (!agent) {
+ error("No agent available for user confirm request");
+ return -1;
+ }
+
+ if (agent_request_passkey(agent, device, passkey_cb, device) < 0) {
+ error("Requesting passkey failed");
+ return -1;
+ }
+
+ auth = adapter_new_auth_request(adapter, dba, AUTH_TYPE_PASSKEY);
+ auth->agent = agent;
+
+ return 0;
+}
+
+int hcid_dbus_user_notify(bdaddr_t *sba, bdaddr_t *dba, uint32_t passkey)
+{
+ struct adapter *adapter;
+ struct btd_device *device;
+ struct agent *agent;
+ char addr[18];
+ struct pending_auth_info *auth;
+
+ adapter = manager_find_adapter(sba);
+ if (!adapter) {
+ error("No matching adapter found");
+ return -1;
+ }
+
+ ba2str(dba, addr);
+
+ device = adapter_get_device(connection, adapter, addr);
+ if (device)
+ agent = device_get_agent(device);
+
+ if (!agent)
+ agent = adapter->agent;
+
+ if (!agent) {
+ error("No agent available for user confirm request");
+ return -1;
+ }
+
+ if (agent_display_passkey(agent, device, passkey) < 0) {
+ error("Displaying passkey failed");
+ return -1;
+ }
+
+ auth = adapter_new_auth_request(adapter, dba, AUTH_TYPE_NOTIFY);
+ auth->agent = agent;
+
+ return 0;
+}
+
+void hcid_dbus_bonding_process_complete(bdaddr_t *local, bdaddr_t *peer,
+ uint8_t status)
+{
+ struct adapter *adapter;
+ char peer_addr[18];
+ const char *paddr = peer_addr;
+ DBusMessage *reply;
+ struct btd_device *device;
+ struct bonding_request_info *bonding;
+ gboolean paired = TRUE;
+ struct pending_auth_info *auth;
+ const gchar *dev_path;
+ const gchar *path;
+
+ debug("hcid_dbus_bonding_process_complete: status=%02x", status);
+
+ ba2str(peer, peer_addr);
+
+ adapter = manager_find_adapter(local);
+ if (!adapter) {
+ error("Unable to find matching adapter");
+ return;
+ }
+
+ if (status) {
+ if (adapter->bonding)
+ adapter->bonding->hci_status = status;
+ }
+
+ auth = adapter_find_auth_request(adapter, peer);
+ if (!auth) {
+ debug("hcid_dbus_bonding_process_complete: no pending auth request");
+ goto proceed;
+ }
+
+ if (auth->agent)
+ agent_cancel(auth->agent);
+
+ adapter_remove_auth_request(adapter, peer);
+
+ if (status)
+ goto proceed;
+
+ device = adapter_get_device(connection, adapter, paddr);
+ if (device) {
+ debug("hcid_dbus_bonding_process_complete: removing temporary flag");
+
+ device_set_temporary(device, FALSE);
+ dev_path = device_get_path(device);
+ path = adapter_get_path(adapter);
+
+ g_dbus_emit_signal(connection, path,
+ ADAPTER_INTERFACE, "DeviceCreated",
+ DBUS_TYPE_OBJECT_PATH, &dev_path,
+ DBUS_TYPE_INVALID);
+
+ dbus_connection_emit_property_changed(connection, dev_path,
+ DEVICE_INTERFACE, "Paired",
+ DBUS_TYPE_BOOLEAN, &paired);
+ }
+
+proceed:
+ bonding = adapter->bonding;
+ if (!bonding || bacmp(&bonding->bdaddr, peer))
+ return; /* skip: no bonding req pending */
+
+ if (bonding->cancel) {
+ /* reply authentication canceled */
+ reply = new_authentication_return(bonding->msg,
+ HCI_OE_USER_ENDED_CONNECTION);
+ g_dbus_send_message(connection, reply);
+ goto cleanup;
+ }
+
+ /* reply authentication success or an error */
+ if (dbus_message_is_method_call(bonding->msg, ADAPTER_INTERFACE,
+ "CreateBonding")) {
+ reply = new_authentication_return(bonding->msg, status);
+ dbus_connection_send(connection, reply, NULL);
+ dbus_message_unref(reply);
+ } else if ((device = adapter_find_device(adapter, paddr))) {
+ if (status) {
+ reply = new_authentication_return(bonding->msg, status);
+ dbus_connection_send(connection, reply, NULL);
+ dbus_message_unref(reply);
+ } else {
+ device_set_temporary(device, FALSE);
+ device_browse(device, bonding->conn,
+ bonding->msg, NULL);
+ }
+ }
+
+cleanup:
+ g_dbus_remove_watch(connection, adapter->bonding->listener_id);
+
+ if (adapter->bonding->io_id)
+ g_source_remove(adapter->bonding->io_id);
+ g_io_channel_close(adapter->bonding->io);
+ bonding_request_free(adapter->bonding);
+ adapter->bonding = NULL;
+}
+
+void hcid_dbus_inquiry_start(bdaddr_t *local)
+{
+ struct adapter *adapter;
+ const gchar *path;
+
+ adapter = manager_find_adapter(local);
+ if (!adapter) {
+ error("Unable to find matching adapter");
+ return;
+ }
+
+ adapter->discov_active = 1;
+ /*
+ * Cancel pending remote name request and clean the device list
+ * when inquiry is supported in periodic inquiry idle state.
+ */
+ if (adapter->pdiscov_active)
+ pending_remote_name_cancel(adapter);
+
+ /* Disable name resolution for non D-Bus clients */
+ if (!adapter->discov_requestor)
+ adapter->discov_type &= ~RESOLVE_NAME;
+
+ path = adapter_get_path(adapter);
+
+ dbus_connection_emit_property_changed(connection, path,
+ ADAPTER_INTERFACE, "PeriodicDiscovery",
+ DBUS_TYPE_BOOLEAN, &adapter->discov_active);
+
+ g_dbus_emit_signal(connection, path,
+ ADAPTER_INTERFACE, "DiscoveryStarted",
+ DBUS_TYPE_INVALID);
+}
+
+int found_device_req_name(struct adapter *adapter)
+{
+ struct hci_request rq;
+ evt_cmd_status rp;
+ remote_name_req_cp cp;
+ struct remote_dev_info match;
+ GSList *l;
+ int dd, req_sent = 0;
+ uint16_t dev_id = adapter_get_dev_id(adapter);
+
+ /* get the next remote address */
+ if (!adapter->found_devices)
+ return -ENODATA;
+
+ memset(&match, 0, sizeof(struct remote_dev_info));
+ bacpy(&match.bdaddr, BDADDR_ANY);
+ match.name_status = NAME_REQUIRED;
+
+ l = g_slist_find_custom(adapter->found_devices, &match,
+ (GCompareFunc) found_device_cmp);
+ if (!l)
+ return -ENODATA;
+
+ dd = hci_open_dev(dev_id);
+ if (dd < 0)
+ return -errno;
+
+ memset(&rq, 0, sizeof(rq));
+ rq.ogf = OGF_LINK_CTL;
+ rq.ocf = OCF_REMOTE_NAME_REQ;
+ rq.cparam = &cp;
+ rq.clen = REMOTE_NAME_REQ_CP_SIZE;
+ rq.rparam = &rp;
+ rq.rlen = EVT_CMD_STATUS_SIZE;
+ rq.event = EVT_CMD_STATUS;
+
+ /* send at least one request or return failed if the list is empty */
+ do {
+ struct remote_dev_info *dev = l->data;
+
+ /* flag to indicate the current remote name requested */
+ dev->name_status = NAME_REQUESTED;
+
+ memset(&rp, 0, sizeof(rp));
+ memset(&cp, 0, sizeof(cp));
+ bacpy(&cp.bdaddr, &dev->bdaddr);
+ cp.pscan_rep_mode = 0x02;
+
+ if (hci_send_req(dd, &rq, 500) < 0)
+ error("Unable to send the HCI remote name request: %s (%d)",
+ strerror(errno), errno);
+
+ if (!rp.status) {
+ req_sent = 1;
+ break;
+ }
+
+ error("Remote name request failed with status 0x%02x",
+ rp.status);
+
+ /* if failed, request the next element */
+ /* remove the element from the list */
+ adapter->found_devices = g_slist_remove(adapter->found_devices, dev);
+ g_free(dev);
+
+ /* get the next element */
+ l = g_slist_find_custom(adapter->found_devices, &match,
+ (GCompareFunc) found_device_cmp);
+
+ } while (l);
+
+ hci_close_dev(dd);
+
+ if (!req_sent)
+ return -ENODATA;
+
+ return 0;
+}
+
+static void send_out_of_range(const char *path, GSList *l)
+{
+ while (l) {
+ const char *peer_addr = l->data;
+
+ g_dbus_emit_signal(connection, path,
+ ADAPTER_INTERFACE, "DeviceDisappeared",
+ DBUS_TYPE_STRING, &peer_addr,
+ DBUS_TYPE_INVALID);
+
+ l = l->next;
+ }
+}
+
+void hcid_dbus_inquiry_complete(bdaddr_t *local)
+{
+ struct adapter *adapter;
+ struct remote_dev_info *dev;
+ bdaddr_t tmp;
+ const gchar *path;
+
+ adapter = manager_find_adapter(local);
+ if (!adapter) {
+ error("Unable to find matching adapter");
+ return;
+ }
+
+ path = adapter_get_path(adapter);
+
+ /* Out of range verification */
+ if (adapter->pdiscov_active && !adapter->discov_active) {
+ GSList *l;
+
+ send_out_of_range(path, adapter->oor_devices);
+
+ g_slist_foreach(adapter->oor_devices, (GFunc) free, NULL);
+ g_slist_free(adapter->oor_devices);
+ adapter->oor_devices = NULL;
+
+ l = adapter->found_devices;
+ while (l) {
+ dev = l->data;
+ baswap(&tmp, &dev->bdaddr);
+ adapter->oor_devices = g_slist_append(adapter->oor_devices,
+ batostr(&tmp));
+ l = l->next;
+ }
+ }
+
+ adapter->pinq_idle = 1;
+
+ /*
+ * Enable resolution again: standard inquiry can be
+ * received in the periodic inquiry idle state.
+ */
+ if (adapter->pdiscov_requestor && adapter->pdiscov_resolve_names)
+ adapter->discov_type |= RESOLVE_NAME;
+
+ /*
+ * The following scenarios can happen:
+ * 1. standard inquiry: always send discovery completed signal
+ * 2. standard inquiry + name resolving: send discovery completed
+ * after name resolving
+ * 3. periodic inquiry: skip discovery completed signal
+ * 4. periodic inquiry + standard inquiry: always send discovery
+ * completed signal
+ *
+ * Keep in mind that non D-Bus requests can arrive.
+ */
+
+ if (!found_device_req_name(adapter))
+ return; /* skip - there is name to resolve */
+
+ if (adapter->discov_active) {
+ g_dbus_emit_signal(connection, path,
+ ADAPTER_INTERFACE, "DiscoveryCompleted",
+ DBUS_TYPE_INVALID);
+
+ adapter->discov_active = 0;
+ }
+
+ /* free discovered devices list */
+ g_slist_foreach(adapter->found_devices, (GFunc) g_free, NULL);
+ g_slist_free(adapter->found_devices);
+ adapter->found_devices = NULL;
+
+ if (adapter->discov_requestor) {
+ g_dbus_remove_watch(connection, adapter->discov_listener);
+ adapter->discov_listener = 0;
+ g_free(adapter->discov_requestor);
+ adapter->discov_requestor = NULL;
+
+ /* If there is a pending reply for discovery cancel */
+ if (adapter->discovery_cancel) {
+ DBusMessage *reply;
+ reply = dbus_message_new_method_return(adapter->discovery_cancel);
+ dbus_connection_send(connection, reply, NULL);
+ dbus_message_unref(reply);
+ dbus_message_unref(adapter->discovery_cancel);
+ adapter->discovery_cancel = NULL;
+ }
+
+ /* reset the discover type for standard inquiry only */
+ adapter->discov_type &= ~STD_INQUIRY;
+ }
+}
+
+void hcid_dbus_periodic_inquiry_start(bdaddr_t *local, uint8_t status)
+{
+ struct adapter *adapter;
+ const gchar *path;
+
+ /* Don't send the signal if the cmd failed */
+ if (status)
+ return;
+
+ adapter = manager_find_adapter(local);
+ if (!adapter) {
+ error("No matching adapter found");
+ return;
+ }
+
+ adapter->pdiscov_active = 1;
+
+ /* Disable name resolution for non D-Bus clients */
+ if (!adapter->pdiscov_requestor)
+ adapter->discov_type &= ~RESOLVE_NAME;
+
+ path = adapter_get_path(adapter);
+
+ dbus_connection_emit_property_changed(connection, path,
+ ADAPTER_INTERFACE, "PeriodicDiscovery",
+ DBUS_TYPE_BOOLEAN, &adapter->pdiscov_active);
+}
+
+void hcid_dbus_periodic_inquiry_exit(bdaddr_t *local, uint8_t status)
+{
+ struct adapter *adapter;
+ const gchar *path;
+
+ /* Don't send the signal if the cmd failed */
+ if (status)
+ return;
+
+ adapter = manager_find_adapter(local);
+ if (!adapter) {
+ error("No matching adapter found");
+ return;
+ }
+
+ /* reset the discover type to be able to handle D-Bus and non D-Bus
+ * requests */
+ adapter->pdiscov_active = 0;
+ adapter->discov_type &= ~(PERIODIC_INQUIRY | RESOLVE_NAME);
+
+ /* free discovered devices list */
+ g_slist_foreach(adapter->found_devices, (GFunc) g_free, NULL);
+ g_slist_free(adapter->found_devices);
+ adapter->found_devices = NULL;
+
+ /* free out of range devices list */
+ g_slist_foreach(adapter->oor_devices, (GFunc) free, NULL);
+ g_slist_free(adapter->oor_devices);
+ adapter->oor_devices = NULL;
+
+ if (adapter->pdiscov_requestor) {
+ g_dbus_remove_watch(connection, adapter->pdiscov_listener);
+ adapter->pdiscov_listener = 0;
+ g_free(adapter->pdiscov_requestor);
+ adapter->pdiscov_requestor = NULL;
+ }
+
+ path = adapter_get_path(adapter);
+
+ /* workaround: inquiry completed is not sent when exiting from
+ * periodic inquiry */
+ if (adapter->discov_active) {
+ g_dbus_emit_signal(connection, path,
+ ADAPTER_INTERFACE, "DiscoveryCompleted",
+ DBUS_TYPE_INVALID);
+
+ adapter->discov_active = 0;
+ }
+
+ /* Send discovery completed signal if there isn't name to resolve */
+ dbus_connection_emit_property_changed(connection, path,
+ ADAPTER_INTERFACE, "PeriodicDiscovery",
+ DBUS_TYPE_BOOLEAN, &adapter->discov_active);
+}
+
+static char *extract_eir_name(uint8_t *data, uint8_t *type)
+{
+ if (!data || !type)
+ return NULL;
+
+ if (data[0] == 0)
+ return NULL;
+
+ *type = data[1];
+
+ switch (*type) {
+ case 0x08:
+ case 0x09:
+ return strndup((char *) (data + 2), data[0] - 1);
+ }
+
+ return NULL;
+}
+
+static void append_dict_valist(DBusMessageIter *iter,
+ const char *first_key,
+ va_list var_args)
+{
+ DBusMessageIter dict;
+ const char *key;
+ int type;
+ 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 *);
+ dbus_message_iter_append_dict_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);
+
+ dbus_connection_send(connection, signal, NULL);
+
+ dbus_message_unref(signal);
+}
+
+void hcid_dbus_inquiry_result(bdaddr_t *local, bdaddr_t *peer, uint32_t class,
+ int8_t rssi, uint8_t *data)
+{
+ char filename[PATH_MAX + 1];
+ struct adapter *adapter;
+ GSList *l;
+ char local_addr[18], peer_addr[18], *name, *tmp_name;
+ const char *paddr = peer_addr;
+ struct remote_dev_info match;
+ dbus_int16_t tmp_rssi = rssi;
+ uint8_t name_type = 0x00;
+ name_status_t name_status;
+ const gchar *path;
+
+ ba2str(local, local_addr);
+ ba2str(peer, peer_addr);
+
+ adapter = manager_find_adapter(local);
+ if (!adapter) {
+ error("No matching adapter found");
+ return;
+ }
+
+ write_remote_class(local, peer, class);
+
+ if (data)
+ write_remote_eir(local, peer, data);
+
+ /*
+ * workaround to identify situation when the daemon started and
+ * a standard inquiry or periodic inquiry was already running
+ */
+ if (!adapter->discov_active && !adapter->pdiscov_active)
+ adapter->pdiscov_active = 1;
+
+ /* reset the idle flag when the inquiry complete event arrives */
+ if (adapter->pdiscov_active) {
+ adapter->pinq_idle = 0;
+
+ /* Out of range list update */
+ l = g_slist_find_custom(adapter->oor_devices, peer_addr,
+ (GCompareFunc) strcmp);
+ if (l) {
+ char *dev = l->data;
+ adapter->oor_devices = g_slist_remove(adapter->oor_devices,
+ dev);
+ g_free(dev);
+ }
+ }
+
+ memset(&match, 0, sizeof(struct remote_dev_info));
+ bacpy(&match.bdaddr, peer);
+ match.name_status = NAME_SENT;
+ /* if found: don't send the name again */
+ l = g_slist_find_custom(adapter->found_devices, &match,
+ (GCompareFunc) found_device_cmp);
+ if (l)
+ return;
+
+ /* the inquiry result can be triggered by NON D-Bus client */
+ if (adapter->discov_type & RESOLVE_NAME)
+ name_status = NAME_REQUIRED;
+ else
+ name_status = NAME_NOT_REQUIRED;
+
+ create_name(filename, PATH_MAX, STORAGEDIR, local_addr, "names");
+ name = textfile_get(filename, peer_addr);
+
+ tmp_name = extract_eir_name(data, &name_type);
+ if (tmp_name) {
+ if (name_type == 0x09) {
+ write_device_name(local, peer, tmp_name);
+ name_status = NAME_NOT_REQUIRED;
+
+ if (name)
+ g_free(name);
+
+ name = tmp_name;
+ } else {
+ if (name)
+ free(tmp_name);
+ else
+ name = tmp_name;
+ }
+ }
+
+ path = adapter_get_path(adapter);
+
+ if (name) {
+ if (name_type != 0x08)
+ name_status = NAME_SENT;
+
+ emit_device_found(path, paddr,
+ "Address", DBUS_TYPE_STRING, &paddr,
+ "Class", DBUS_TYPE_UINT32, &class,
+ "RSSI", DBUS_TYPE_INT16, &tmp_rssi,
+ "Name", DBUS_TYPE_STRING, &name,
+ NULL);
+
+ g_free(name);
+ } else {
+ emit_device_found(path, paddr,
+ "Address", DBUS_TYPE_STRING, &paddr,
+ "Class", DBUS_TYPE_UINT32, &class,
+ "RSSI", DBUS_TYPE_INT16, &tmp_rssi,
+ NULL);
+ }
+
+ /* add in the list to track name sent/pending */
+ found_device_add(&adapter->found_devices, peer, rssi, name_status);
+}
+
+void hcid_dbus_remote_class(bdaddr_t *local, bdaddr_t *peer, uint32_t class)
+{
+ char peer_addr[18];
+ const char *paddr = peer_addr;
+ uint32_t old_class = 0;
+ struct adapter *adapter;
+ GSList *l;
+ struct btd_device *device;
+ const gchar *dev_path;
+
+ read_remote_class(local, peer, &old_class);
+
+ if (old_class == class)
+ return;
+
+ adapter = manager_find_adapter(local);
+ if (!adapter) {
+ error("No matching adapter found");
+ return;
+ }
+
+ ba2str(peer, peer_addr);
+
+ l = g_slist_find_custom(adapter->devices, paddr,
+ (GCompareFunc) device_address_cmp);
+ if (!l)
+ return;
+
+ device = l->data;
+
+ dev_path = device_get_path(device);
+
+ dbus_connection_emit_property_changed(connection, dev_path,
+ DEVICE_INTERFACE, "Class",
+ DBUS_TYPE_UINT32, &class);
+}
+
+void hcid_dbus_remote_name(bdaddr_t *local, bdaddr_t *peer, uint8_t status,
+ char *name)
+{
+ struct adapter *adapter;
+ char peer_addr[18];
+ const char *paddr = peer_addr;
+ const gchar *dev_path;
+ const gchar *path;
+
+ adapter = manager_find_adapter(local);
+ if (!adapter) {
+ error("No matching adapter found");
+ return;
+ }
+
+ ba2str(peer, peer_addr);
+
+ if (!status) {
+ struct btd_device *device;
+
+ device = adapter_find_device(adapter, paddr);
+ if (device) {
+
+ dev_path = device_get_path(device);
+
+ dbus_connection_emit_property_changed(connection,
+ dev_path, DEVICE_INTERFACE,
+ "Name", DBUS_TYPE_STRING, &name);
+ }
+ }
+
+ /* remove from remote name request list */
+ found_device_remove(&adapter->found_devices, peer);
+
+ /* check if there is more devices to request names */
+ if (!found_device_req_name(adapter))
+ return; /* skip if a new request has been sent */
+
+ /* free discovered devices list */
+ g_slist_foreach(adapter->found_devices, (GFunc) g_free, NULL);
+ g_slist_free(adapter->found_devices);
+ adapter->found_devices = NULL;
+
+ /* The discovery completed signal must be sent only for discover
+ * devices request WITH name resolving */
+ if (adapter->discov_requestor) {
+ g_dbus_remove_watch(connection, adapter->discov_listener);
+ adapter->discov_listener = 0;
+ g_free(adapter->discov_requestor);
+ adapter->discov_requestor = NULL;
+
+ /* If there is a pending reply for discovery cancel */
+ if (adapter->discovery_cancel) {
+ DBusMessage *reply;
+ reply = dbus_message_new_method_return(adapter->discovery_cancel);
+ dbus_connection_send(connection, reply, NULL);
+ dbus_message_unref(reply);
+ dbus_message_unref(adapter->discovery_cancel);
+ adapter->discovery_cancel = NULL;
+ }
+
+ /* Disable name resolution for non D-Bus clients */
+ if (!adapter->pdiscov_requestor)
+ adapter->discov_type &= ~RESOLVE_NAME;
+ }
+
+ path = adapter_get_path(adapter);
+
+ if (adapter->discov_active) {
+ g_dbus_emit_signal(connection, path,
+ ADAPTER_INTERFACE, "DiscoveryCompleted",
+ DBUS_TYPE_INVALID);
+
+ adapter->discov_active = 0;
+ }
+}
+
+void hcid_dbus_conn_complete(bdaddr_t *local, uint8_t status, uint16_t handle,
+ bdaddr_t *peer)
+{
+ char peer_addr[18];
+ const char *paddr = peer_addr;
+ struct adapter *adapter;
+ const gchar *dev_path;
+
+ adapter = manager_find_adapter(local);
+ if (!adapter) {
+ error("No matching adapter found");
+ return;
+ }
+
+ ba2str(peer, peer_addr);
+
+ if (status) {
+ struct pending_auth_info *auth;
+
+ auth = adapter_find_auth_request(adapter, peer);
+ if (auth && auth->agent)
+ agent_cancel(auth->agent);
+
+ adapter_remove_auth_request(adapter, peer);
+
+ if (adapter->bonding)
+ adapter->bonding->hci_status = status;
+ } else {
+ struct btd_device *device;
+ gboolean connected = TRUE;
+
+ device = adapter_find_device(adapter, paddr);
+ if (device) {
+
+ dev_path = device_get_path(device);
+
+ dbus_connection_emit_property_changed(connection,
+ dev_path, DEVICE_INTERFACE,
+ "Connected", DBUS_TYPE_BOOLEAN,
+ &connected);
+ }
+
+ /* add in the active connetions list */
+ active_conn_append(&adapter->active_conn, peer, handle);
+ }
+}
+
+void hcid_dbus_disconn_complete(bdaddr_t *local, uint8_t status,
+ uint16_t handle, uint8_t reason)
+{
+ DBusMessage *reply;
+ char peer_addr[18];
+ const char *paddr = peer_addr;
+ struct adapter *adapter;
+ struct btd_device *device;
+ struct active_conn_info *dev;
+ GSList *l;
+ gboolean connected = FALSE;
+ struct pending_auth_info *auth;
+ const gchar *destination;
+ const gchar *dev_path;
+ uint16_t dev_id;
+
+ if (status) {
+ error("Disconnection failed: 0x%02x", status);
+ return;
+ }
+
+ adapter = manager_find_adapter(local);
+ if (!adapter) {
+ error("No matching adapter found");
+ return;
+ }
+
+ l = g_slist_find_custom(adapter->active_conn, &handle,
+ active_conn_find_by_handle);
+
+ if (!l)
+ return;
+
+ dev = l->data;
+
+ ba2str(&dev->bdaddr, peer_addr);
+
+ dev_id = adapter_get_dev_id(adapter);
+
+ /* clean pending HCI cmds */
+ hci_req_queue_remove(dev_id, &dev->bdaddr);
+
+ /* Cancel D-Bus/non D-Bus requests */
+ auth = adapter_find_auth_request(adapter, &dev->bdaddr);
+ if (auth && auth->agent)
+ agent_cancel(auth->agent);
+
+ adapter_remove_auth_request(adapter, &dev->bdaddr);
+
+ /* Check if there is a pending CreateBonding request */
+ if (adapter->bonding && (bacmp(&adapter->bonding->bdaddr, &dev->bdaddr) == 0)) {
+ if (adapter->bonding->cancel) {
+ /* reply authentication canceled */
+ reply = new_authentication_return(adapter->bonding->msg,
+ HCI_OE_USER_ENDED_CONNECTION);
+ g_dbus_send_message(connection, reply);
+ } else {
+ reply = new_authentication_return(adapter->bonding->msg,
+ HCI_AUTHENTICATION_FAILURE);
+ dbus_connection_send(connection, reply, NULL);
+ dbus_message_unref(reply);
+ }
+
+ g_dbus_remove_watch(adapter->bonding->conn,
+ adapter->bonding->listener_id);
+
+ if (adapter->bonding->io_id)
+ g_source_remove(adapter->bonding->io_id);
+ g_io_channel_close(adapter->bonding->io);
+ bonding_request_free(adapter->bonding);
+ adapter->bonding = NULL;
+ }
+
+ adapter->active_conn = g_slist_remove(adapter->active_conn, dev);
+ g_free(dev);
+
+ device = adapter_find_device(adapter, paddr);
+ if (device) {
+ destination = device_get_address(device);
+ dev_path = device_get_path(device);
+
+ dbus_connection_emit_property_changed(connection,
+ dev_path, DEVICE_INTERFACE,
+ "Connected", DBUS_TYPE_BOOLEAN,
+ &connected);
+ if (device_is_temporary(device)) {
+ debug("Removing temporary device %s", destination);
+ adapter_remove_device(connection, adapter, device);
+ }
+ }
+}
+
+int set_limited_discoverable(int dd, const uint8_t *cls, gboolean limited)
+{
+ uint32_t dev_class;
+ int err;
+ int num = (limited ? 2 : 1);
+ uint8_t lap[] = { 0x33, 0x8b, 0x9e, 0x00, 0x8b, 0x9e };
+ /*
+ * 1: giac
+ * 2: giac + liac
+ */
+ if (hci_write_current_iac_lap(dd, num, lap, 1000) < 0) {
+ err = errno;
+ error("Can't write current IAC LAP: %s(%d)",
+ strerror(err), err);
+ return -err;
+ }
+
+ if (limited) {
+ if (cls[1] & 0x20)
+ return 0; /* Already limited */
+
+ dev_class = (cls[2] << 16) | ((cls[1] | 0x20) << 8) | cls[0];
+ } else {
+ if (!(cls[1] & 0x20))
+ return 0; /* Already clear */
+
+ dev_class = (cls[2] << 16) | ((cls[1] & 0xdf) << 8) | cls[0];
+ }
+
+ if (hci_write_class_of_dev(dd, dev_class, 1000) < 0) {
+ err = errno;
+ error("Can't write class of device: %s (%d)",
+ strerror(err), err);
+ return -err;
+ }
+
+ return 0;
+}
+
+int set_service_classes(int dd, const uint8_t *cls, uint8_t value)
+{
+ uint32_t dev_class;
+ int err;
+
+ if (cls[2] == value)
+ return 0; /* Already set */
+
+ dev_class = (value << 16) | (cls[1] << 8) | cls[0];
+
+ if (hci_write_class_of_dev(dd, dev_class, 1000) < 0) {
+ err = errno;
+ error("Can't write class of device: %s (%d)",
+ strerror(err), err);
+ return -err;
+ }
+
+ return 0;
+}
+
+/* Section reserved to device HCI callbacks */
+
+void hcid_dbus_setname_complete(bdaddr_t *local)
+{
+ int id, dd = -1;
+ read_local_name_rp rp;
+ struct hci_request rq;
+ const char *pname = (char *) rp.name;
+ char local_addr[18], name[249];
+
+ ba2str(local, local_addr);
+
+ id = hci_devid(local_addr);
+ if (id < 0) {
+ error("No matching device id for %s", local_addr);
+ return;
+ }
+
+ dd = hci_open_dev(id);
+ if (dd < 0) {
+ error("HCI device open failed: hci%d", id);
+ memset(&rp, 0, sizeof(rp));
+ } else {
+ memset(&rq, 0, sizeof(rq));
+ rq.ogf = OGF_HOST_CTL;
+ rq.ocf = OCF_READ_LOCAL_NAME;
+ rq.rparam = &rp;
+ rq.rlen = READ_LOCAL_NAME_RP_SIZE;
+ rq.event = EVT_CMD_COMPLETE;
+
+ if (hci_send_req(dd, &rq, 1000) < 0) {
+ error("Sending getting name command failed: %s (%d)",
+ strerror(errno), errno);
+ rp.name[0] = '\0';
+ } else if (rp.status) {
+ error("Getting name failed with status 0x%02x",
+ rp.status);
+ rp.name[0] = '\0';
+ }
+ hci_close_dev(dd);
+ }
+
+ strncpy(name, pname, sizeof(name) - 1);
+ name[248] = '\0';
+ pname = name;
+}
+
+void hcid_dbus_setscan_enable_complete(bdaddr_t *local)
+{
+ struct adapter *adapter;
+ read_scan_enable_rp rp;
+ struct hci_request rq;
+ int dd = -1;
+ uint16_t dev_id;
+
+ adapter = manager_find_adapter(local);
+ if (!adapter) {
+ error("No matching adapter found");
+ return;
+ }
+
+ dev_id = adapter_get_dev_id(adapter);
+
+ dd = hci_open_dev(dev_id);
+ if (dd < 0) {
+ error("HCI device open failed: hci%d", dev_id);
+ return;
+ }
+
+ memset(&rq, 0, sizeof(rq));
+ rq.ogf = OGF_HOST_CTL;
+ rq.ocf = OCF_READ_SCAN_ENABLE;
+ rq.rparam = &rp;
+ rq.rlen = READ_SCAN_ENABLE_RP_SIZE;
+ rq.event = EVT_CMD_COMPLETE;
+
+ if (hci_send_req(dd, &rq, 1000) < 0) {
+ error("Sending read scan enable command failed: %s (%d)",
+ strerror(errno), errno);
+ goto failed;
+ }
+
+ if (rp.status) {
+ error("Getting scan enable failed with status 0x%02x",
+ rp.status);
+ goto failed;
+ }
+
+ adapter_remove_discov_timeout(adapter);
+
+ if (adapter_get_scan_mode(adapter) != rp.enable)
+ adapter_mode_changed(adapter, rp.enable);
+
+failed:
+ if (dd >= 0)
+ hci_close_dev(dd);
+}
+
+void hcid_dbus_write_class_complete(bdaddr_t *local)
+{
+ struct adapter *adapter;
+ int dd;
+ uint8_t cls[3];
+ uint16_t dev_id;
+
+ adapter = manager_find_adapter(local);
+ if (!adapter) {
+ error("No matching adapter found");
+ return;
+ }
+
+ dev_id = adapter_get_dev_id(adapter);
+
+ dd = hci_open_dev(dev_id);
+ if (dd < 0) {
+ error("HCI device open failed: hci%d", dev_id);
+ return;
+ }
+
+ if (hci_read_class_of_dev(dd, cls, 1000) < 0) {
+ error("Can't read class of device on hci%d: %s (%d)",
+ dev_id, strerror(errno), errno);
+ hci_close_dev(dd);
+ return;
+ }
+
+ write_local_class(local, cls);
+ adapter_set_class(adapter, cls);
+
+ hci_close_dev(dd);
+}
+
+void hcid_dbus_write_simple_pairing_mode_complete(bdaddr_t *local)
+{
+ struct adapter *adapter;
+ int dd;
+ uint8_t mode;
+ uint16_t dev_id;
+ const gchar *path;
+
+ adapter = manager_find_adapter(local);
+ if (!adapter) {
+ error("No matching adapter found");
+ return;
+ }
+
+ dev_id = adapter_get_dev_id(adapter);
+ path = adapter_get_path(adapter);
+
+ dd = hci_open_dev(dev_id);
+ if (dd < 0) {
+ error("HCI adapter open failed: %s", path);
+ return;
+ }
+
+ if (hci_read_simple_pairing_mode(dd, &mode, 1000) < 0) {
+ error("Can't read class of adapter on %s: %s(%d)",
+ path, strerror(errno), errno);
+ hci_close_dev(dd);
+ return;
+ }
+
+ adapter_update_ssp_mode(adapter, dd, mode);
+
+ hci_close_dev(dd);
+}
+
+int hcid_dbus_get_io_cap(bdaddr_t *local, bdaddr_t *remote,
+ uint8_t *cap, uint8_t *auth)
+{
+ struct adapter *adapter;
+ struct btd_device *device;
+ struct agent *agent;
+ char addr[18];
+
+ adapter = manager_find_adapter(local);
+ if (!adapter) {
+ error("No matching adapter found");
+ return -1;
+ }
+
+ if (get_auth_requirements(local, remote, auth) < 0)
+ return -1;
+
+ ba2str(remote, addr);
+
+ device = adapter_find_device(adapter, addr);
+ if (device) {
+ agent = device_get_agent(device);
+ if (agent)
+ *auth = 0x03;
+ }
+ if (!agent)
+ agent = adapter->agent;
+
+ if (!agent) {
+ if (!(*auth & 0x01)) {
+ /* No input, no output */
+ *cap = 0x03;
+ return 0;
+ }
+ error("No agent available for IO capability");
+ return -1;
+ }
+
+ *cap = agent_get_io_capability(agent);
+
+ return 0;
+}
+
+int hcid_dbus_set_io_cap(bdaddr_t *local, bdaddr_t *remote,
+ uint8_t cap, uint8_t auth)
+{
+ struct adapter *adapter;
+ struct btd_device *device;
+ char addr[18];
+
+ adapter = manager_find_adapter(local);
+ if (!adapter) {
+ error("No matching adapter found");
+ return -1;
+ }
+
+ ba2str(remote, addr);
+
+ device = adapter_get_device(connection, adapter, addr);
+ if (device) {
+ device_set_cap(device, cap);
+ device_set_auth(device, auth);
+ }
+
+ return 0;
+}
+
+static int inquiry_cancel(int dd, int to)
+{
+ struct hci_request rq;
+ uint8_t status;
+
+ memset(&rq, 0, sizeof(rq));
+ rq.ogf = OGF_LINK_CTL;
+ rq.ocf = OCF_INQUIRY_CANCEL;
+ rq.rparam = &status;
+ rq.rlen = sizeof(status);
+ rq.event = EVT_CMD_COMPLETE;
+
+ if (hci_send_req(dd, &rq, to) < 0)
+ return -1;
+
+ if (status) {
+ errno = bt_error(status);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int remote_name_cancel(int dd, bdaddr_t *dba, int to)
+{
+ remote_name_req_cancel_cp cp;
+ struct hci_request rq;
+ uint8_t status;
+
+ memset(&rq, 0, sizeof(rq));
+ memset(&cp, 0, sizeof(cp));
+
+ bacpy(&cp.bdaddr, dba);
+
+ rq.ogf = OGF_LINK_CTL;
+ rq.ocf = OCF_REMOTE_NAME_REQ_CANCEL;
+ rq.cparam = &cp;
+ rq.clen = REMOTE_NAME_REQ_CANCEL_CP_SIZE;
+ rq.rparam = &status;
+ rq.rlen = sizeof(status);
+ rq.event = EVT_CMD_COMPLETE;
+
+ if (hci_send_req(dd, &rq, to) < 0)
+ return -1;
+
+ if (status) {
+ errno = bt_error(status);
+ return -1;
+ }
+
+ return 0;
+}
+
+int cancel_discovery(struct adapter *adapter)
+{
+ struct remote_dev_info *dev, match;
+ GSList *l;
+ int dd, err = 0;
+ uint16_t dev_id = adapter_get_dev_id(adapter);
+
+ if (!adapter->discov_active)
+ goto cleanup;
+
+ dd = hci_open_dev(dev_id);
+ if (dd < 0) {
+ err = -ENODEV;
+ goto cleanup;
+ }
+
+ /*
+ * If there is a pending read remote name request means
+ * that the inquiry complete event was already received
+ */
+ memset(&match, 0, sizeof(struct remote_dev_info));
+ bacpy(&match.bdaddr, BDADDR_ANY);
+ match.name_status = NAME_REQUESTED;
+
+ l = g_slist_find_custom(adapter->found_devices, &match,
+ (GCompareFunc) found_device_cmp);
+ if (l) {
+ dev = l->data;
+ if (remote_name_cancel(dd, &dev->bdaddr, 1000) < 0) {
+ error("Read remote name cancel failed: %s, (%d)",
+ strerror(errno), errno);
+ err = -errno;
+ }
+ } else {
+ if (inquiry_cancel(dd, 1000) < 0) {
+ error("Inquiry cancel failed:%s (%d)",
+ strerror(errno), errno);
+ err = -errno;
+ }
+ }
+
+ hci_close_dev(dd);
+
+cleanup:
+ /*
+ * Reset discov_requestor and discover_state in the remote name
+ * request event handler or in the inquiry complete handler.
+ */
+ g_slist_foreach(adapter->found_devices, (GFunc) g_free, NULL);
+ g_slist_free(adapter->found_devices);
+ adapter->found_devices = NULL;
+
+ /* Disable name resolution for non D-Bus clients */
+ if (!adapter->pdiscov_requestor)
+ adapter->discov_type &= ~RESOLVE_NAME;
+
+ return err;
+}
+
+static int periodic_inquiry_exit(int dd, int to)
+{
+ struct hci_request rq;
+ uint8_t status;
+
+ memset(&rq, 0, sizeof(rq));
+ rq.ogf = OGF_LINK_CTL;
+ rq.ocf = OCF_EXIT_PERIODIC_INQUIRY;
+ rq.rparam = &status;
+ rq.rlen = sizeof(status);
+ rq.event = EVT_CMD_COMPLETE;
+
+ if (hci_send_req(dd, &rq, to) < 0)
+ return -1;
+
+ if (status) {
+ errno = status;
+ return -1;
+ }
+
+ return 0;
+}
+
+int cancel_periodic_discovery(struct adapter *adapter)
+{
+ struct remote_dev_info *dev, match;
+ GSList *l;
+ int dd, err = 0;
+ uint16_t dev_id = adapter_get_dev_id(adapter);
+
+ if (!adapter->pdiscov_active)
+ goto cleanup;
+
+ dd = hci_open_dev(dev_id);
+ if (dd < 0) {
+ err = -ENODEV;
+ goto cleanup;
+ }
+ /* find the pending remote name request */
+ memset(&match, 0, sizeof(struct remote_dev_info));
+ bacpy(&match.bdaddr, BDADDR_ANY);
+ match.name_status = NAME_REQUESTED;
+
+ l = g_slist_find_custom(adapter->found_devices, &match,
+ (GCompareFunc) found_device_cmp);
+ if (l) {
+ dev = l->data;
+ if (remote_name_cancel(dd, &dev->bdaddr, 1000) < 0) {
+ error("Read remote name cancel failed: %s, (%d)",
+ strerror(errno), errno);
+ err = -errno;
+ }
+ }
+
+ /* ovewrite err if necessary: stop periodic inquiry has higher
+ * priority */
+ if (periodic_inquiry_exit(dd, 1000) < 0) {
+ error("Periodic Inquiry exit failed:%s (%d)",
+ strerror(errno), errno);
+ err = -errno;
+ }
+
+ hci_close_dev(dd);
+
+cleanup:
+ /*
+ * Reset pdiscov_requestor and pdiscov_active is done when the
+ * cmd complete event for exit periodic inquiry mode cmd arrives.
+ */
+ g_slist_foreach(adapter->found_devices, (GFunc) g_free, NULL);
+ g_slist_free(adapter->found_devices);
+ adapter->found_devices = NULL;
+
+ return err;
+}
+
+/* Most of the functions in this module require easy access to a connection so
+ * we keep it global here and provide these access functions the other (few)
+ * modules that require access to it */
+
+void set_dbus_connection(DBusConnection *conn)
+{
+ connection = conn;
+}
+
+DBusConnection *get_dbus_connection(void)
+{
+ return connection;
+}
diff --git a/src/dbus-hci.h b/src/dbus-hci.h
new file mode 100644
index 00000000..f141d664
--- /dev/null
+++ b/src/dbus-hci.h
@@ -0,0 +1,76 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 hcid_dbus_set_experimental();
+int hcid_dbus_use_experimental();
+int hcid_dbus_request_pin(int dev, bdaddr_t *sba, struct hci_conn_info *ci);
+
+void hcid_dbus_inquiry_start(bdaddr_t *local);
+void hcid_dbus_inquiry_complete(bdaddr_t *local);
+void hcid_dbus_periodic_inquiry_start(bdaddr_t *local, uint8_t status);
+void hcid_dbus_periodic_inquiry_exit(bdaddr_t *local, uint8_t status);
+void hcid_dbus_inquiry_result(bdaddr_t *local, bdaddr_t *peer, uint32_t class, int8_t rssi, uint8_t *data);
+void hcid_dbus_remote_class(bdaddr_t *local, bdaddr_t *peer, uint32_t class);
+void hcid_dbus_remote_name(bdaddr_t *local, bdaddr_t *peer, uint8_t status, char *name);
+void hcid_dbus_conn_complete(bdaddr_t *local, uint8_t status, uint16_t handle, bdaddr_t *peer);
+void hcid_dbus_disconn_complete(bdaddr_t *local, uint8_t status, uint16_t handle, uint8_t reason);
+void hcid_dbus_bonding_process_complete(bdaddr_t *local, bdaddr_t *peer, uint8_t status);
+void hcid_dbus_setname_complete(bdaddr_t *local);
+void hcid_dbus_setscan_enable_complete(bdaddr_t *local);
+void hcid_dbus_write_class_complete(bdaddr_t *local);
+void hcid_dbus_write_simple_pairing_mode_complete(bdaddr_t *local);
+int hcid_dbus_get_io_cap(bdaddr_t *local, bdaddr_t *remote,
+ uint8_t *cap, uint8_t *auth);
+int hcid_dbus_set_io_cap(bdaddr_t *local, bdaddr_t *remote,
+ uint8_t cap, uint8_t auth);
+int hcid_dbus_user_confirm(bdaddr_t *sba, bdaddr_t *dba, uint32_t passkey);
+int hcid_dbus_user_passkey(bdaddr_t *sba, bdaddr_t *dba);
+int hcid_dbus_user_notify(bdaddr_t *sba, bdaddr_t *dba, uint32_t passkey);
+
+int unregister_adapter_path(const char *path);
+
+DBusMessage *new_authentication_return(DBusMessage *msg, uint8_t status);
+
+int get_default_dev_id(void);
+
+int cancel_discovery(struct adapter *adapter);
+int cancel_periodic_discovery(struct adapter *adapter);
+
+int active_conn_find_by_bdaddr(const void *data, const void *user_data);
+void bonding_request_free(struct bonding_request_info *dev);
+int found_device_cmp(const struct remote_dev_info *d1,
+ const struct remote_dev_info *d2);
+int found_device_add(GSList **list, bdaddr_t *bdaddr, int8_t rssi,
+ name_status_t name_status);
+int found_device_req_name(struct adapter *dbus_data);
+
+int set_limited_discoverable(int dd, const uint8_t *cls, gboolean limited);
+int set_service_classes(int dd, const uint8_t *cls, uint8_t value);
+
+int discov_timeout_handler(void *data);
+
+void set_dbus_connection(DBusConnection *conn);
+
+DBusConnection *get_dbus_connection(void);
+struct adapter *adapter_find(const bdaddr_t *sba);
diff --git a/src/dbus-service.c b/src/dbus-service.c
new file mode 100644
index 00000000..a85c3d8a
--- /dev/null
+++ b/src/dbus-service.c
@@ -0,0 +1,157 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 <unistd.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <signal.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "hcid.h"
+#include "server.h"
+#include "dbus-common.h"
+#include "error.h"
+#include "manager.h"
+#include "adapter.h"
+#include "agent.h"
+#include "device.h"
+#include "dbus-service.h"
+#include "dbus-hci.h"
+
+struct service_auth {
+ service_auth_cb cb;
+ void *user_data;
+};
+
+static void agent_auth_cb(struct agent *agent, DBusError *derr, void *user_data)
+{
+ struct service_auth *auth = user_data;
+
+ auth->cb(derr, auth->user_data);
+
+ g_free(auth);
+}
+
+int service_req_auth(const bdaddr_t *src, const bdaddr_t *dst,
+ const char *uuid, service_auth_cb cb, void *user_data)
+{
+ struct service_auth *auth;
+ struct adapter *adapter;
+ struct btd_device *device;
+ struct agent *agent;
+ char address[18];
+ gboolean trusted;
+ const gchar *dev_path;
+
+ if (src == NULL || dst == NULL)
+ return -EINVAL;
+
+ adapter = manager_find_adapter(src);
+ if (!adapter)
+ return -EPERM;
+
+ /* Device connected? */
+ if (!g_slist_find_custom(adapter->active_conn,
+ dst, active_conn_find_by_bdaddr))
+ return -ENOTCONN;
+
+ ba2str(dst, address);
+ trusted = read_trust(src, address, GLOBAL_TRUST);
+
+ if (trusted) {
+ cb(NULL, user_data);
+ return 0;
+ }
+
+ device = adapter_find_device(adapter, address);
+ if (!device)
+ return -EPERM;
+
+ agent = device_get_agent(device);
+
+ if (!agent)
+ agent = adapter->agent;
+
+ if (!agent)
+ return -EPERM;
+
+ auth = g_try_new0(struct service_auth, 1);
+ if (!auth)
+ return -ENOMEM;
+
+ auth->cb = cb;
+ auth->user_data = user_data;
+
+ dev_path = device_get_path(device);
+
+ return agent_authorize(agent, dev_path, uuid, agent_auth_cb, auth);
+}
+
+int service_cancel_auth(const bdaddr_t *src, const bdaddr_t *dst)
+{
+ struct adapter *adapter = manager_find_adapter(src);
+ struct btd_device *device;
+ struct agent *agent;
+ char address[18];
+
+ if (!adapter)
+ return -EPERM;
+
+ ba2str(dst, address);
+ device = adapter_find_device(adapter, address);
+ if (!device)
+ return -EPERM;
+
+ /*
+ * 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)
+ agent = adapter->agent;
+
+ if (!agent)
+ return -EPERM;
+
+ return agent_cancel(agent);
+}
diff --git a/src/dbus-service.h b/src/dbus-service.h
new file mode 100644
index 00000000..374f9008
--- /dev/null
+++ b/src/dbus-service.h
@@ -0,0 +1,28 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 (*service_auth_cb) (DBusError *derr, void *user_data);
+int service_req_auth(const bdaddr_t *src, const bdaddr_t *dst,
+ const char *uuid, service_auth_cb cb, void *user_data);
+int service_cancel_auth(const bdaddr_t *src, const bdaddr_t *dst);
diff --git a/src/device.c b/src/device.c
new file mode 100644
index 00000000..9d9e03b8
--- /dev/null
+++ b/src/device.c
@@ -0,0 +1,1100 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 <stdarg.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "hcid.h"
+#include "sdpd.h"
+
+#include "logging.h"
+#include "textfile.h"
+
+#include "adapter.h"
+#include "device.h"
+#include "dbus-common.h"
+#include "dbus-hci.h"
+#include "dbus-service.h"
+#include "error.h"
+#include "glib-helper.h"
+#include "agent.h"
+#include "sdp-xml.h"
+
+#define DEFAULT_XML_BUF_SIZE 1024
+#define DISCONNECT_TIMER 2
+
+#define DEVICE_INTERFACE "org.bluez.Device"
+
+struct btd_driver_data {
+ struct btd_device_driver *driver;
+ void *priv;
+};
+
+struct btd_device {
+ gchar *address;
+ gchar *path;
+ struct adapter *adapter;
+ GSList *uuids;
+ GSList *drivers; /* List of driver_data */
+ gboolean temporary;
+ struct agent *agent;
+ guint disconn_timer;
+ int discov_active; /* Service discovery active */
+ char *discov_requestor; /* discovery requestor unique name */
+ guint discov_listener;
+
+ /* For Secure Simple Pairing */
+ uint8_t cap;
+ uint8_t auth;
+};
+
+struct browse_req {
+ DBusConnection *conn;
+ DBusMessage *msg;
+ struct btd_device *device;
+ GSList *uuids_added;
+ GSList *uuids_removed;
+ int search_uuid;
+ gboolean browse;
+};
+
+static GSList *drivers = NULL;
+
+static uint16_t uuid_list[] = {
+ PUBLIC_BROWSE_GROUP,
+ HID_SVCLASS_ID,
+ GENERIC_AUDIO_SVCLASS_ID,
+ ADVANCED_AUDIO_SVCLASS_ID,
+ AV_REMOTE_SVCLASS_ID,
+ 0
+};
+
+static void device_free(gpointer user_data)
+{
+ struct btd_device *device = user_data;
+
+ if (device->agent)
+ agent_destroy(device->agent, FALSE);
+
+ g_slist_foreach(device->uuids, (GFunc) g_free, NULL);
+ g_slist_free(device->uuids);
+
+ if (device->disconn_timer)
+ g_source_remove(device->disconn_timer);
+
+ g_free(device->address);
+ g_free(device->path);
+ g_free(device);
+}
+
+static gboolean device_is_paired(struct btd_device *device)
+{
+ struct adapter *adapter = device->adapter;
+ char filename[PATH_MAX + 1], *str;
+ gboolean ret;
+ const gchar *source = adapter_get_address(adapter);
+
+ create_name(filename, PATH_MAX, STORAGEDIR,
+ source, "linkkeys");
+ str = textfile_caseget(filename, device->address);
+ ret = str ? TRUE : FALSE;
+ g_free(str);
+
+ return ret;
+}
+
+static char *device_get_name(struct btd_device *device)
+{
+ struct adapter *adapter = device->adapter;
+ char filename[PATH_MAX + 1];
+ const gchar *source = adapter_get_address(adapter);
+
+ create_name(filename, PATH_MAX, STORAGEDIR, source, "names");
+ return textfile_caseget(filename, device->address);
+}
+
+static DBusMessage *get_properties(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ struct btd_device *device = user_data;
+ struct adapter *adapter = device->adapter;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ bdaddr_t src, dst;
+ char path[MAX_PATH_LENGTH];
+ char buf[64];
+ const char *ptr;
+ char *name, *ppath, **uuids;
+ dbus_bool_t boolean;
+ uint32_t class;
+ int i;
+ GSList *l;
+ uint16_t dev_id = adapter_get_dev_id(adapter);
+ const gchar *source = adapter_get_address(adapter);
+
+ 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 */
+ dbus_message_iter_append_dict_entry(&dict, "Address", DBUS_TYPE_STRING,
+ &device->address);
+
+ /* Name */
+ name = device_get_name(device);
+ if (name) {
+ dbus_message_iter_append_dict_entry(&dict, "Name",
+ DBUS_TYPE_STRING, &name);
+ }
+
+ str2ba(source, &src);
+ str2ba(device->address, &dst);
+
+ /* Class */
+ if (read_remote_class(&src, &dst, &class) == 0) {
+ dbus_message_iter_append_dict_entry(&dict, "Class",
+ DBUS_TYPE_UINT32, &class);
+ }
+
+ /* Alias */
+ if (get_device_alias(dev_id, &dst, buf, sizeof(buf)) > 0) {
+ ptr = buf;
+ dbus_message_iter_append_dict_entry(&dict, "Alias",
+ DBUS_TYPE_STRING, &ptr);
+ } else if (name) {
+ dbus_message_iter_append_dict_entry(&dict, "Alias",
+ DBUS_TYPE_STRING, &name);
+ free(name);
+ }
+
+ /* Paired */
+ boolean = device_is_paired(device);
+ dbus_message_iter_append_dict_entry(&dict, "Paired",
+ DBUS_TYPE_BOOLEAN, &boolean);
+
+ /* Trusted */
+ boolean = read_trust(&src, device->address, GLOBAL_TRUST);
+ dbus_message_iter_append_dict_entry(&dict, "Trusted",
+ DBUS_TYPE_BOOLEAN, &boolean);
+
+ /* Connected */
+ if (g_slist_find_custom(adapter->active_conn, &dst,
+ active_conn_find_by_bdaddr))
+ boolean = TRUE;
+ else
+ boolean = FALSE;
+
+ dbus_message_iter_append_dict_entry(&dict, "Connected",
+ DBUS_TYPE_BOOLEAN, &boolean);
+
+ /* UUIDs */
+ 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;
+ dbus_message_iter_append_dict_entry(&dict, "UUIDs",
+ DBUS_TYPE_ARRAY, &uuids);
+ g_free(uuids);
+
+ /* Adapter */
+ snprintf(path, sizeof(path), "/hci%d", dev_id);
+ ppath = path;
+ dbus_message_iter_append_dict_entry(&dict, "Adapter",
+ DBUS_TYPE_OBJECT_PATH, &ppath);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static int remove_device_alias(const char *source, const char *destination)
+{
+ char filename[PATH_MAX + 1];
+
+ create_name(filename, PATH_MAX, STORAGEDIR, source, "aliases");
+ create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+ return textfile_del(filename, destination);
+}
+
+static DBusMessage *set_alias(DBusConnection *conn, DBusMessage *msg,
+ const char *alias, void *data)
+{
+ struct btd_device *device = data;
+ struct adapter *adapter = device->adapter;
+ bdaddr_t bdaddr;
+ int ecode;
+ char *str, filename[PATH_MAX + 1];
+ uint16_t dev_id = adapter_get_dev_id(adapter);
+ const gchar *source = adapter_get_address(adapter);
+
+ str2ba(device->address, &bdaddr);
+
+ /* Remove alias if empty string */
+ if (g_str_equal(alias, "")) {
+ create_name(filename, PATH_MAX, STORAGEDIR, source,
+ "names");
+ str = textfile_caseget(filename, device->address);
+ ecode = remove_device_alias(source, device->address);
+ } else {
+ str = g_strdup(alias);
+ ecode = set_device_alias(dev_id, &bdaddr, alias);
+ }
+
+ if (ecode < 0)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".Failed",
+ strerror(-ecode));
+
+ dbus_connection_emit_property_changed(conn, dbus_message_get_path(msg),
+ DEVICE_INTERFACE, "Alias",
+ DBUS_TYPE_STRING, &str);
+
+ g_free(str);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *set_trust(DBusConnection *conn, DBusMessage *msg,
+ dbus_bool_t value, void *data)
+{
+ struct btd_device *device = data;
+ struct adapter *adapter = device->adapter;
+ bdaddr_t local;
+ const gchar *source = adapter_get_address(adapter);
+
+ str2ba(source, &local);
+
+ write_trust(&local, device->address, GLOBAL_TRUST, value);
+
+ dbus_connection_emit_property_changed(conn, dbus_message_get_path(msg),
+ DEVICE_INTERFACE, "Trusted",
+ DBUS_TYPE_BOOLEAN, &value);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static inline DBusMessage *invalid_args(DBusMessage *msg)
+{
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".InvalidArguments",
+ "Invalid arguments in method call");
+}
+
+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 invalid_args(msg);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+ return 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 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 invalid_args(msg);
+ dbus_message_iter_get_basic(&sub, &value);
+
+ return set_trust(conn, msg, value, data);
+ } else if (g_str_equal("Alias", property)) {
+ char *alias;
+
+ if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING)
+ return invalid_args(msg);
+ dbus_message_iter_get_basic(&sub, &alias);
+
+ return set_alias(conn, msg, alias, data);
+ }
+
+ return invalid_args(msg);
+}
+
+static void discover_services_req_exit(void *user_data)
+{
+ struct btd_device *device = user_data;
+ struct adapter *adapter = device->adapter;
+ bdaddr_t src, dst;
+ const gchar *source = adapter_get_address(adapter);
+
+ debug("DiscoverDevices requestor exited");
+
+ str2ba(source, &src);
+ str2ba(device->address, &dst);
+
+ bt_cancel_discovery(&src, &dst);
+}
+
+static DBusMessage *discover_services(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ struct btd_device *device = user_data;
+ const char *pattern;
+ int err;
+
+ if (device->discov_active)
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress",
+ "Discover in progress");
+
+ if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern,
+ DBUS_TYPE_INVALID) == FALSE)
+ goto fail;
+
+ if (strlen(pattern) == 0) {
+ err = device_browse(device, conn, msg, NULL);
+ if (err < 0)
+ goto fail;
+ } else {
+ uuid_t uuid;
+
+ if (bt_string2uuid(&uuid, pattern) < 0)
+ return invalid_args(msg);
+
+ err = device_browse(device, conn, msg, &uuid);
+ if (err < 0)
+ goto fail;
+ }
+
+ return NULL;
+
+fail:
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ "Discovery Failed");
+}
+
+static DBusMessage *cancel_discover(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ struct btd_device *device = user_data;
+ struct adapter *adapter = device->adapter;
+ bdaddr_t src, dst;
+ const gchar *source = adapter_get_address(adapter);
+
+ if (!device->discov_active)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".Failed",
+ "No pending discovery");
+
+ /* only the discover requestor can cancel the inquiry process */
+ if (!device->discov_requestor ||
+ strcmp(device->discov_requestor, dbus_message_get_sender(msg)))
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".NotAuthorized",
+ "Not Authorized");
+
+ str2ba(source, &src);
+ str2ba(device->address, &dst);
+
+ if (bt_cancel_discovery(&src, &dst) < 0)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".Failed",
+ "No pending discover");
+
+ return dbus_message_new_method_return(msg);
+}
+
+static gboolean disconnect_timeout(gpointer user_data)
+{
+ struct btd_device *device = user_data;
+ struct active_conn_info *ci;
+ GSList *l;
+ disconnect_cp cp;
+ bdaddr_t bda;
+ int dd;
+ uint16_t dev_id = adapter_get_dev_id(device->adapter);
+
+ device->disconn_timer = 0;
+
+ str2ba(device->address, &bda);
+ l = g_slist_find_custom(device->adapter->active_conn,
+ &bda, active_conn_find_by_bdaddr);
+ if (!l)
+ return FALSE;
+
+ ci = l->data;
+ dd = hci_open_dev(dev_id);
+ if (dd < 0)
+ goto fail;
+
+ memset(&cp, 0, sizeof(cp));
+ cp.handle = htobs(ci->handle);
+ cp.reason = HCI_OE_USER_ENDED_CONNECTION;
+
+ hci_send_cmd(dd, OGF_LINK_CTL, OCF_DISCONNECT,
+ DISCONNECT_CP_SIZE, &cp);
+
+ close(dd);
+
+fail:
+ return FALSE;
+}
+
+static DBusMessage *disconnect(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ struct btd_device *device = user_data;
+ GSList *l;
+ bdaddr_t bda;
+
+ str2ba(device->address, &bda);
+ l = g_slist_find_custom(device->adapter->active_conn,
+ &bda, active_conn_find_by_bdaddr);
+ if (!l)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".NotConnected",
+ "Device is not connected");
+
+ g_dbus_emit_signal(conn, device->path,
+ DEVICE_INTERFACE, "DisconnectRequested",
+ DBUS_TYPE_INVALID);
+
+ device->disconn_timer = g_timeout_add_seconds(DISCONNECT_TIMER,
+ disconnect_timeout, device);
+
+ return dbus_message_new_method_return(msg);
+}
+
+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 },
+ { }
+};
+
+static GDBusSignalTable device_signals[] = {
+ { "PropertyChanged", "sv" },
+ { "DisconnectRequested", "" },
+ { }
+};
+
+struct btd_device *device_create(DBusConnection *conn, struct adapter *adapter,
+ const gchar *address)
+{
+ gchar *address_up;
+ struct btd_device *device;
+ uint16_t dev_id = adapter_get_dev_id(adapter);
+
+ 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("/hci%d/dev_%s",
+ dev_id, address_up);
+ g_strdelimit(device->path, ":", '_');
+ g_free(address_up);
+
+ debug("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;
+ }
+
+ device->address = g_strdup(address);
+ device->adapter = adapter;
+
+ return device;
+}
+
+void device_remove(DBusConnection *conn, struct btd_device *device)
+{
+ GSList *list;
+ struct btd_device_driver *driver;
+ gchar *path = g_strdup(device->path);
+
+ debug("Removing device %s", path);
+
+ for (list = device->drivers; list; list = list->next) {
+ struct btd_driver_data *driver_data = list->data;
+ driver = driver_data->driver;
+
+ driver->remove(driver, device);
+ g_free(driver_data);
+ }
+
+ g_dbus_unregister_interface(conn, path, DEVICE_INTERFACE);
+
+ g_free(path);
+}
+
+gint device_address_cmp(struct btd_device *device, const gchar *address)
+{
+ return strcasecmp(device->address, address);
+}
+
+sdp_record_t *get_record(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;
+}
+
+void device_probe_drivers(struct btd_device *device, GSList *uuids, sdp_list_t *recs)
+{
+ GSList *list;
+ const char **uuid;
+ int err;
+
+ debug("Probe drivers for %s", device->path);
+
+ for (list = drivers; list; list = list->next) {
+ struct btd_device_driver *driver = list->data;
+ GSList *records = NULL;
+
+ for (uuid = driver->uuids; *uuid; uuid++) {
+ GSList *match = g_slist_find_custom(uuids, *uuid,
+ (GCompareFunc) strcasecmp);
+ if (match) {
+ sdp_record_t *rec = get_record(recs, *uuid);
+
+ records = g_slist_append(records, rec);
+ }
+ }
+
+ if (records) {
+ struct btd_driver_data *driver_data = g_new0(struct btd_driver_data, 1);
+
+ err = driver->probe(driver, device, records);
+ if (err < 0) {
+ error("probe failed for driver %s",
+ driver->name);
+
+ g_free(driver_data);
+ continue;
+ }
+
+ driver_data->driver = driver;
+ device->drivers = g_slist_append(device->drivers,
+ driver_data);
+ }
+ }
+
+ for (list = uuids; list; list = list->next)
+ device->uuids = g_slist_insert_sorted(device->uuids,
+ list->data, (GCompareFunc) strcmp);
+}
+
+void device_remove_drivers(struct btd_device *device, GSList *uuids, sdp_list_t *recs)
+{
+ struct adapter *adapter = device_get_adapter(device);
+ const gchar *src = adapter_get_address(adapter);
+ const gchar *dst = device_get_address(device);
+ GSList *list;
+
+ debug("Remove drivers for %s", device->path);
+
+ for (list = device->drivers; list; list = list->next) {
+ struct btd_driver_data *driver_data = list->data;
+ struct btd_device_driver *driver = driver_data->driver;
+ const char **uuid;
+
+ for (uuid = driver->uuids; *uuid; uuid++) {
+ GSList *match = g_slist_find_custom(uuids, *uuid,
+ (GCompareFunc) strcasecmp);
+
+ if (!match)
+ continue;
+
+ driver->remove(driver, device);
+ device->drivers = g_slist_remove(device->drivers,
+ driver_data);
+
+ g_free(driver_data);
+
+ sdp_record_t *rec = get_record(recs, *uuid);
+ delete_record(src, dst, rec->handle);
+ }
+ }
+
+ for (list = uuids; list; list = list->next)
+ device->uuids = g_slist_remove(device->uuids, list->data);
+}
+
+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);
+}
+
+void append_and_grow_string(void *data, const char *str)
+{
+ sdp_buf_t *buff = data;
+ int len;
+
+ len = strlen(str);
+
+ if (!buff->data) {
+ buff->data = malloc(DEFAULT_XML_BUF_SIZE);
+ if (!buff->data)
+ return;
+ buff->buf_size = DEFAULT_XML_BUF_SIZE;
+ }
+
+ /* Grow string */
+ while (buff->buf_size < (buff->data_size + len + 1)) {
+ void *tmp;
+ uint32_t new_size;
+
+ /* Grow buffer by a factor of 2 */
+ new_size = (buff->buf_size << 1);
+
+ tmp = realloc(buff->data, new_size);
+ if (!tmp)
+ return;
+
+ buff->data = tmp;
+ buff->buf_size = new_size;
+ }
+
+ /* Include the NULL character */
+ memcpy(buff->data + buff->data_size, str, len + 1);
+ buff->data_size += len;
+}
+
+static void discover_device_reply(struct browse_req *req, sdp_list_t *recs)
+{
+ DBusMessage *reply;
+ DBusMessageIter iter, dict;
+ sdp_list_t *seq;
+
+ 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;
+ sdp_buf_t result;
+
+ if (!rec)
+ break;
+
+ memset(&result, 0, sizeof(sdp_buf_t));
+
+ convert_sdp_record_to_xml(rec, &result,
+ append_and_grow_string);
+
+ if (result.data) {
+ const char *val = (char *) result.data;
+ iter_append_record(&dict, rec->handle, val);
+ free(result.data);
+ }
+ }
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ dbus_connection_send(req->conn, reply, NULL);
+ dbus_message_unref(reply);
+}
+
+static void services_changed(struct browse_req *req)
+{
+ struct btd_device *device = req->device;
+ 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;
+
+ dbus_connection_emit_property_changed(req->conn, device->path,
+ DEVICE_INTERFACE, "UUIDs",
+ DBUS_TYPE_ARRAY, &uuids);
+
+ g_free(uuids);
+}
+
+static void update_services(struct browse_req *req, sdp_list_t *recs)
+{
+ struct btd_device *device = req->device;
+ struct adapter *adapter = device_get_adapter(device);
+ const gchar *src = adapter_get_address(adapter);
+ const gchar *dst = device_get_address(device);
+ 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;
+ gchar *uuid_str;
+ GSList *l;
+
+ if (!rec)
+ break;
+
+ if (sdp_get_service_classes(rec, &svcclass) < 0)
+ continue;
+ store_record(src, dst, rec);
+
+ /* Extract the first element and skip the remainning */
+ uuid_str = bt_uuid2string(svcclass->data);
+ if (!uuid_str)
+ continue;
+
+ l = g_slist_find_custom(device->uuids, uuid_str,
+ (GCompareFunc) strcmp);
+ if (!l)
+ req->uuids_added = g_slist_append(req->uuids_added,
+ uuid_str);
+ else {
+ req->uuids_removed = g_slist_remove(req->uuids_removed,
+ l->data);
+ g_free(uuid_str);
+ }
+
+ sdp_list_free(svcclass, free);
+ }
+}
+
+static void store(struct btd_device *device)
+{
+ struct adapter *adapter = device->adapter;
+ bdaddr_t src, dst;
+ char *str;
+ const gchar *source = adapter_get_address(adapter);
+
+ str2ba(source, &src);
+ str2ba(device->address, &dst);
+
+ if (!device->uuids) {
+ write_device_profiles(&src, &dst, "");
+ return;
+ }
+
+ str = bt_list2string(device->uuids);
+ write_device_profiles(&src, &dst, str);
+ g_free(str);
+}
+
+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 adapter *adapter = device->adapter;
+ bdaddr_t src, dst;
+ uuid_t uuid;
+ DBusMessage *reply;
+ const gchar *source = adapter_get_address(adapter);
+
+ if (err < 0)
+ goto proceed;
+
+ update_services(req, recs);
+
+ /* Public browsing successful or Single record requested */
+ if (req->browse == FALSE || (!req->search_uuid && recs))
+ goto probe;
+
+ if (uuid_list[++req->search_uuid]) {
+ sdp_uuid16_create(&uuid, uuid_list[req->search_uuid]);
+ str2ba(source, &src);
+ str2ba(device->address, &dst);
+ bt_search_service(&src, &dst, &uuid, browse_cb, user_data, NULL);
+ return;
+ }
+
+probe:
+
+ if (!req->uuids_added && !req->uuids_removed)
+ goto proceed;
+
+ /* Probe matching drivers for services added */
+ if (req->uuids_added)
+ device_probe_drivers(device, req->uuids_added, recs);
+
+ /* Remove drivers for services removed */
+ if (req->uuids_removed)
+ device_remove_drivers(device, req->uuids_removed, recs);
+
+ /* Store the device's profiles in the filesystem */
+ store(device);
+
+ /* Propagate services changes */
+ services_changed(req);
+
+proceed:
+ if (dbus_message_is_method_call(req->msg, DEVICE_INTERFACE,
+ "DiscoverServices")) {
+ discover_device_reply(req, recs);
+ goto cleanup;
+ }
+
+ g_dbus_emit_signal(req->conn, dbus_message_get_path(req->msg),
+ ADAPTER_INTERFACE, "DeviceCreated",
+ DBUS_TYPE_OBJECT_PATH, &device->path,
+ DBUS_TYPE_INVALID);
+
+ /* Reply create device request */
+ reply = dbus_message_new_method_return(req->msg);
+ if (!reply)
+ goto cleanup;
+
+ dbus_message_append_args(reply, DBUS_TYPE_OBJECT_PATH, &device->path,
+ DBUS_TYPE_INVALID);
+
+ dbus_connection_send(req->conn, reply, NULL);
+ dbus_message_unref(reply);
+
+cleanup:
+ device->discov_active = 0;
+
+ if (device->discov_requestor) {
+ g_dbus_remove_watch(req->conn, device->discov_listener);
+ device->discov_listener = 0;
+ g_free(device->discov_requestor);
+ device->discov_requestor = NULL;
+ }
+
+ if (recs != NULL)
+ sdp_list_free(recs, (sdp_free_func_t) sdp_record_free);
+
+ dbus_message_unref(req->msg);
+ dbus_connection_unref(req->conn);
+ g_slist_free(req->uuids_added);
+ g_slist_free(req->uuids_removed);
+ g_free(req);
+}
+
+int device_browse(struct btd_device *device, DBusConnection *conn,
+ DBusMessage *msg, uuid_t *search)
+{
+ struct adapter *adapter = device->adapter;
+ struct browse_req *req;
+ bdaddr_t src, dst;
+ uuid_t uuid;
+ GSList *l;
+ const gchar *source = adapter_get_address(adapter);
+
+ req = g_new0(struct browse_req, 1);
+ req->conn = dbus_connection_ref(conn);
+ req->msg = dbus_message_ref(msg);
+ req->device = device;
+
+ str2ba(source, &src);
+ str2ba(device->address, &dst);
+
+ if (search) {
+ memcpy(&uuid, search, sizeof(uuid_t));
+ req->browse = FALSE;
+ } else {
+ sdp_uuid16_create(&uuid, uuid_list[req->search_uuid]);
+ req->browse = TRUE;
+ for (l = device->uuids; l; l = l->next)
+ req->uuids_removed = g_slist_append(req->uuids_removed,
+ l->data);
+ }
+
+ device->discov_active = 1;
+ device->discov_requestor = g_strdup(dbus_message_get_sender(msg));
+ /* Track the request owner to cancel it
+ * automatically if the owner exits */
+ device->discov_listener = g_dbus_add_disconnect_watch(conn,
+ dbus_message_get_sender(msg),
+ discover_services_req_exit,
+ device, NULL);
+
+ return bt_search_service(&src, &dst, &uuid, browse_cb, req, NULL);
+}
+
+struct adapter *device_get_adapter(struct btd_device *device)
+{
+ if (!device)
+ return NULL;
+
+ return device->adapter;
+}
+
+const gchar *device_get_address(struct btd_device *device)
+{
+ if (!device)
+ return NULL;
+
+ return device->address;
+}
+
+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;
+
+ return device->agent;
+}
+
+void device_set_agent(struct btd_device *device, struct agent *agent)
+{
+ if (!device)
+ return;
+
+ device->agent = agent;
+}
+
+gboolean device_is_busy(struct btd_device *device)
+{
+ return device->discov_active ? 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;
+
+ device->temporary = temporary;
+}
+
+void device_set_cap(struct btd_device *device, uint8_t cap)
+{
+ if (!device)
+ return;
+
+ device->cap = cap;
+}
+
+void device_set_auth(struct btd_device *device, uint8_t auth)
+{
+ if (!device)
+ return;
+
+ device->auth = auth;
+}
+
+uint8_t device_get_auth(struct btd_device *device)
+{
+ return device->auth;
+}
+
+int btd_register_device_driver(struct btd_device_driver *driver)
+{
+ const char **uuid;
+
+ /* FIXME: hack to make hci to resolve service_req_auth symbol*/
+ service_req_auth(NULL, NULL, NULL, NULL, NULL);
+ drivers = g_slist_append(drivers, driver);
+
+ for (uuid = driver->uuids; *uuid; uuid++) {
+ debug("name %s uuid %s", driver->name, *uuid);
+ }
+
+ return 0;
+}
+
+void btd_unregister_device_driver(struct btd_device_driver *driver)
+{
+ drivers = g_slist_remove(drivers, driver);
+}
diff --git a/src/device.h b/src/device.h
new file mode 100644
index 00000000..31480a66
--- /dev/null
+++ b/src/device.h
@@ -0,0 +1,58 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 *device_create(DBusConnection *conn, struct adapter *adapter,
+ const gchar *address);
+void device_remove(DBusConnection *conn, struct btd_device *device);
+gint device_address_cmp(struct btd_device *device, const gchar *address);
+int device_browse(struct btd_device *device, DBusConnection *conn,
+ DBusMessage *msg, uuid_t *search);
+void device_probe_drivers(struct btd_device *device, GSList *uuids, sdp_list_t *recs);
+struct adapter *device_get_adapter(struct btd_device *device);
+const gchar *device_get_address(struct btd_device *device);
+const gchar *device_get_path(struct btd_device *device);
+struct agent *device_get_agent(struct btd_device *device);
+void device_set_agent(struct btd_device *device, struct agent *agent);
+gboolean device_is_busy(struct btd_device *device);
+gboolean device_is_temporary(struct btd_device *device);
+void device_set_temporary(struct btd_device *device, gboolean temporary);
+void device_set_cap(struct btd_device *device, uint8_t cap);
+void device_set_auth(struct btd_device *device, uint8_t auth);
+uint8_t device_get_auth(struct btd_device *device);
+
+#define BTD_UUIDS(args...) ((const char *[]) { args, NULL } )
+
+struct btd_device_driver {
+ const char *name;
+ const char **uuids;
+ int (*probe) (struct btd_device_driver *driver,
+ struct btd_device *device, GSList *records);
+ void (*remove) (struct btd_device_driver *driver,
+ struct btd_device *device);
+};
+
+int btd_register_device_driver(struct btd_device_driver *driver);
+void btd_unregister_device_driver(struct btd_device_driver *driver);
diff --git a/src/hcid.8 b/src/hcid.8
new file mode 100644
index 00000000..a7ab0410
--- /dev/null
+++ b/src/hcid.8
@@ -0,0 +1,101 @@
+.\"
+.TH "HCID" "8" "March 2004" "hcid - HCI daemon" "System management commands"
+.SH "NAME"
+hcid \- Bluetooth Host Controller Interface Daemon
+
+.SH "SYNOPSIS"
+.B hcid
+[
+.B \-n
+] [
+.B \-f
+.I config\-file
+]
+
+.SH "DESCRIPTION"
+This manual page documents briefly the
+.B hcid
+daemon, which manages all the Bluetooth devices.
+.B hcid
+itself does not accept many command\-line options, as most of its
+configuration is done in the
+.B hcid.conf
+file, which has its own man page.
+.B hcid
+can also provide a number of services via the D-BUS message bus
+system.
+.SH "OPTIONS"
+.TP
+.BI \-n
+Don't fork to run daemon in background.
+.TP
+.BI \-d
+Enable debug information output.
+.TP
+.BI \-s
+Enable internal SDP server.
+.TP
+.BI \-m\ mtu\-size
+Use specific MTU size for SDP server.
+.TP
+.BI \-f\ config\-file
+Use alternate configuration file instead of /etc/bluetooth/hcid.conf
+.SH "FILES"
+.TP
+.I /etc/bluetooth/hcid.conf
+Default location of the global configuration file.
+
+.TP
+.I /var/lib/bluetooth/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 /var/lib/bluetooth/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 /var/lib/bluetooth/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 /var/lib/bluetooth/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 "SEE ALSO"
+\fBhcid.conf\fR(5)
+.SH "AUTHOR"
+This manual page was written by Philipp Matthias Hahn and Fredrik Noring.
diff --git a/src/hcid.conf b/src/hcid.conf
new file mode 100644
index 00000000..b6ce3b48
--- /dev/null
+++ b/src/hcid.conf
@@ -0,0 +1,57 @@
+#
+# HCI daemon configuration file.
+#
+
+# HCId options
+options {
+ # Automatically initialize new devices
+ autoinit yes;
+
+ # Security Manager mode
+ # none - Security manager disabled
+ # auto - Use local PIN for incoming connections
+ # user - Always ask user for a PIN
+ #
+ security user;
+
+ # Pairing mode
+ # none - Pairing disabled
+ # multi - Allow pairing with already paired devices
+ # once - Pair once and deny successive attempts
+ pairing multi;
+
+ # Default PIN code for incoming connections
+ passkey "BlueZ";
+}
+
+# Default settings for HCI devices
+device {
+ # Local device name
+ # %d - device id
+ # %h - host name
+ name "BlueZ (%d)";
+
+ # Local device class
+ class 0x000100;
+
+ # Default packet type
+ #pkt_type DH1,DM1,HV1;
+
+ # Inquiry and Page scan
+ iscan enable; pscan enable;
+
+ # Default link mode
+ # none - no specific policy
+ # accept - always accept incoming connections
+ # master - become master on incoming connections,
+ # deny role switch on outgoing connections
+ lm accept;
+
+ # Default link policy
+ # none - no specific policy
+ # rswitch - allow role switch
+ # hold - allow hold mode
+ # sniff - allow sniff mode
+ # park - allow park mode
+ lp rswitch,hold,sniff,park;
+}
diff --git a/src/hcid.conf.5 b/src/hcid.conf.5
new file mode 100644
index 00000000..cb5bcfa9
--- /dev/null
+++ b/src/hcid.conf.5
@@ -0,0 +1,227 @@
+.TH "HCID.CONF" "5" "March 2004" "hcid.conf - HCI daemon" "System management commands"
+.SH "NAME"
+/etc/bluetooth/hcid.conf \- Configuration file for the hcid Bluetooth HCI daemon
+
+.SH "DESCRIPTION"
+/etc/bluetooth/hcid.conf contains all the options needed by the Bluetooth Host Controller Interface daemon.
+
+It consists of sections and parameters. A section begins with
+the name of the section followed by optional specifiers and the
+parameters inside curly brackets. Sections contain parameters of
+the form:
+.TP
+\fIname\fP \fIvalue1\fP, \fIvalue2\fP ... ;
+
+.PP
+Any character after a hash ('#') character is ignored until newline.
+Whitespace is also ignored.
+
+
+The valid section names for
+.B hcid.conf
+are, at the moment:
+
+.TP
+.B options
+contains generic options for hcid and the pairing policy.
+.TP
+.B device
+contains lower\-level options for the hci devices connected to the computer.
+.SH "OPTIONS SECTION"
+The following parameters may be present in an option section:
+
+
+.TP
+\fBautoinit\fP yes|no
+
+Automatically initialize newly connected devices. The default is \fIno\fP.
+
+
+.TP
+\fBpairing\fP none|multi|once
+
+\fInone\fP means that pairing is disabled. \fImulti\fP allows pairing
+with already paired devices. \fIonce\fP allows pairing once and denies
+successive attempts. The default hcid configuration is shipped with \fBmulti\fP
+enabled
+
+.TP
+\fBoffmode\fP noscan|devdown
+
+\fInoscan\fP means that page and inquiry scans are disabled when you call
+SetMode("off"). \fIdevdown\fP sets the adapter into down state (same what
+\fIhciconfig hci0 down\fP does).
+
+.TP
+\fBdeviceid\fP <vendor>:<product>:<version>
+
+This option allows to specify the vendor and product information of the
+Bluetooth device ID service record.
+
+.TP
+\fBpasskey\fP "\fIpin\fP"
+
+The default PIN for incoming connections if \fBsecurity\fP has been
+set to \fIauto\fP.
+
+.TP
+\fBsecurity\fP none|auto|user
+
+\fInone\fP means the security manager is disabled. \fIauto\fP uses
+local PIN, by default from pin_code, for incoming
+connections. \fIuser\fP always asks the user for a PIN.
+
+.SH "DEVICE SECTION"
+Parameters within a device section with no specifier, the default
+device section, will be applied to all devices and device sections
+where these are unspecified. The following optional device specifiers
+are supported:
+
+.TP
+\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP\fB:\fP\fInn\fP
+
+Parameters specified within this section will be applied to the device
+with this \fIdevice bluetooth address\fP. All other parameters are applied from
+the default section.
+
+.TP
+\fBhci\fIn\fP
+
+Parameters specified within this section will be applied to the device
+with this \fIdevice interface\fP, unless that device is matched by a
+\fIdevice address\fP section. All other parameters are applied from
+the default section.
+
+
+.PP
+\fBNote\fP: Most of the options supported in the \fBdevice\fP section are described to some extent in the bluetooth specification version 1.2 Vol2, Part E section 6. Please refer to it for technical details.
+
+.PP
+The following parameters may be present in a device section:
+
+.TP
+\fBname\fP "\fIname\fP"
+
+The device name. \fI%d\fP inserts the device id. \fI%h\fP inserts
+the host name.
+
+
+.TP
+\fBclass\fP 0x\fISSDDdd\fP (three bytes)
+
+The Bluetooth Device Class is described in the Bluetooth Specification section 1.2 ("Assigned Numbers \- Bluetooth Baseband").
+
+The default shipped with hcid is 0x000100 which simply stands for "Computer".
+
+The Bluetooth device class is a high\-level description of the bluetooth device, composed of three bytes: the "Major Service Class" (byte "SS" above), the "Major Device Class" (byte "DD" above) and the "Minor Device Class" (byte "dd" above). These classes describe the high\-level capabilities of the device, such as "Networking Device", "Computer", etc. This information is often used by clients who are looking for a certain type of service around them.
+
+Where it becomes tricky is that another type of mechanism for service discovery exists: "SDP", as in "Service Discovery Protocol".
+
+In practice, most Bluetooth clients scan their surroundings in two successive steps: they first look for all bluetooth devices around them and find out their "class". You can do this on Linux with the \fBhcitool scan\fP command. Then, they use SDP in order to check if a device in a given class offers the type of service that they want.
+
+This means that the hcid.conf "class" parameter needs to be set up properly if particular services are running on the host, such as "PAN", or "OBEX Obect Push", etc: in general a device looking for a service such as "Network Access Point" will only scan for this service on devices containing "Networking" in their major service class.
+
+
+.IP
+Major service class byte allocation (from LSB to MSB):
+
+Bit 1: Positioning (Location identification)
+
+Bit 2: Networking (LAN, Ad hoc, ...)
+
+Bit 3: Rendering (Printing, Speaker, ...)
+
+Bit 4: Capturing (Scanner, Microphone, ...)
+
+Bit 5: Object Transfer (v\-Inbox, v\-Folder, ...)
+
+Bit 6: Audio (Speaker, Microphone, Headset service, ...)
+
+Bit 7: Telephony (Cordless telephony, Modem, Headset service, ...)
+
+Bit 8: Information (WEB\-server, WAP\-server, ...)
+
+.IP
+Example: class 0x02hhhh : the device offers networking service
+
+
+.IP
+Major device class allocation:
+
+0x00: Miscellaneous
+
+0x01: Computer (desktop,notebook, PDA, organizers, .... )
+
+0x02: Phone (cellular, cordless, payphone, modem, ...)
+
+0x03: LAN /Network Access point
+
+0x04: Audio/Video (headset,speaker,stereo, video display, vcr.....
+
+0x05: Peripheral (mouse, joystick, keyboards, ..... )
+
+0x06: Imaging (printing, scanner, camera, display, ...)
+
+Other values are not defined (refer to the Bluetooth specification for more details
+
+.IP
+Minor device class allocation: the meaning of this byte depends on the major class allocation, please refer to the Bluetooth specifications for more details).
+
+.IP
+.B Example:
+if PAND runs on your server, you need to set up at least \fBclass 0x020100\fP, which stands for "Service Class: Networking" and "Device Class: Computer, Uncategorized".
+
+
+.TP
+\fBiscan\fP enable|disable
+.TP
+\fBpscan\fP enable|disable
+
+Bluetooth devices discover and connect to each other through the use of two special Bluetooth channels, the Inquiry and Page channels (described in the Bluetooth Spec Volume 1, Part A, Section 3.3.3, page 35). These two options enable the channels on the bluetooth device.
+
+\fBiscan enable\fP: makes the bluetooth device "discoverable" by enabling it to answer "inquiries" from other nearby bluetooth devices.
+
+\fBpscan enable\fP: makes the bluetooth device "connectable to" by enabling the use of the "page scan" channel.
+
+.TP
+\fBlm\fP none|accept,master
+
+\fInone\fP means no specific policy. \fIaccept\fP means always accept
+incoming connections. \fImaster\fP means become master on incoming
+connections and deny role switch on outgoing connections.
+
+.TP
+\fBlp\fP none|rswitch,hold,sniff,park
+
+\fInone\fP means no specific policy. \fIrswitch\fP means allow role
+switch. \fIhold\fP means allow hold mode. \fIsniff\fP means allow
+sniff mode. \fIpark\fP means allow park mode. Several options can be
+combined.
+
+This option determines the various operational modes that are allowed for this device when it participates to a piconet. Normally hold and sniff should be enabled for standard operations.
+
+hold: this mode is related to synchronous communications (SCO voice channel for example).
+
+sniff: when in this mode, a device is only present on the piconet during determined slots of time, allowing it to do other things when it is "absent", for example to scan for other bluetooth devices.
+
+park: this is a mode where the device is put on standby on the piconet, for power\-saving purposes for example.
+
+rswitch: this is a mode that enables role\-switch (master <\-> slave) between two devices in a piconet. It is not clear whether this needs to be enabled in order to make the "lm master" setting work properly or not.
+
+.TP
+\fBpageto\fP \fIn\fP
+
+Page Timeout measured in number of baseband slots. Interval length = N * 0.625 msec (1 baseband slot)
+
+.TP
+\fBdiscovto\fP \fIn\fP
+
+The time in seconds that the device will stay in discoverable mode. 0 disables this feature and forces the device to be always discoverable.
+
+.SH "FILES"
+.TP
+.I /etc/bluetooth/hcid.conf
+Default location of the global configuration file.
+
+.SH "AUTHOR"
+This manual page was written by Edouard Lafargue, Fredrik Noring, Maxim Krasnyansky and Marcel Holtmann.
diff --git a/src/hcid.h b/src/hcid.h
new file mode 100644
index 00000000..2dd522f7
--- /dev/null
+++ b/src/hcid.h
@@ -0,0 +1,204 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2000-2001 Qualcomm Incorporated
+ * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
+ * Copyright (C) 2002-2008 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 <time.h>
+#include <sys/types.h>
+
+#include <glib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/hci.h>
+
+#include "logging.h"
+
+#define HCID_CONFIG_FILE CONFIGDIR "/hcid.conf"
+
+#define HCID_DEFAULT_DISCOVERABLE_TIMEOUT 180 /* 3 minutes */
+
+/* When all services should trust a remote device */
+#define GLOBAL_TRUST "[all]"
+
+enum {
+ HCID_SET_NAME,
+ HCID_SET_CLASS,
+ HCID_SET_VOICE,
+ HCID_SET_INQMODE,
+ HCID_SET_PAGETO,
+ HCID_SET_DISCOVTO,
+ HCID_SET_PTYPE,
+ HCID_SET_LM,
+ HCID_SET_LP,
+};
+
+/*
+ * Scanning modes, used by DEV_SET_MODE
+ * off: remote devices are not allowed to find or connect to this device
+ * connectable: remote devices are allowed to connect, but they are not
+ * allowed to find it.
+ * discoverable: remote devices are allowed to connect and find this device
+ * limited: limited discoverable - GIAC + IAC enabled and set limited
+ * bit on device class.
+ */
+
+#define MODE_OFF 0x00
+#define MODE_CONNECTABLE 0x01
+#define MODE_DISCOVERABLE 0x02
+#define MODE_LIMITED 0x03
+#define MODE_UNKNOWN 0xff
+
+struct device_opts {
+ unsigned long flags;
+ char *name;
+ uint32_t class;
+ uint16_t voice;
+ uint8_t inqmode;
+ uint16_t pageto;
+ uint16_t pkt_type;
+ uint16_t link_mode;
+ uint16_t link_policy;
+ uint8_t scan;
+ uint8_t mode;
+ uint32_t discovto;
+};
+
+extern struct device_opts default_device;
+extern struct device_opts *parser_device;
+
+struct device_list {
+ char *ref; /* HCI device or Bluetooth address */
+ struct device_list *next;
+ struct device_opts opts;
+};
+
+struct hcid_opts {
+ char host_name[40];
+ int auto_init;
+ int security;
+ int pairing;
+ int offmode;
+ char deviceid[15];
+
+ char *config_file;
+
+ uint8_t pin_code[16];
+ int pin_len;
+
+ int sock;
+};
+extern struct hcid_opts hcid;
+
+typedef enum {
+ REQ_PENDING,
+ REQ_SENT
+} req_status_t;
+
+struct hci_req_data {
+ int dev_id;
+ int event;
+ req_status_t status;
+ bdaddr_t dba;
+ uint16_t ogf;
+ uint16_t ocf;
+ void *cparam;
+ int clen;
+};
+
+struct hci_req_data *hci_req_data_new(int dev_id, const bdaddr_t *dba, uint16_t ogf, uint16_t ocf, int event, const void *cparam, int clen);
+void hci_req_queue_append(struct hci_req_data *data);
+void hci_req_queue_remove(int dev_id, bdaddr_t *dba);
+
+#define HCID_SEC_NONE 0
+#define HCID_SEC_AUTO 1
+#define HCID_SEC_USER 2
+
+#define HCID_PAIRING_NONE 0
+#define HCID_PAIRING_MULTI 1
+#define HCID_PAIRING_ONCE 2
+
+#define HCID_OFFMODE_DEVDOWN 0
+#define HCID_OFFMODE_NOSCAN 1
+
+int read_config(char *file);
+
+struct device_opts *alloc_device_opts(char *ref);
+
+uint8_t get_startup_scan(int hdev);
+uint8_t get_startup_mode(int hdev);
+int get_discoverable_timeout(int dev_id);
+
+void init_security_data(void);
+void start_security_manager(int hdev);
+void stop_security_manager(int hdev);
+void toggle_pairing(int enable);
+
+void set_pin_length(bdaddr_t *sba, int length);
+
+int get_device_alias(uint16_t dev_id, const bdaddr_t *bdaddr, char *alias, size_t size);
+int set_device_alias(uint16_t dev_id, const bdaddr_t *bdaddr, const char *alias);
+
+int get_encryption_key_size(uint16_t dev_id, const bdaddr_t *baddr);
+
+int write_discoverable_timeout(bdaddr_t *bdaddr, int timeout);
+int read_discoverable_timeout(bdaddr_t *bdaddr, int *timeout);
+int write_device_mode(bdaddr_t *bdaddr, const char *mode);
+int read_device_mode(bdaddr_t *bdaddr, char *mode, int length);
+int read_on_mode(bdaddr_t *bdaddr, char *mode, int length);
+int write_local_name(bdaddr_t *bdaddr, 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(bdaddr_t *local, bdaddr_t *peer, char *name);
+int write_remote_eir(bdaddr_t *local, bdaddr_t *peer, uint8_t *data);
+int write_l2cap_info(bdaddr_t *local, bdaddr_t *peer,
+ uint16_t mtu_result, uint16_t mtu,
+ uint16_t mask_result, uint32_t mask);
+int read_l2cap_info(bdaddr_t *local, bdaddr_t *peer,
+ uint16_t *mtu_result, uint16_t *mtu,
+ uint16_t *mask_result, uint32_t *mask);
+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 *features);
+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_length(bdaddr_t *local, bdaddr_t *peer);
+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(bdaddr_t *local, 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 *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);
+
+gboolean plugin_init(GKeyFile *config);
+void plugin_cleanup(void);
+void __probe_servers(const char *adapter);
+void __remove_servers(const char *adapter);
diff --git a/src/kword.c b/src/kword.c
new file mode 100644
index 00000000..3a89e5a3
--- /dev/null
+++ b/src/kword.c
@@ -0,0 +1,102 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2000-2001 Qualcomm Incorporated
+ * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
+ * Copyright (C) 2002-2008 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 <string.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include "hcid.h"
+#include "kword.h"
+#include "parser.h"
+
+struct kword cfg_keyword[] = {
+ { "options", K_OPTIONS },
+ { "default", K_DEVICE },
+ { "device", K_DEVICE },
+ { "autoinit", K_AUTOINIT },
+ { "security", K_SECURITY },
+ { "pairing", K_PAIRING },
+ { "offmode", K_OFFMODE },
+ { "deviceid", K_DEVICEID },
+ { "pkt_type", K_PTYPE },
+ { "lm", K_LM },
+ { "lp", K_LP },
+ { "iscan", K_ISCAN },
+ { "pscan", K_PSCAN },
+ { "name", K_NAME },
+ { "class", K_CLASS },
+ { "voice", K_VOICE },
+ { "pageto", K_PAGETO },
+ { "discovto", K_DISCOVTO },
+ { "passkey", K_PASSKEY },
+
+ { "yes", K_YES },
+ { "no", K_NO },
+ { "enable", K_YES },
+ { "disable", K_NO },
+ { NULL , 0 }
+};
+
+struct kword sec_param[] = {
+ { "none", HCID_SEC_NONE },
+ { "auto", HCID_SEC_AUTO },
+ { "user", HCID_SEC_USER },
+ { NULL , 0 }
+};
+
+struct kword pair_param[] = {
+ { "none", HCID_PAIRING_NONE },
+ { "multi", HCID_PAIRING_MULTI },
+ { "once", HCID_PAIRING_ONCE },
+ { NULL , 0 }
+};
+
+struct kword off_param[] = {
+ { "devdown", HCID_OFFMODE_DEVDOWN },
+ { "noscan", HCID_OFFMODE_NOSCAN },
+ { NULL , 0 }
+};
+
+int lineno;
+
+int find_keyword(struct kword *kw, char *str)
+{
+ while (kw->str) {
+ if (!strcmp(str,kw->str))
+ return kw->type;
+ kw++;
+ }
+ return -1;
+}
diff --git a/src/kword.h b/src/kword.h
new file mode 100644
index 00000000..ac4781cc
--- /dev/null
+++ b/src/kword.h
@@ -0,0 +1,37 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2000-2001 Qualcomm Incorporated
+ * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
+ * Copyright (C) 2002-2008 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 kword {
+ char *str;
+ int type;
+};
+extern int lineno;
+
+extern struct kword cfg_keyword[];
+extern struct kword sec_param[];
+extern struct kword pair_param[];
+extern struct kword off_param[];
+
+int find_keyword(struct kword *kw, char *str);
diff --git a/src/lexer.l b/src/lexer.l
new file mode 100644
index 00000000..768a0783
--- /dev/null
+++ b/src/lexer.l
@@ -0,0 +1,160 @@
+%{
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2000-2001 Qualcomm Incorporated
+ * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
+ * Copyright (C) 2002-2008 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 <string.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include "hcid.h"
+#include "kword.h"
+#include "parser.h"
+
+static char str_buf[255];
+
+#define ECHO {;}
+#define YY_DECL int yylex(void)
+
+int cfg_error(const char *ftm, ...);
+int yyerror(char *str);
+
+%}
+
+%option nounput
+
+hex 0x[0-9a-zA-Z]+
+num [0-9]+
+kword [A-Za-z0-9\_\-]+
+word [A-Za-z0-9\-\_+=\!\$\#\%\&\*\^\@@\\\~\.]+
+wordnm {word}:{num}
+list ({word}\,*)+
+comment \#.*\n
+fname [A-Za-z0-9\_\.\-]+
+path (\/{fname})+
+string \".*\"
+hci hci[0-9]+
+hextuple [0-9a-zA-Z][0-9a-zA-Z]
+hexquad {hextuple}{hextuple}
+bdaddr {hextuple}:{hextuple}:{hextuple}:{hextuple}:{hextuple}:{hextuple}
+id {hexquad}:{hexquad}
+
+%x OPTION PARAM
+
+%%
+[ \t] {
+ /* Skip spaces and tabs */
+ ;
+}
+
+{comment} {
+ /* Skip comments */
+ lineno++;
+}
+
+\n {
+ lineno++;
+}
+
+{hci} {
+ yylval.str = yytext;
+ return HCI;
+}
+
+{bdaddr} {
+ yylval.str = yytext;
+ return BDADDR;
+}
+
+{hex} {
+ yylval.num = strtol(yytext, NULL, 16);
+ return NUM;
+}
+
+{num} {
+ yylval.num = atoi(yytext);
+ return NUM;
+}
+
+{kword} {
+ int kw = find_keyword(cfg_keyword, yytext);
+ if( kw != -1 )
+ return kw;
+
+ yylval.str = yytext;
+ return WORD;
+}
+
+{word} {
+ yylval.str = yytext;
+ return WORD;
+}
+
+{string} {
+ if (yyleng > sizeof(str_buf) - 1) {
+ yyerror("string too long");
+ return 0;
+ }
+
+ strncpy(str_buf, yytext + 1, yyleng - 2);
+ str_buf[yyleng - 2] = '\0';
+
+ yylval.str = str_buf;
+ return STRING;
+}
+
+{list} {
+ yylval.str = yytext;
+ return LIST;
+}
+
+{path} {
+ yylval.str = yytext;
+ return PATH;
+}
+
+{id} {
+ yylval.str = yytext;
+ return ID;
+}
+
+. {
+ return *yytext;
+}
+
+%%
+
+int yywrap(void)
+{
+ return 1;
+}
diff --git a/src/list-devices b/src/list-devices
new file mode 100755
index 00000000..ec6c580a
--- /dev/null
+++ b/src/list-devices
@@ -0,0 +1,52 @@
+#!/usr/bin/python
+
+import dbus
+
+bus = dbus.SystemBus()
+
+manager = dbus.Interface(bus.get_object("org.bluez", "/"),
+ "org.bluez.Manager")
+
+def extract_uuids(uuid_list):
+ list = ""
+ for uuid in uuid_list:
+ if (uuid.endswith("-0000-1000-8000-00805f9b34fb")):
+ if (uuid.startswith("0000")):
+ val = "0x" + uuid[4:8]
+ else:
+ val = "0x" + uuid[0:8]
+ else:
+ val = str(uuid)
+ list = list + val + " "
+ return list
+
+adapter_list = manager.ListAdapters()
+
+for i in adapter_list:
+ adapter = dbus.Interface(bus.get_object("org.bluez", i),
+ "org.bluez.Adapter")
+ print "[ " + i + " ]"
+
+ properties = adapter.GetProperties()
+ for key in properties.keys():
+ print " %s = %s" % (key, properties[key])
+
+ device_list = adapter.ListDevices()
+
+ for n in device_list:
+ device = dbus.Interface(bus.get_object("org.bluez", n),
+ "org.bluez.Device")
+ print " [ " + n + " ]"
+
+ properties = device.GetProperties()
+ for key in properties.keys():
+ value = properties[key]
+ if (key == "UUIDs"):
+ list = extract_uuids(value)
+ print " %s = %s" % (key, list)
+ elif (key == "Class"):
+ print " %s = 0x%06x" % (key, value)
+ else:
+ print " %s = %s" % (key, value)
+
+ print
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 00000000..fbd776a6
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,965 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2000-2001 Qualcomm Incorporated
+ * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
+ * Copyright (C) 2002-2008 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 <signal.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include <glib.h>
+
+#include <dbus/dbus.h>
+
+#include "hcid.h"
+#include "sdpd.h"
+#include "adapter.h"
+#include "dbus-common.h"
+#include "dbus-database.h"
+#include "dbus-hci.h"
+#include "device.h"
+#include "agent.h"
+#include "manager.h"
+
+struct hcid_opts hcid;
+struct device_opts default_device;
+struct device_opts *parser_device;
+static struct device_list *device_list = NULL;
+static int child_pipe[2];
+
+static GKeyFile *load_config(const char *file)
+{
+ GError *err = NULL;
+ GKeyFile *keyfile;
+
+ keyfile = g_key_file_new();
+
+ 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 inline void init_device_defaults(struct device_opts *device_opts)
+{
+ memset(device_opts, 0, sizeof(*device_opts));
+ device_opts->scan = SCAN_PAGE;
+ device_opts->mode = MODE_CONNECTABLE;
+ device_opts->name = g_strdup("BlueZ");
+ device_opts->discovto = HCID_DEFAULT_DISCOVERABLE_TIMEOUT;
+}
+
+struct device_opts *alloc_device_opts(char *ref)
+{
+ struct device_list *device;
+
+ device = g_try_new(struct device_list, 1);
+ if (!device) {
+ info("Can't allocate devlist opts buffer: %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ device->ref = g_strdup(ref);
+ device->next = device_list;
+ device_list = device;
+
+ memcpy(&device->opts, &default_device, sizeof(struct device_opts));
+ device->opts.name = g_strdup(default_device.name);
+
+ return &device->opts;
+}
+
+static void free_device_opts(void)
+{
+ struct device_list *device, *next;
+
+ g_free(default_device.name);
+
+ for (device = device_list; device; device = next) {
+ g_free(device->ref);
+ g_free(device->opts.name);
+ next = device->next;
+ g_free(device);
+ }
+
+ device_list = NULL;
+}
+
+static inline struct device_opts *find_device_opts(char *ref)
+{
+ struct device_list *device;
+
+ for (device = device_list; device; device = device->next)
+ if (!strcmp(ref, device->ref))
+ return &device->opts;
+
+ return NULL;
+}
+
+static struct device_opts *get_device_opts(int hdev)
+{
+ struct device_opts *device_opts = NULL;
+ struct hci_dev_info di;
+
+ /* First try to get BD_ADDR based settings ... */
+ if (hci_devinfo(hdev, &di) == 0) {
+ char addr[18];
+ ba2str(&di.bdaddr, addr);
+ device_opts = find_device_opts(addr);
+ }
+
+ /* ... then try HCI based settings ... */
+ if (!device_opts) {
+ char ref[8];
+ snprintf(ref, sizeof(ref) - 1, "hci%d", hdev);
+ device_opts = find_device_opts(ref);
+ }
+
+ /* ... and last use the default settings. */
+ if (!device_opts)
+ device_opts = &default_device;
+
+ return device_opts;
+}
+
+static struct device_opts *get_opts(int hdev)
+{
+ struct device_opts *device_opts = NULL;
+ struct hci_dev_info di;
+ char addr[18];
+ int sock;
+
+ if (hdev < 0)
+ return NULL;
+
+ sock = hci_open_dev(hdev);
+ if (sock < 0)
+ goto no_address;
+
+ if (hci_devinfo(hdev, &di) < 0) {
+ close(sock);
+ goto no_address;
+ }
+
+ close(sock);
+
+ ba2str(&di.bdaddr, addr);
+ device_opts = find_device_opts(addr);
+
+no_address:
+ if (!device_opts) {
+ char ref[8];
+ snprintf(ref, sizeof(ref) - 1, "hci%d", hdev);
+ device_opts = find_device_opts(ref);
+ }
+
+ if (!device_opts)
+ device_opts = &default_device;
+
+ return device_opts;
+}
+
+uint8_t get_startup_scan(int hdev)
+{
+ struct device_opts *device_opts = get_opts(hdev);
+ if (!device_opts)
+ return SCAN_DISABLED;
+
+ return device_opts->scan;
+}
+
+uint8_t get_startup_mode(int hdev)
+{
+ struct device_opts *device_opts = get_opts(hdev);
+ if (!device_opts)
+ return MODE_OFF;
+
+ return device_opts->mode;
+}
+
+int get_discoverable_timeout(int hdev)
+{
+ struct device_opts *device_opts = NULL;
+ struct hci_dev_info di;
+ char addr[18];
+ int sock, timeout;
+
+ if (hdev < 0)
+ return HCID_DEFAULT_DISCOVERABLE_TIMEOUT;
+
+ sock = hci_open_dev(hdev);
+ if (sock < 0)
+ goto no_address;
+
+ if (hci_devinfo(hdev, &di) < 0) {
+ close(sock);
+ goto no_address;
+ }
+
+ close(sock);
+
+ if (read_discoverable_timeout(&di.bdaddr, &timeout) == 0)
+ return timeout;
+
+ ba2str(&di.bdaddr, addr);
+ device_opts = find_device_opts(addr);
+
+no_address:
+ if (!device_opts) {
+ char ref[8];
+ snprintf(ref, sizeof(ref) - 1, "hci%d", hdev);
+ device_opts = find_device_opts(ref);
+ }
+
+ if (!device_opts)
+ device_opts = &default_device;
+
+ return device_opts->discovto;
+}
+
+void update_service_classes(const bdaddr_t *bdaddr, uint8_t value)
+{
+ struct hci_dev_list_req *dl;
+ struct hci_dev_req *dr;
+ int i, sk;
+
+ sk = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
+ if (sk < 0)
+ return;
+
+ dl = g_malloc0(HCI_MAX_DEV * sizeof(*dr) + sizeof(*dl));
+
+ dl->dev_num = HCI_MAX_DEV;
+ dr = dl->dev_req;
+
+ if (ioctl(sk, HCIGETDEVLIST, dl) < 0) {
+ close(sk);
+ g_free(dl);
+ return;
+ }
+
+ dr = dl->dev_req;
+
+ for (i = 0; i < dl->dev_num; i++, dr++) {
+ struct hci_dev_info di;
+ uint8_t cls[3];
+ int dd;
+
+ if (hci_devinfo(dr->dev_id, &di) < 0)
+ continue;
+
+ if (hci_test_bit(HCI_RAW, &di.flags))
+ continue;
+
+ if (!hci_test_bit(HCI_UP, &di.flags))
+ continue;
+
+ if (manager_get_adapter_class(di.dev_id, cls) < 0)
+ continue;
+
+ dd = hci_open_dev(di.dev_id);
+ if (dd < 0)
+ continue;
+
+ set_service_classes(dd, cls, value);
+
+ hci_close_dev(dd);
+
+ manager_update_adapter(di.dev_id);
+ }
+
+ g_free(dl);
+
+ close(sk);
+}
+
+/*
+ * 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 = hcid.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;
+}
+
+static gboolean child_exit(GIOChannel *io, GIOCondition cond, void *user_data)
+{
+ int status, fd = g_io_channel_unix_get_fd(io);
+ pid_t child_pid;
+
+ if (read(fd, &child_pid, sizeof(child_pid)) != sizeof(child_pid)) {
+ error("child_exit: unable to read child pid from pipe");
+ return TRUE;
+ }
+
+ if (waitpid(child_pid, &status, 0) != child_pid)
+ error("waitpid(%d) failed", child_pid);
+ else
+ debug("child %d exited", child_pid);
+
+ return TRUE;
+}
+
+static void at_child_exit(void)
+{
+ pid_t pid = getpid();
+
+ if (write(child_pipe[1], &pid, sizeof(pid)) != sizeof(pid))
+ error("unable to write to child pipe");
+}
+
+static void configure_device(int dev_id)
+{
+ struct device_opts *device_opts;
+ struct hci_dev_req dr;
+ struct hci_dev_info di;
+ char mode[14];
+ int dd;
+
+ device_opts = get_device_opts(dev_id);
+
+ if (hci_devinfo(dev_id, &di) < 0)
+ return;
+
+ if (hci_test_bit(HCI_RAW, &di.flags))
+ return;
+
+ /* Set default discoverable timeout if not set */
+ if (!(device_opts->flags & (1 << HCID_SET_DISCOVTO)))
+ device_opts->discovto = HCID_DEFAULT_DISCOVERABLE_TIMEOUT;
+
+ /* Set scan mode */
+ if (read_device_mode(&di.bdaddr, mode, sizeof(mode)) == 0) {
+ if (!strcmp(mode, "off") && hcid.offmode == HCID_OFFMODE_NOSCAN) {
+ device_opts->mode = MODE_OFF;
+ device_opts->scan = SCAN_DISABLED;
+ } else if (!strcmp(mode, "connectable")) {
+ device_opts->mode = MODE_CONNECTABLE;
+ device_opts->scan = SCAN_PAGE;
+ } else if (!strcmp(mode, "discoverable")) {
+ /* Set discoverable only if timeout is 0 */
+ if (!get_discoverable_timeout(dev_id)) {
+ device_opts->scan = SCAN_PAGE | SCAN_INQUIRY;
+ device_opts->mode = MODE_DISCOVERABLE;
+ } else {
+ device_opts->scan = SCAN_PAGE;
+ device_opts->mode = MODE_CONNECTABLE;
+ }
+ } else if (!strcmp(mode, "limited")) {
+ /* Set discoverable only if timeout is 0 */
+ if (!get_discoverable_timeout(dev_id)) {
+ device_opts->scan = SCAN_PAGE | SCAN_INQUIRY;
+ device_opts->mode = MODE_LIMITED;
+ } else {
+ device_opts->scan = SCAN_PAGE;
+ device_opts->mode = MODE_CONNECTABLE;
+ }
+ }
+ }
+
+ /* Do configuration in the separate process */
+ switch (fork()) {
+ case 0:
+ atexit(at_child_exit);
+ break;
+ case -1:
+ error("Fork failed. Can't init device hci%d: %s (%d)",
+ dev_id, strerror(errno), errno);
+ default:
+ return;
+ }
+
+ dd = hci_open_dev(dev_id);
+ if (dd < 0) {
+ error("Can't open device hci%d: %s (%d)",
+ dev_id, strerror(errno), errno);
+ exit(1);
+ }
+
+ memset(&dr, 0, sizeof(dr));
+ dr.dev_id = dev_id;
+
+ /* Set packet type */
+ if ((device_opts->flags & (1 << HCID_SET_PTYPE))) {
+ dr.dev_opt = device_opts->pkt_type;
+ if (ioctl(dd, HCISETPTYPE, (unsigned long) &dr) < 0) {
+ error("Can't set packet type on hci%d: %s (%d)",
+ dev_id, strerror(errno), errno);
+ }
+ }
+
+ /* Set link mode */
+ if ((device_opts->flags & (1 << HCID_SET_LM))) {
+ dr.dev_opt = device_opts->link_mode;
+ if (ioctl(dd, HCISETLINKMODE, (unsigned long) &dr) < 0) {
+ error("Can't set link mode on hci%d: %s (%d)",
+ dev_id, strerror(errno), errno);
+ }
+ }
+
+ /* Set link policy */
+ if ((device_opts->flags & (1 << HCID_SET_LP))) {
+ dr.dev_opt = device_opts->link_policy;
+ if (ioctl(dd, HCISETLINKPOL, (unsigned long) &dr) < 0) {
+ error("Can't set link policy on hci%d: %s (%d)",
+ dev_id, strerror(errno), errno);
+ }
+ }
+
+ /* Set device name */
+ if ((device_opts->flags & (1 << HCID_SET_NAME)) && device_opts->name) {
+ change_local_name_cp cp;
+
+ memset(cp.name, 0, sizeof(cp.name));
+ expand_name((char *) cp.name, sizeof(cp.name),
+ device_opts->name, dev_id);
+
+ hci_send_cmd(dd, OGF_HOST_CTL, OCF_CHANGE_LOCAL_NAME,
+ CHANGE_LOCAL_NAME_CP_SIZE, &cp);
+ }
+
+ /* Set device class */
+ if ((device_opts->flags & (1 << HCID_SET_CLASS))) {
+ write_class_of_dev_cp cp;
+ uint32_t class;
+ uint8_t cls[3];
+
+ if (read_local_class(&di.bdaddr, cls) < 0) {
+ class = htobl(device_opts->class);
+ cls[2] = get_service_classes(&di.bdaddr);
+ memcpy(cp.dev_class, &class, 3);
+ } else {
+ if (!(device_opts->scan & SCAN_INQUIRY))
+ cls[1] &= 0xdf; /* Clear discoverable bit */
+ cls[2] = get_service_classes(&di.bdaddr);
+ memcpy(cp.dev_class, cls, 3);
+ }
+
+ hci_send_cmd(dd, OGF_HOST_CTL, OCF_WRITE_CLASS_OF_DEV,
+ WRITE_CLASS_OF_DEV_CP_SIZE, &cp);
+ }
+
+ /* Set page timeout */
+ if ((device_opts->flags & (1 << HCID_SET_PAGETO))) {
+ write_page_timeout_cp cp;
+
+ cp.timeout = htobs(device_opts->pageto);
+ hci_send_cmd(dd, OGF_HOST_CTL, OCF_WRITE_PAGE_TIMEOUT,
+ WRITE_PAGE_TIMEOUT_CP_SIZE, &cp);
+ }
+
+ /* Set voice setting */
+ if ((device_opts->flags & (1 << HCID_SET_VOICE))) {
+ write_voice_setting_cp cp;
+
+ cp.voice_setting = htobl(device_opts->voice);
+ hci_send_cmd(dd, OGF_HOST_CTL, OCF_WRITE_VOICE_SETTING,
+ WRITE_VOICE_SETTING_CP_SIZE, &cp);
+ }
+
+ exit(0);
+}
+
+static void init_device(int dev_id)
+{
+ struct hci_dev_info di;
+ int dd;
+
+ /* Do initialization in the separate process */
+ switch (fork()) {
+ case 0:
+ atexit(at_child_exit);
+ break;
+ case -1:
+ error("Fork failed. Can't init device hci%d: %s (%d)",
+ dev_id, strerror(errno), errno);
+ default:
+ return;
+ }
+
+ dd = hci_open_dev(dev_id);
+ if (dd < 0) {
+ error("Can't open device hci%d: %s (%d)",
+ dev_id, strerror(errno), errno);
+ exit(1);
+ }
+
+ /* Start HCI device */
+ if (ioctl(dd, HCIDEVUP, dev_id) < 0 && errno != EALREADY) {
+ error("Can't init device hci%d: %s (%d)",
+ dev_id, strerror(errno), errno);
+ goto fail;
+ }
+
+ if (hci_devinfo(dev_id, &di) < 0)
+ goto fail;
+
+ if (hci_test_bit(HCI_RAW, &di.flags))
+ goto done;
+
+ if (hcid.offmode == HCID_OFFMODE_DEVDOWN) {
+ char mode[16];
+
+ if (read_device_mode(&di.bdaddr, mode, sizeof(mode)) == 0 &&
+ strcmp(mode, "off") == 0) {
+ ioctl(dd, HCIDEVDOWN, dev_id);
+ goto done;
+ }
+ }
+
+done:
+ hci_close_dev(dd);
+ exit(0);
+
+fail:
+ hci_close_dev(dd);
+ exit(1);
+}
+
+static void device_devreg_setup(int dev_id)
+{
+ struct hci_dev_info di;
+
+ if (hcid.auto_init)
+ init_device(dev_id);
+
+ if (hci_devinfo(dev_id, &di) < 0)
+ return;
+
+ if (!hci_test_bit(HCI_RAW, &di.flags))
+ manager_register_adapter(dev_id);
+}
+
+static void device_devup_setup(int dev_id)
+{
+ if (hcid.auto_init)
+ configure_device(dev_id);
+ manager_start_adapter(dev_id);
+ if (hcid.security)
+ start_security_manager(dev_id);
+}
+
+static void init_all_devices(int ctl)
+{
+ struct hci_dev_list_req *dl;
+ struct hci_dev_req *dr;
+ int i;
+
+ dl = g_try_malloc0(HCI_MAX_DEV * sizeof(struct hci_dev_req) + sizeof(uint16_t));
+ if (!dl) {
+ info("Can't allocate devlist buffer: %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ dl->dev_num = HCI_MAX_DEV;
+ dr = dl->dev_req;
+
+ if (ioctl(ctl, HCIGETDEVLIST, (void *) dl) < 0) {
+ info("Can't get device list: %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ for (i = 0; i < dl->dev_num; i++, dr++) {
+ info("HCI dev %d registered", dr->dev_id);
+ device_devreg_setup(dr->dev_id);
+ if (hci_test_bit(HCI_UP, &dr->dev_opt)) {
+ info("HCI dev %d already up", dr->dev_id);
+ device_devup_setup(dr->dev_id);
+ }
+ }
+
+ g_free(dl);
+}
+
+static void init_defaults(void)
+{
+ hcid.auto_init = 1;
+ hcid.security = HCID_SEC_AUTO;
+
+ init_device_defaults(&default_device);
+}
+
+static inline void device_event(GIOChannel *chan, evt_stack_internal *si)
+{
+ evt_si_device *sd = (void *) &si->data;
+
+ switch (sd->event) {
+ case HCI_DEV_REG:
+ info("HCI dev %d registered", sd->dev_id);
+ device_devreg_setup(sd->dev_id);
+ break;
+
+ case HCI_DEV_UNREG:
+ info("HCI dev %d unregistered", sd->dev_id);
+ manager_unregister_adapter(sd->dev_id);
+ break;
+
+ case HCI_DEV_UP:
+ info("HCI dev %d up", sd->dev_id);
+ device_devup_setup(sd->dev_id);
+ break;
+
+ case HCI_DEV_DOWN:
+ info("HCI dev %d down", sd->dev_id);
+ manager_stop_adapter(sd->dev_id);
+ if (hcid.security)
+ stop_security_manager(sd->dev_id);
+ break;
+ }
+}
+
+static gboolean io_stack_event(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+ unsigned char buf[HCI_MAX_FRAME_SIZE], *ptr;
+ evt_stack_internal *si;
+ hci_event_hdr *eh;
+ int type;
+ size_t len;
+ GIOError err;
+
+ ptr = buf;
+
+ if ((err = g_io_channel_read(chan, (gchar *) buf, sizeof(buf), &len))) {
+ if (err == G_IO_ERROR_AGAIN)
+ return TRUE;
+
+ error("Read from control socket failed: %s (%d)",
+ strerror(errno), errno);
+ return FALSE;
+ }
+
+ type = *ptr++;
+
+ if (type != HCI_EVENT_PKT)
+ return TRUE;
+
+ eh = (hci_event_hdr *) ptr;
+ if (eh->evt != EVT_STACK_INTERNAL)
+ return TRUE;
+
+ ptr += HCI_EVENT_HDR_SIZE;
+
+ si = (evt_stack_internal *) ptr;
+ switch (si->type) {
+ case EVT_SI_DEVICE:
+ device_event(chan, si);
+ break;
+ }
+
+ return TRUE;
+}
+
+static GMainLoop *event_loop;
+
+static void sig_term(int sig)
+{
+ g_main_loop_quit(event_loop);
+}
+
+static void sig_hup(int sig)
+{
+ info("Reloading config file");
+
+ free_device_opts();
+
+ init_defaults();
+
+ if (read_config(hcid.config_file) < 0)
+ error("Config reload failed");
+
+ init_security_data();
+
+ init_all_devices(hcid.sock);
+}
+
+static void sig_debug(int sig)
+{
+ toggle_debug();
+}
+
+static void usage(void)
+{
+ printf("hcid - HCI daemon ver %s\n", VERSION);
+ printf("Usage: \n");
+ printf("\thcid [-n] [-d] [-m mtu] [-f config file]\n");
+}
+
+int main(int argc, char *argv[])
+{
+ struct sockaddr_hci addr;
+ struct hci_filter flt;
+ struct sigaction sa;
+ GIOChannel *ctl_io, *child_io;
+ uint16_t mtu = 0;
+ int opt, daemonize = 1, debug = 0, sdp = 1, experimental = 0;
+ GKeyFile *config;
+
+ /* Default HCId settings */
+ memset(&hcid, 0, sizeof(hcid));
+ hcid.auto_init = 1;
+ hcid.config_file = HCID_CONFIG_FILE;
+ hcid.security = HCID_SEC_AUTO;
+ hcid.pairing = HCID_PAIRING_MULTI;
+ hcid.offmode = HCID_OFFMODE_NOSCAN;
+
+ if (gethostname(hcid.host_name, sizeof(hcid.host_name) - 1) < 0)
+ strcpy(hcid.host_name, "noname");
+
+ strcpy((char *) hcid.pin_code, "BlueZ");
+ hcid.pin_len = 5;
+
+ init_defaults();
+
+ while ((opt = getopt(argc, argv, "ndsm:xf:")) != EOF) {
+ switch (opt) {
+ case 'n':
+ daemonize = 0;
+ break;
+
+ case 'd':
+ debug = 1;
+ break;
+
+ case 's':
+ sdp = 1;
+ break;
+
+ case 'm':
+ mtu = atoi(optarg);
+ break;
+
+ case 'x':
+ experimental = 1;
+ break;
+
+ case 'f':
+ hcid.config_file = g_strdup(optarg);
+ break;
+
+ default:
+ usage();
+ exit(1);
+ }
+ }
+
+ if (daemonize && daemon(0, 0)) {
+ error("Can't daemonize: %s (%d)", strerror(errno), errno);
+ exit(1);
+ }
+
+ umask(0077);
+
+ start_logging("hcid", "Bluetooth HCI daemon");
+
+ 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_hup;
+ sigaction(SIGHUP, &sa, NULL);
+
+ sa.sa_handler = sig_debug;
+ sigaction(SIGUSR2, &sa, NULL);
+
+ sa.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &sa, NULL);
+
+ if (debug) {
+ info("Enabling debug information");
+ enable_debug();
+ }
+
+ /* Create and bind HCI socket */
+ if ((hcid.sock = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)) < 0) {
+ error("Can't open HCI socket: %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ /* Set filter */
+ hci_filter_clear(&flt);
+ hci_filter_set_ptype(HCI_EVENT_PKT, &flt);
+ hci_filter_set_event(EVT_STACK_INTERNAL, &flt);
+ if (setsockopt(hcid.sock, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) {
+ error("Can't set filter: %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ addr.hci_family = AF_BLUETOOTH;
+ addr.hci_dev = HCI_DEV_NONE;
+ if (bind(hcid.sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ error("Can't bind HCI socket: %s (%d)",
+ strerror(errno), errno);
+ exit(1);
+ }
+
+ config = load_config(CONFIGDIR "/main.conf");
+
+ if (read_config(hcid.config_file) < 0)
+ error("Config load failed");
+
+ if (pipe(child_pipe) < 0) {
+ error("pipe(): %s (%d)", strerror(errno), errno);
+ exit(1);
+ }
+
+ child_io = g_io_channel_unix_new(child_pipe[0]);
+ g_io_channel_set_close_on_unref(child_io, TRUE);
+ g_io_add_watch(child_io,
+ G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ child_exit, NULL);
+ g_io_channel_unref(child_io);
+
+ agent_init();
+
+ if (experimental)
+ hcid_dbus_set_experimental();
+
+ if (hcid_dbus_init() < 0) {
+ error("Unable to get on D-Bus");
+ exit(1);
+ }
+
+ start_sdp_server(mtu, hcid.deviceid, SDP_SERVER_COMPAT);
+ set_service_classes_callback(update_service_classes);
+
+ /* 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);
+
+ init_security_data();
+
+ event_loop = g_main_loop_new(NULL, FALSE);
+
+ ctl_io = g_io_channel_unix_new(hcid.sock);
+ g_io_channel_set_close_on_unref(ctl_io, TRUE);
+
+ g_io_add_watch(ctl_io, G_IO_IN, io_stack_event, NULL);
+
+ g_io_channel_unref(ctl_io);
+
+ /* Initialize already connected devices */
+ init_all_devices(hcid.sock);
+
+ g_main_loop_run(event_loop);
+
+ hcid_dbus_unregister();
+
+ plugin_cleanup();
+
+ stop_sdp_server();
+
+ free_device_opts();
+
+ agent_exit();
+
+ hcid_dbus_exit();
+
+ g_main_loop_unref(event_loop);
+
+ if (config)
+ g_key_file_free(config);
+
+ info("Exit");
+
+ stop_logging();
+
+ return 0;
+}
diff --git a/src/manager.c b/src/manager.c
new file mode 100644
index 00000000..4f638566
--- /dev/null
+++ b/src/manager.c
@@ -0,0 +1,547 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 <sys/stat.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+
+#include <dbus/dbus.h>
+
+#include <gdbus.h>
+
+#include "logging.h"
+#include "textfile.h"
+#include "hcid.h"
+#include "sdpd.h"
+#include "adapter.h"
+#include "dbus-common.h"
+#include "error.h"
+#include "dbus-hci.h"
+#include "dbus-database.h"
+#include "sdp-xml.h"
+#include "oui.h"
+#include "agent.h"
+#include "device.h"
+#include "glib-helper.h"
+
+#include "manager.h"
+
+static DBusConnection *connection = NULL;
+static int default_adapter_id = -1;
+static GSList *adapters = NULL;
+
+int manager_update_adapter(uint16_t dev_id)
+{
+ struct adapter *adapter;
+
+ adapter = manager_find_adapter_by_id(dev_id);
+ if (!adapter)
+ return -EINVAL;
+
+ return adapter_update(adapter);
+}
+
+int manager_get_adapter_class(uint16_t dev_id, uint8_t *cls)
+{
+ struct adapter *adapter;
+
+ adapter = manager_find_adapter_by_id(dev_id);
+ if (!adapter)
+ return -EINVAL;
+
+ return adapter_get_class(adapter, cls);
+}
+
+int manager_set_adapter_class(uint16_t dev_id, uint8_t *cls)
+{
+ struct adapter *adapter;
+
+ adapter = manager_find_adapter_by_id(dev_id);
+ if (!adapter)
+ return -EINVAL;
+
+ return adapter_set_class(adapter, cls);
+}
+
+int get_device_alias(uint16_t dev_id, const bdaddr_t *bdaddr, char *alias, size_t size)
+{
+ struct adapter *adapter = manager_find_adapter_by_id(dev_id);
+ char filename[PATH_MAX + 1], addr[18], *tmp;
+ int err;
+ const gchar *source = adapter_get_address(adapter);
+
+ create_name(filename, PATH_MAX, STORAGEDIR, source, "aliases");
+
+ ba2str(bdaddr, addr);
+
+ tmp = textfile_get(filename, addr);
+ if (!tmp)
+ return -ENXIO;
+
+ err = snprintf(alias, size, "%s", tmp);
+
+ free(tmp);
+
+ return err;
+}
+
+int set_device_alias(uint16_t dev_id, const bdaddr_t *bdaddr, const char *alias)
+{
+ struct adapter *adapter = manager_find_adapter_by_id(dev_id);
+ const gchar *source = adapter_get_address(adapter);
+ char filename[PATH_MAX + 1], addr[18];
+
+ create_name(filename, PATH_MAX, STORAGEDIR, source, "aliases");
+
+ create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+ ba2str(bdaddr, addr);
+
+ return textfile_put(filename, addr, alias);
+}
+
+static inline DBusMessage *invalid_args(DBusMessage *msg)
+{
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".InvalidArguments",
+ "Invalid arguments in method call");
+}
+
+static inline DBusMessage *no_such_adapter(DBusMessage *msg)
+{
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".NoSuchAdapter",
+ "No such adapter");
+}
+
+static inline DBusMessage *no_such_service(DBusMessage *msg)
+{
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".NoSuchService",
+ "No such service");
+}
+
+static inline DBusMessage *failed_strerror(DBusMessage *msg, int err)
+{
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".Failed",
+ strerror(err));
+}
+
+static int find_by_address(const char *str)
+{
+ struct hci_dev_list_req *dl;
+ struct hci_dev_req *dr;
+ bdaddr_t ba;
+ int i, sk;
+ int devid = -1;
+
+ sk = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
+ if (sk < 0)
+ return -1;
+
+ dl = g_malloc0(HCI_MAX_DEV * sizeof(*dr) + sizeof(*dl));
+
+ dl->dev_num = HCI_MAX_DEV;
+ dr = dl->dev_req;
+
+ if (ioctl(sk, HCIGETDEVLIST, dl) < 0)
+ goto out;
+
+ dr = dl->dev_req;
+ str2ba(str, &ba);
+
+ for (i = 0; i < dl->dev_num; i++, dr++) {
+ struct hci_dev_info di;
+
+ if (hci_devinfo(dr->dev_id, &di) < 0)
+ continue;
+
+ if (hci_test_bit(HCI_RAW, &di.flags))
+ continue;
+
+ if (!bacmp(&ba, &di.bdaddr)) {
+ devid = dr->dev_id;
+ break;
+ }
+ }
+
+out:
+ g_free(dl);
+ close(sk);
+ return devid;
+}
+
+static DBusMessage *default_adapter(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ DBusMessage *reply;
+ struct adapter *adapter;
+ const gchar *path;
+
+ adapter = manager_find_adapter_by_id(default_adapter_id);
+ if (!adapter)
+ return 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 adapter *adapter;
+ struct hci_dev_info di;
+ 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 (!strncmp(pattern, "hci", 3) && strlen(pattern) >= 4)
+ dev_id = atoi(pattern + 3);
+ else
+ dev_id = find_by_address(pattern);
+
+ if (dev_id < 0)
+ return no_such_adapter(msg);
+
+ if (hci_devinfo(dev_id, &di) < 0)
+ return no_such_adapter(msg);
+
+ if (hci_test_bit(HCI_RAW, &di.flags))
+ return no_such_adapter(msg);
+
+ adapter = manager_find_adapter_by_id(dev_id);
+ if (!adapter)
+ return 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 *list_adapters(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ DBusMessageIter iter;
+ DBusMessageIter array_iter;
+ DBusMessage *reply;
+ GSList *l;
+ uint16_t dev_id;
+
+ 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 adapter *adapter = l->data;
+ struct hci_dev_info di;
+ dev_id = adapter_get_dev_id(adapter);
+ const gchar *path = adapter_get_path(adapter);
+
+ if (hci_devinfo(dev_id, &di) < 0)
+ continue;
+
+ if (hci_test_bit(HCI_RAW, &di.flags))
+ continue;
+
+ dbus_message_iter_append_basic(&array_iter,
+ DBUS_TYPE_OBJECT_PATH, &path);
+ }
+
+ dbus_message_iter_close_container(&iter, &array_iter);
+
+ return reply;
+}
+
+static GDBusMethodTable manager_methods[] = {
+ { "DefaultAdapter", "", "o", default_adapter },
+ { "FindAdapter", "s", "o", find_adapter },
+ { "ListAdapters", "", "ao", list_adapters },
+ { }
+};
+
+static GDBusSignalTable manager_signals[] = {
+ { "AdapterAdded", "o" },
+ { "AdapterRemoved", "o" },
+ { "DefaultAdapterChanged", "o" },
+ { }
+};
+
+dbus_bool_t manager_init(DBusConnection *conn, const char *path)
+{
+ connection = conn;
+
+ return g_dbus_register_interface(conn, "/", MANAGER_INTERFACE,
+ manager_methods, manager_signals,
+ NULL, NULL, NULL);
+}
+
+void manager_cleanup(DBusConnection *conn, const char *path)
+{
+ g_dbus_unregister_interface(conn, "/", MANAGER_INTERFACE);
+}
+
+static gint adapter_id_cmp(gconstpointer a, gconstpointer b)
+{
+ struct adapter *adapter = (struct 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_path_cmp(gconstpointer a, gconstpointer b)
+{
+ struct adapter *adapter = (struct adapter *) a;
+ const char *path = b;
+ const gchar *adapter_path = adapter_get_path(adapter);
+
+ return strcmp(adapter_path, path);
+}
+
+static gint adapter_address_cmp(gconstpointer a, gconstpointer b)
+{
+ struct adapter *adapter = (struct adapter *) a;
+ const char *address = b;
+ const gchar *source = adapter_get_address(adapter);
+
+ return strcmp(source, address);
+}
+
+struct adapter *manager_find_adapter(const bdaddr_t *sba)
+{
+ GSList *match;
+ char address[18];
+
+ ba2str(sba, address);
+
+ match = g_slist_find_custom(adapters, address, adapter_address_cmp);
+ if (!match)
+ return NULL;
+
+ return match->data;
+}
+
+struct adapter *manager_find_adapter_by_path(const char *path)
+{
+ GSList *match;
+
+ match = g_slist_find_custom(adapters, path, adapter_path_cmp);
+ if (!match)
+ return NULL;
+
+ return match->data;
+}
+
+struct 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;
+}
+
+static void manager_add_adapter(struct adapter *adapter)
+{
+ const gchar *path = adapter_get_path(adapter);
+
+ g_dbus_emit_signal(connection, "/",
+ MANAGER_INTERFACE, "AdapterAdded",
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID);
+
+ adapters = g_slist_append(adapters, adapter);
+}
+
+static void manager_remove_adapter(struct adapter *adapter)
+{
+ uint16_t dev_id = adapter_get_dev_id(adapter);
+ const gchar *path = adapter_get_path(adapter);
+
+ g_dbus_emit_signal(connection, "/",
+ MANAGER_INTERFACE, "AdapterRemoved",
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID);
+
+ if ((default_adapter_id == dev_id || default_adapter_id < 0)) {
+ int new_default = hci_get_route(NULL);
+
+ if (new_default >= 0)
+ manager_set_default_adapter(new_default);
+ }
+
+ adapters = g_slist_remove(adapters, adapter);
+}
+
+int manager_register_adapter(int id)
+{
+ struct adapter *adapter = adapter_create(id);
+ const gchar *path;
+
+ if(!adapter)
+ return -1;
+
+ path = adapter_get_path(adapter);
+
+ if (!adapter_init(connection, path, adapter)) {
+ error("Adapter interface init failed on path %s", path);
+ adapter_free(adapter);
+ return -1;
+ }
+
+ __probe_servers(path);
+
+ manager_add_adapter(adapter);
+
+ return 0;
+}
+
+int manager_unregister_adapter(int id)
+{
+ struct 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);
+
+ __remove_servers(path);
+
+ adapter_stop(adapter);
+
+ manager_remove_adapter(adapter);
+
+ if (!adapter_cleanup(connection, path)) {
+ error("Failed to unregister adapter interface on %s object",
+ path);
+ return -1;
+ }
+
+ adapter_free(adapter);
+
+ return 0;
+}
+
+int manager_start_adapter(int id)
+{
+ struct adapter* adapter;
+ int ret;
+
+ adapter = manager_find_adapter_by_id(id);
+ if (!adapter) {
+ error("Getting device data failed: hci%d", id);
+ return -EINVAL;
+ }
+
+ ret = adapter_start(adapter);
+ if (ret < 0)
+ return ret;
+
+ if (manager_get_default_adapter() < 0)
+ manager_set_default_adapter(id);
+
+ return 0;
+}
+
+int manager_stop_adapter(int id)
+{
+ struct adapter *adapter;
+
+ adapter = manager_find_adapter_by_id(id);
+ if (!adapter) {
+ error("Getting device data failed: hci%d", id);
+ return -EINVAL;
+ }
+
+ return adapter_stop(adapter);
+}
+
+int manager_get_default_adapter()
+{
+ return default_adapter_id;
+}
+
+void manager_set_default_adapter(int id)
+{
+ struct adapter *adapter = manager_find_adapter_by_id(id);
+ const gchar *path = adapter_get_path(adapter);
+
+ default_adapter_id = id;
+
+ g_dbus_emit_signal(connection, "/",
+ MANAGER_INTERFACE,
+ "DefaultAdapterChanged",
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID);
+}
diff --git a/src/manager.h b/src/manager.h
new file mode 100644
index 00000000..99bc26c3
--- /dev/null
+++ b/src/manager.h
@@ -0,0 +1,41 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 MANAGER_INTERFACE "org.bluez.Manager"
+
+dbus_bool_t manager_init(DBusConnection *conn, const char *path);
+void manager_cleanup(DBusConnection *conn, const char *path);
+
+struct adapter *manager_find_adapter(const bdaddr_t *sba);
+struct adapter *manager_find_adapter_by_path(const char *path);
+struct adapter *manager_find_adapter_by_id(int id);
+int manager_register_adapter(int id);
+int manager_unregister_adapter(int id);
+int manager_start_adapter(int id);
+int manager_stop_adapter(int id);
+int manager_get_default_adapter();
+void manager_set_default_adapter(int id);
+int manager_update_adapter(uint16_t id);
+int manager_get_adapter_class(uint16_t dev_id, uint8_t *cls);
+int manager_set_adapter_class(uint16_t dev_id, uint8_t *cls);
diff --git a/src/parser.y b/src/parser.y
new file mode 100644
index 00000000..c8b9a12d
--- /dev/null
+++ b/src/parser.y
@@ -0,0 +1,360 @@
+%{
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2000-2001 Qualcomm Incorporated
+ * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
+ * Copyright (C) 2002-2008 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 <string.h>
+#include <stdarg.h>
+
+#include <sys/socket.h>
+#include <asm/types.h>
+
+#include <glib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include "hcid.h"
+#include "kword.h"
+
+int cfg_error(const char *fmt, ...);
+
+int yyparse(void);
+int yylex(void);
+int yyerror(char *s);
+
+void yylex_destroy(void);
+
+%}
+
+%union {
+ char *str;
+ long num;
+}
+
+%token K_OPTIONS K_DEVICE
+%token K_AUTOINIT K_SECURITY K_PAIRING K_OFFMODE K_DEVICEID
+%token K_PTYPE K_NAME K_CLASS K_VOICE K_PAGETO K_LM K_LP K_ISCAN K_PSCAN K_DISCOVTO
+%token K_PASSKEY
+%token K_YES K_NO
+
+%token <str> WORD PATH STRING LIST HCI BDADDR ID
+%token <num> NUM
+
+%type <num> bool pkt_type link_mode link_policy sec_mode pair_mode off_mode
+%type <str> dev_name dev_id hci bdaddr
+
+%%
+config: statement | config statement;
+statement:
+ K_OPTIONS hcid_options
+
+ | device device_options
+
+ | WORD {
+ cfg_error("Invalid statement '%s'", $1);
+ }
+
+ | error {
+ yyclearin; yyerrok;
+ }
+ ;
+
+device:
+ K_DEVICE {
+ parser_device = &default_device;
+ }
+
+ | K_DEVICE hci {
+ parser_device = alloc_device_opts($2);
+ }
+
+ | K_DEVICE bdaddr {
+ parser_device = alloc_device_opts($2);
+ }
+ ;
+
+hcid_options: '{' hcid_opts '}';
+hcid_opts: | hcid_opt ';' | error ';' | hcid_opts hcid_opt ';';
+hcid_opt:
+ K_AUTOINIT bool {
+ hcid.auto_init = $2;
+ }
+
+ | K_SECURITY sec_mode {
+ hcid.security = $2;
+ }
+
+ | K_PAIRING pair_mode {
+ hcid.pairing = $2;
+ }
+
+ | K_OFFMODE off_mode {
+ hcid.offmode = $2;
+ }
+
+ | K_DEVICEID dev_id {
+ strncpy((char *) hcid.deviceid, $2, 15);
+ }
+
+ | K_PASSKEY STRING {
+ strncpy((char *) hcid.pin_code, $2, 16);
+ hcid.pin_len = strlen($2);
+ if (hcid.pin_len > 16)
+ hcid.pin_len = 16;
+ }
+
+
+ | WORD {
+ cfg_error("Unknown option '%s'", $1);
+ }
+ ;
+
+sec_mode:
+ WORD {
+ int opt = find_keyword(sec_param, $1);
+ if (opt < 0) {
+ cfg_error("Unknown security mode '%s'", $1);
+ $$ = 0;
+ } else
+ $$ = opt;
+ }
+
+ | K_NO {
+ $$ = HCID_SEC_NONE;
+ }
+ ;
+
+pair_mode:
+ WORD {
+ int opt = find_keyword(pair_param, $1);
+ if (opt < 0) {
+ cfg_error("Unknown pairing mode '%s'", $1);
+ $$ = 0;
+ } else
+ $$ = opt;
+ }
+ ;
+
+off_mode:
+ WORD {
+ int opt = find_keyword(off_param, $1);
+ if (opt < 0) {
+ cfg_error("Unknown off mode '%s'", $1);
+ $$ = 0;
+ } else
+ $$ = opt;
+ }
+ ;
+
+dev_id:
+ ID {
+ }
+ ;
+
+device_options: '{' device_opts '}';
+device_opts: | device_opt ';' | error ';' | device_opts device_opt ';';
+device_opt:
+ K_PTYPE pkt_type {
+ parser_device->flags |= (1 << HCID_SET_PTYPE);
+ parser_device->pkt_type = $2;
+ }
+
+ | K_LM link_mode {
+ parser_device->flags |= (1 << HCID_SET_LM);
+ parser_device->link_mode = $2;
+ }
+
+ | K_LP link_policy {
+ parser_device->flags |= (1 << HCID_SET_LP);
+ parser_device->link_policy = $2;
+ }
+
+ | K_NAME dev_name {
+ if (parser_device->name)
+ g_free(parser_device->name);
+ parser_device->flags |= (1 << HCID_SET_NAME);
+ parser_device->name = g_strdup($2);
+ }
+
+ | K_CLASS NUM {
+ parser_device->flags |= (1 << HCID_SET_CLASS);
+ parser_device->class = $2;
+ }
+
+ | K_VOICE NUM {
+ parser_device->flags |= (1 << HCID_SET_VOICE);
+ parser_device->voice = $2;
+ }
+
+ | K_PAGETO NUM {
+ parser_device->flags |= (1 << HCID_SET_PAGETO);
+ parser_device->pageto = $2;
+ }
+
+ | K_DISCOVTO NUM {
+ parser_device->flags |= (1 << HCID_SET_DISCOVTO);
+ parser_device->discovto = $2;
+ }
+
+ | K_ISCAN bool {
+ if ($2)
+ parser_device->scan |= SCAN_INQUIRY;
+ else
+ parser_device->scan &= ~SCAN_INQUIRY;
+ }
+
+ | K_PSCAN bool {
+ if ($2)
+ parser_device->scan |= SCAN_PAGE;
+ else
+ parser_device->scan &= ~SCAN_PAGE;
+ }
+
+ | WORD {
+ cfg_error("Unknown option '%s'",$1);
+ YYABORT;
+ }
+ ;
+
+dev_name:
+ WORD {
+ $$ = $1;
+ }
+
+ | STRING {
+ $$ = $1;
+ }
+ ;
+
+hci:
+ HCI {
+ $$ = $1;
+ }
+ ;
+
+bdaddr:
+ BDADDR {
+ $$ = $1;
+ }
+ ;
+
+pkt_type:
+ WORD {
+ unsigned int opt;
+ if (!hci_strtoptype($1, &opt))
+ cfg_error("Unknown packet type '%s'", $1);
+ $$ = opt;
+ }
+
+ | LIST {
+ unsigned int opt;
+ if (!hci_strtoptype($1, &opt))
+ cfg_error("Unknown packet type '%s'", $1);
+ $$ = opt;
+ }
+ ;
+
+link_mode:
+ WORD {
+ unsigned int opt;
+ if (!hci_strtolm($1, &opt))
+ cfg_error("Unknown link mode '%s'", $1);
+ $$ = opt;
+ }
+
+ | LIST {
+ unsigned int opt;
+ if (!hci_strtolm($1, &opt))
+ cfg_error("Unknown link mode '%s'", $1);
+ $$ = opt;
+ }
+ ;
+
+link_policy:
+ WORD {
+ unsigned int opt;
+ if (!hci_strtolp($1, &opt))
+ cfg_error("Unknown link policy '%s'", $1);
+ $$ = opt;
+ }
+
+ | LIST {
+ unsigned int opt;
+ if (!hci_strtolp($1, &opt))
+ cfg_error("Unknown link policy '%s'", $1);
+ $$ = opt;
+ }
+ ;
+
+bool: K_YES { $$ = 1; } | K_NO { $$ = 0; };
+
+%%
+
+int yyerror(char *s)
+{
+ error("%s line %d", s, lineno);
+ return 0;
+}
+
+int cfg_error(const char *fmt, ...)
+{
+ char buf[255];
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(buf,sizeof(buf),fmt,ap);
+ va_end(ap);
+
+ yyerror(buf);
+ return 0;
+}
+
+/*
+ * Read config file.
+ */
+int read_config(char *file)
+{
+ extern FILE *yyin;
+
+ if (!(yyin = fopen(file, "r"))) {
+ error("Can't open config file %s", file);
+ return -1;
+ }
+
+ lineno = 1;
+ yyparse();
+
+ fclose(yyin);
+
+ return 0;
+}
diff --git a/src/plugin.c b/src/plugin.c
new file mode 100644
index 00000000..331a832f
--- /dev/null
+++ b/src/plugin.c
@@ -0,0 +1,190 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 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 <glib.h>
+#include <gmodule.h>
+#include <string.h>
+
+#include <sys/stat.h>
+#include <errno.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include "logging.h"
+
+#include "plugin.h"
+
+static GSList *plugins = NULL;
+
+struct bluetooth_plugin {
+ GModule *module;
+ struct bluetooth_plugin_desc *desc;
+};
+
+static gboolean add_plugin(GModule *module, struct bluetooth_plugin_desc *desc)
+{
+ struct bluetooth_plugin *plugin;
+
+ if (desc->init() < 0)
+ return FALSE;
+
+ plugin = g_try_new0(struct bluetooth_plugin, 1);
+ if (plugin == NULL)
+ return FALSE;
+
+ plugin->module = module;
+ plugin->desc = desc;
+
+ plugins = g_slist_append(plugins, plugin);
+
+ return TRUE;
+}
+
+static gboolean is_disabled(const char *name, char **list)
+{
+ int i;
+
+ for (i = 0; list[i] != NULL; i++) {
+ char *str;
+ gboolean equal;
+
+ str = g_strdup_printf("lib%s.so", list[i]);
+
+ equal = g_str_equal(str, name);
+
+ g_free(str);
+
+ if (equal)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean plugin_init(GKeyFile *config)
+{
+ GDir *dir;
+ const gchar *file;
+ gchar **disabled;
+
+ if (strlen(PLUGINDIR) == 0)
+ return FALSE;
+
+ if (config)
+ disabled = g_key_file_get_string_list(config, "General",
+ "DisablePlugins",
+ NULL, NULL);
+ else
+ disabled = NULL;
+
+ debug("Loading plugins %s", PLUGINDIR);
+
+ dir = g_dir_open(PLUGINDIR, 0, NULL);
+ if (!dir) {
+ g_strfreev(disabled);
+ return FALSE;
+ }
+
+ while ((file = g_dir_read_name(dir)) != NULL) {
+ GModule *module;
+ struct bluetooth_plugin_desc *desc;
+ gchar *filename;
+ struct stat st;
+
+ if (g_str_has_prefix(file, "lib") == TRUE ||
+ g_str_has_suffix(file, ".so") == FALSE)
+ continue;
+
+ if (disabled && is_disabled(file, disabled))
+ continue;
+
+ filename = g_build_filename(PLUGINDIR, file, NULL);
+
+ if (stat(filename, &st) < 0) {
+ error("Can't load plugin %s: %s (%d)", filename,
+ strerror(errno), errno);
+ g_free(filename);
+ continue;
+ }
+
+ module = g_module_open(filename, G_MODULE_BIND_LOCAL);
+ if (module == NULL) {
+ error("Can't load plugin: %s", g_module_error());
+ g_free(filename);
+ continue;
+ }
+
+ g_free(filename);
+
+ debug("%s", g_module_name(module));
+
+ if (g_module_symbol(module, "bluetooth_plugin_desc",
+ (gpointer) &desc) == FALSE) {
+ error("Can't load plugin description");
+ g_module_close(module);
+ continue;
+ }
+
+ if (desc == NULL || desc->init == NULL) {
+ g_module_close(module);
+ continue;
+ }
+
+ if (add_plugin(module, desc) == FALSE) {
+ error("Can't init plugin %s", g_module_name(module));
+ g_module_close(module);
+ }
+ }
+
+ g_dir_close(dir);
+
+ g_strfreev(disabled);
+
+ return TRUE;
+}
+
+void plugin_cleanup(void)
+{
+ GSList *list;
+
+ debug("Cleanup plugins");
+
+ for (list = plugins; list; list = list->next) {
+ struct bluetooth_plugin *plugin = list->data;
+
+ debug("%s", g_module_name(plugin->module));
+
+ if (plugin->desc->exit)
+ plugin->desc->exit();
+
+ g_module_close(plugin->module);
+
+ g_free(plugin);
+ }
+
+ g_slist_free(plugins);
+}
diff --git a/src/plugin.h b/src/plugin.h
new file mode 100644
index 00000000..9248aab6
--- /dev/null
+++ b/src/plugin.h
@@ -0,0 +1,33 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 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 bluetooth_plugin_desc {
+ const char *name;
+ int (*init) (void);
+ void (*exit) (void);
+};
+
+#define BLUETOOTH_PLUGIN_DEFINE(name,init,exit) \
+ struct bluetooth_plugin_desc bluetooth_plugin_desc = { \
+ name, init, exit \
+ };
diff --git a/src/sdpd-database.c b/src/sdpd-database.c
new file mode 100644
index 00000000..6cc34bd3
--- /dev/null
+++ b/src/sdpd-database.c
@@ -0,0 +1,304 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2001-2002 Nokia Corporation
+ * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
+ * Copyright (C) 2002-2008 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 "logging.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
+ */
+static int record_sort(const void *r1, const void *r2)
+{
+ const sdp_record_t *rec1 = (const sdp_record_t *) r1;
+ const sdp_record_t *rec2 = (const sdp_record_t *) 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 = (const sdp_access_t *) r1;
+ const sdp_access_t *rec2 = (const sdp_access_t *) 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 = (sdp_indexed_t *) 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 = (sdp_indexed_t *) 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 = (const sdp_indexed_t *) i1;
+ const sdp_indexed_t *s2 = (const sdp_indexed_t *) 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(bdaddr_t *device, sdp_record_t *rec)
+{
+ sdp_access_t *dev;
+
+ debug("Adding rec : 0x%lx", (long) rec);
+ debug("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);
+}
+
+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;
+ }
+
+ debug("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;
+ }
+
+ debug("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) {
+ debug("Couldn't find record for : 0x%x", handle);
+ return 0;
+ }
+
+ return (sdp_record_t *) 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 = (sdp_record_t *) p->data;
+ if (r)
+ service_db = sdp_list_remove(service_db, r);
+
+ p = access_locate(handle);
+ if (p) {
+ a = (sdp_access_t *) p->data;
+ if (a) {
+ 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 = (sdp_access_t *) 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;
+}
diff --git a/src/sdpd-request.c b/src/sdpd-request.c
new file mode 100644
index 00000000..ece8cd54
--- /dev/null
+++ b/src/sdpd-request.c
@@ -0,0 +1,960 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2001-2002 Nokia Corporation
+ * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
+ * Copyright (C) 2002-2008 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 "logging.h"
+
+#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
+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_ANY 0xff
+
+/*
+ * 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;
+ int bufsize;
+
+ scanned = sdp_extract_seqtype_safe(buf, len, &seqType, &data_size);
+
+ debug("Seq type : %d", seqType);
+ if (!scanned || (seqType != SDP_SEQ8 && seqType != SDP_SEQ16)) {
+ error("Unknown seq type");
+ return -1;
+ }
+ p = buf + scanned;
+ bufsize = len - scanned;
+
+ debug("Data size : %d", data_size);
+
+ for (;;) {
+ char *pElem = NULL;
+ int localSeqLength = 0;
+
+ if (bufsize < sizeof(uint8_t)) {
+ debug("->Unexpected end of buffer");
+ return -1;
+ }
+
+ dataType = *(uint8_t *)p;
+ debug("Data type: 0x%02x", dataType);
+
+ if (expectedType == SDP_TYPE_UUID) {
+ if (dataType != SDP_UUID16 && dataType != SDP_UUID32 && dataType != SDP_UUID128) {
+ debug("->Unexpected Data type (expected UUID_ANY)");
+ return -1;
+ }
+ } else if (expectedType != SDP_TYPE_ANY && dataType != expectedType) {
+ debug("->Unexpected Data type (expected 0x%02x)", expectedType);
+ return -1;
+ }
+
+ switch (dataType) {
+ case SDP_UINT16:
+ p += sizeof(uint8_t);
+ seqlen += sizeof(uint8_t);
+ bufsize -= sizeof(uint8_t);
+ if (bufsize < sizeof(uint16_t)) {
+ debug("->Unexpected end of buffer");
+ return -1;
+ }
+
+ 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)) {
+ debug("->Unexpected end of buffer");
+ return -1;
+ }
+
+ 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_safe(p, bufsize, (uuid_t *) pElem, &localSeqLength);
+ if (status == 0) {
+ seqlen += localSeqLength;
+ p += localSeqLength;
+ bufsize -= localSeqLength;
+ }
+ break;
+ default:
+ return -1;
+ }
+ if (status == 0) {
+ pSeq = sdp_list_append(pSeq, pElem);
+ numberOfElements++;
+ debug("No of elements : %d", numberOfElements);
+
+ if (seqlen == data_size)
+ break;
+ else if (seqlen > data_size || seqlen > len)
+ return -1;
+ } else
+ free(pElem);
+ }
+ *svcReqSeq = pSeq;
+ scanned += seqlen;
+ *pDataType = dataType;
+ return scanned;
+}
+
+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) {
+ debug("Non null sdp_cstate_t id : 0x%lx", 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 sdp_cont_state_t *sdp_cstate_get(uint8_t *buffer)
+{
+ uint8_t *pdata = buffer;
+ uint8_t cStateSize = *(uint8_t *)pdata;
+
+ /*
+ * Check if continuation state exists, if yes attempt
+ * to get response remainder from cache, else send error
+ */
+ debug("Continuation State size : %d", cStateSize);
+
+ pdata += sizeof(uint8_t);
+ if (cStateSize != 0) {
+ sdp_cont_state_t *cstate = malloc(sizeof(sdp_cont_state_t));
+ if (!cstate)
+ return NULL;
+ memcpy(cstate, (sdp_cont_state_t *)pdata, sizeof(sdp_cont_state_t));
+ debug("Cstate TS : 0x%lx", cstate->timestamp);
+ debug("Bytes sent : %d", cstate->cStateValue.maxBytesSent);
+ return cstate;
+ }
+ return NULL;
+}
+
+/*
+ * 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);
+
+ scanned = extract_des(pdata, req->len - sizeof(sdp_pdu_hdr_t),
+ &pattern, &dtd, SDP_TYPE_UUID);
+
+ if (scanned == -1) {
+ status = SDP_INVALID_SYNTAX;
+ goto done;
+ }
+ pdata += 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;
+ }
+
+ expected = ntohs(bt_get_unaligned((uint16_t *)pdata));
+
+ debug("Expected count: %d", expected);
+ debug("Bytes scanned : %d", scanned);
+
+ pdata += sizeof(uint16_t);
+
+ /*
+ * Check if continuation state exists, if yes attempt
+ * to get rsp remainder from cache, else send error
+ */
+ cstate = sdp_cstate_get(pdata);
+
+ 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 = (sdp_record_t *) list->data;
+
+ debug("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);
+ }
+ }
+
+ debug("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;
+
+ debug("Setting non-NULL sdp_cstate_t");
+
+ if (cstate)
+ memcpy((char *)&newState, cstate, sizeof(sdp_cont_state_t));
+ else {
+ memset((char *)&newState, 0, sizeof(sdp_cont_state_t));
+ newState.timestamp = cStateId;
+ }
+ newState.cStateValue.lastIndexSent = i;
+ sdp_set_cstate_pdu(buf, &newState);
+ }
+ }
+
+done:
+ if (cstate)
+ 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, uint8_t dtd, sdp_buf_t *buf)
+{
+ if (!rec)
+ return SDP_INVALID_RECORD_HANDLE;
+
+ if (seq)
+ debug("Entries in attr seq : %d", sdp_list_len(seq));
+ else
+ debug("NULL attribute descriptor");
+
+ debug("AttrDataType : %d", dtd);
+
+ if (seq == NULL) {
+ debug("Attribute sequence is NULL");
+ return 0;
+ }
+ if (dtd == SDP_UINT16)
+ for (; seq; seq = seq->next) {
+ uint16_t attr = bt_get_unaligned((uint16_t *)seq->data);
+ sdp_data_t *a = (sdp_data_t *)sdp_data_get(rec, attr);
+ if (a)
+ sdp_append_to_pdu(buf, a);
+ }
+ else if (dtd == SDP_UINT32) {
+ sdp_buf_t pdu;
+ sdp_gen_record_pdu(rec, &pdu);
+ for (; seq; seq = seq->next) {
+ uint32_t range = bt_get_unaligned((uint32_t *)seq->data);
+ uint16_t attr;
+ uint16_t low = (0xffff0000 & range) >> 16;
+ uint16_t high = 0x0000ffff & range;
+ sdp_data_t *data;
+
+ debug("attr range : 0x%x", range);
+ debug("Low id : 0x%x", low);
+ debug("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);
+ }
+ free(pdu.data);
+ } else {
+ error("Unexpected data type : 0x%x", dtd);
+ error("Expect uint16_t or uint32_t");
+ return SDP_INVALID_SYNTAX;
+ }
+ 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;
+ int max_rsp_size;
+ int status = 0, plen, mlen;
+ uint8_t *pdata = req->buf + sizeof(sdp_pdu_hdr_t);
+ uint32_t handle = ntohl(bt_get_unaligned((uint32_t *)pdata));
+
+ pdata += sizeof(uint32_t);
+ max_rsp_size = ntohs(bt_get_unaligned((uint16_t *)pdata));
+ pdata += sizeof(uint16_t);
+
+ /* extract the attribute list */
+ scanned = extract_des(pdata, req->len - sizeof(sdp_pdu_hdr_t),
+ &seq, &dtd, SDP_TYPE_ANY);
+ if (scanned == -1) {
+ status = SDP_INVALID_SYNTAX;
+ goto done;
+ }
+ pdata += 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_SYNTAX;
+ goto done;
+ }
+
+ /*
+ * if continuation state exists, attempt
+ * to get rsp remainder from cache, else send error
+ */
+ cstate = sdp_cstate_get(pdata);
+
+ debug("SvcRecHandle : 0x%x", handle);
+ debug("max_rsp_size : %d", max_rsp_size);
+
+ /*
+ * 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);
+
+ debug("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;
+
+ debug("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, dtd, 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
+ */
+ debug("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:
+ if (cstate)
+ 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;
+ int scanned, max, 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;
+
+ tmpbuf.data = NULL;
+ pdata = req->buf + sizeof(sdp_pdu_hdr_t);
+ scanned = extract_des(pdata, req->len - sizeof(sdp_pdu_hdr_t),
+ &pattern, &dtd, SDP_TYPE_UUID);
+ if (scanned == -1) {
+ status = SDP_INVALID_SYNTAX;
+ goto done;
+ }
+ totscanned = scanned;
+
+ debug("Bytes scanned: %d", scanned);
+
+ pdata += scanned;
+ max = ntohs(bt_get_unaligned((uint16_t *)pdata));
+ pdata += sizeof(uint16_t);
+
+ debug("Max Attr expected: %d", max);
+
+ /* extract the attribute list */
+ scanned = extract_des(pdata, req->len - sizeof(sdp_pdu_hdr_t),
+ &seq, &dtd, SDP_TYPE_ANY);
+ if (scanned == -1) {
+ status = SDP_INVALID_SYNTAX;
+ goto done;
+ }
+ pdata += 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_SYNTAX;
+ goto done;
+ }
+
+ /*
+ * if continuation state exists attempt
+ * to get rsp remainder from cache, else send error
+ */
+ cstate = sdp_cstate_get(pdata); // continuation information
+
+ 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 = (sdp_record_t *) p->data;
+ if (sdp_match_uuid(pattern, rec->pattern) > 0 &&
+ sdp_check_access(rec->handle, &req->device)) {
+ rsp_count++;
+ status = extract_attrs(rec, seq, dtd, &tmpbuf);
+
+ debug("Response count : %d", rsp_count);
+ debug("Local PDU size : %d", tmpbuf.data_size);
+ if (status) {
+ debug("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;
+ }
+ debug("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;
+ debug("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:
+ if (cstate)
+ free(cstate);
+ if (tmpbuf.data)
+ 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:
+ debug("Got a svc srch req");
+ status = service_search_req(req, &rsp);
+ rsphdr->pdu_id = SDP_SVC_SEARCH_RSP;
+ break;
+ case SDP_SVC_ATTR_REQ:
+ debug("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:
+ debug("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:
+ debug("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:
+ debug("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:
+ debug("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);
+ }
+
+ debug("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);
+
+ debug("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)
+ return;
+
+ if (sa.l2_family == AF_BLUETOOTH) {
+ struct l2cap_options lo;
+ memset(&lo, 0, sizeof(lo));
+ size = sizeof(lo);
+ getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &lo, &size);
+ bacpy(&req.bdaddr, &sa.l2_bdaddr);
+ req.mtu = lo.omtu;
+ req.local = 0;
+ memset(&sa, 0, sizeof(sa));
+ size = sizeof(sa);
+ getsockname(sk, (struct sockaddr *) &sa, &size);
+ 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 00000000..1524d1c0
--- /dev/null
+++ b/src/sdpd-server.c
@@ -0,0 +1,280 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2001-2002 Nokia Corporation
+ * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
+ * Copyright (C) 2002-2008 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 "logging.h"
+#include "sdpd.h"
+
+static GIOChannel *l2cap_io = NULL, *unix_io = NULL;
+
+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;
+ }
+ }
+
+ listen(l2cap_sock, 5);
+
+ 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;
+ }
+
+ listen(unix_sock, 5);
+
+ 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);
+ 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)) {
+ g_io_channel_unref(chan);
+ 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;
+
+ 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);
+ }
+ }
+
+ l2cap_io = g_io_channel_unix_new(l2cap_sock);
+ g_io_channel_set_close_on_unref(l2cap_io, TRUE);
+
+ g_io_add_watch(l2cap_io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ io_accept_event, &l2cap_sock);
+
+ if (compat && unix_sock > fileno(stderr)) {
+ unix_io = g_io_channel_unix_new(unix_sock);
+ g_io_channel_set_close_on_unref(unix_io, TRUE);
+
+ g_io_add_watch(unix_io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ io_accept_event, &unix_sock);
+ }
+
+ return 0;
+}
+
+void stop_sdp_server(void)
+{
+ info("Stopping SDP server");
+
+ sdp_svcdb_reset();
+
+ if (unix_io)
+ g_io_channel_unref(unix_io);
+
+ if (l2cap_io)
+ g_io_channel_unref(l2cap_io);
+}
diff --git a/src/sdpd-service.c b/src/sdpd-service.c
new file mode 100644
index 00000000..09459f43
--- /dev/null
+++ b/src/sdpd-service.c
@@ -0,0 +1,677 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2001-2002 Nokia Corporation
+ * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
+ * Copyright (C) 2002-2008 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/time.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <netinet/in.h>
+
+#include "sdpd.h"
+#include "logging.h"
+
+static sdp_record_t *server = NULL;
+
+static uint8_t service_classes = 0x00;
+static service_classes_callback_t service_classes_callback = NULL;
+
+static uint16_t did_vendor = 0x0000;
+static uint16_t did_product = 0x0000;
+static uint16_t did_version = 0x0000;
+
+/*
+ * 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);
+}
+
+static void update_svclass_list(void)
+{
+ sdp_list_t *list = sdp_get_record_list();
+ uint8_t val = 0;
+
+ for (; list; list = list->next) {
+ sdp_record_t *rec = (sdp_record_t *) list->data;
+
+ if (rec->svclass.type != SDP_UUID16)
+ continue;
+
+ switch (rec->svclass.value.uuid16) {
+ case DIALUP_NET_SVCLASS_ID:
+ case CIP_SVCLASS_ID:
+ val |= 0x42; /* Telephony & Networking */
+ break;
+ 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:
+ val |= 0x10; /* Object Transfer */
+ break;
+ case HEADSET_SVCLASS_ID:
+ case HANDSFREE_SVCLASS_ID:
+ val |= 0x20; /* Audio */
+ break;
+ case CORDLESS_TELEPHONY_SVCLASS_ID:
+ case INTERCOM_SVCLASS_ID:
+ case FAX_SVCLASS_ID:
+ case SAP_SVCLASS_ID:
+ val |= 0x40; /* Telephony */
+ break;
+ case AUDIO_SOURCE_SVCLASS_ID:
+ case VIDEO_SOURCE_SVCLASS_ID:
+ val |= 0x08; /* Capturing */
+ break;
+ case AUDIO_SINK_SVCLASS_ID:
+ case VIDEO_SINK_SVCLASS_ID:
+ val |= 0x04; /* Rendering */
+ break;
+ case PANU_SVCLASS_ID:
+ case NAP_SVCLASS_ID:
+ case GN_SVCLASS_ID:
+ val |= 0x02; /* Networking */
+ break;
+ }
+ }
+
+ debug("Service classes 0x%02x", val);
+
+ service_classes = val;
+
+ if (service_classes_callback)
+ service_classes_callback(BDADDR_ANY, val);
+}
+
+uint8_t get_service_classes(const bdaddr_t *bdaddr)
+{
+ return service_classes;
+}
+
+void set_service_classes_callback(service_classes_callback_t callback)
+{
+ service_classes_callback = callback;
+}
+
+void create_ext_inquiry_response(const char *name, uint8_t *data)
+{
+ sdp_list_t *list = sdp_get_record_list();
+ uint8_t *ptr = data;
+ uint16_t uuid[24];
+ int i, index = 0;
+
+ if (name) {
+ int len = strlen(name);
+
+ if (len > 48) {
+ len = 48;
+ ptr[1] = 0x08;
+ } else
+ ptr[1] = 0x09;
+
+ ptr[0] = len + 1;
+
+ memcpy(ptr + 2, name, len);
+
+ ptr += len + 2;
+ }
+
+ if (did_vendor != 0x0000) {
+ uint16_t source = 0x0002;
+ *ptr++ = 9;
+ *ptr++ = 11;
+ *ptr++ = (source & 0x00ff);
+ *ptr++ = (source & 0xff00) >> 8;
+ *ptr++ = (did_vendor & 0x00ff);
+ *ptr++ = (did_vendor & 0xff00) >> 8;
+ *ptr++ = (did_product & 0x00ff);
+ *ptr++ = (did_product & 0xff00) >> 8;
+ *ptr++ = (did_version & 0x00ff);
+ *ptr++ = (did_version & 0xff00) >> 8;
+ }
+
+ ptr[1] = 0x03;
+
+ for (; list; list = list->next) {
+ sdp_record_t *rec = (sdp_record_t *) list->data;
+
+ if (rec->svclass.type != SDP_UUID16)
+ continue;
+
+ if (rec->svclass.value.uuid16 < 0x1100)
+ continue;
+
+ if (index > 23) {
+ ptr[1] = 0x02;
+ break;
+ }
+
+ for (i = 0; i < index; i++)
+ if (uuid[i] == rec->svclass.value.uuid16)
+ break;
+
+ if (i == index - 1)
+ continue;
+
+ uuid[index++] = rec->svclass.value.uuid16;
+ }
+
+ if (index > 0) {
+ ptr[0] = (index * 2) + 1;
+ ptr += 2;
+
+ for (i = 0; i < index; i++) {
+ *ptr++ = (uuid[i] & 0x00ff);
+ *ptr++ = (uuid[i] & 0xff00) >> 8;
+ }
+ }
+}
+
+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 = (void **)malloc(sdpServerVnumEntries * sizeof(void *));
+ versionDTDs = (void **)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();
+ update_svclass_list();
+}
+
+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);
+
+ did_vendor = vendor;
+ did_product = product;
+ did_version = 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();
+ update_svclass_list();
+}
+
+int add_record_to_server(bdaddr_t *src, sdp_record_t *rec)
+{
+ sdp_data_t *data;
+
+ if (rec->handle == 0xffffffff) {
+ rec->handle = sdp_next_handle();
+ if (rec->handle < 0x10000)
+ return -1;
+ } else {
+ if (sdp_record_find(rec->handle))
+ return -1;
+ }
+
+ debug("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);
+ }
+
+ update_db_timestamp();
+ update_svclass_list();
+
+ return 0;
+}
+
+int remove_record_from_server(uint32_t handle)
+{
+ sdp_record_t *rec;
+
+ debug("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();
+ update_svclass_list();
+ }
+
+ sdp_record_free(rec);
+
+ return 0;
+}
+
+// FIXME: refactor for server-side
+static sdp_record_t *extract_pdu_server(bdaddr_t *device, uint8_t *p, 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_safe(p, bufsize, &dtd, &seqlen);
+ p += *scanned;
+ bufsize -= *scanned;
+
+ if (bufsize < sizeof(uint8_t) + sizeof(uint8_t)) {
+ debug("Unexpected end of packet");
+ return NULL;
+ }
+
+ lookAheadAttrId = ntohs(bt_get_unaligned((uint16_t *) (p + sizeof(uint8_t))));
+
+ debug("Look ahead attr id : %d", lookAheadAttrId);
+
+ if (lookAheadAttrId == SDP_ATTR_RECORD_HANDLE) {
+ if (bufsize < (sizeof(uint8_t) * 2) + sizeof(uint16_t) + sizeof(uint32_t)) {
+ debug("Unexpected end of packet");
+ return NULL;
+ }
+ handle = ntohl(bt_get_unaligned((uint32_t *) (p +
+ sizeof(uint8_t) + sizeof(uint16_t) +
+ sizeof(uint8_t))));
+ debug("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)) {
+ debug("Unexpected end of packet: Terminating extraction of attributes");
+ break;
+ }
+
+ debug("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);
+
+ debug("DTD of attrId : %d Attr id : 0x%x", dtd, attrId);
+
+ pAttr = sdp_extract_attr_safe(p + attrSize, bufsize - attrSize,
+ &attrValueLength, rec);
+
+ debug("Attr id : 0x%x attrValueLength : %d", attrId, attrValueLength);
+
+ attrSize += attrValueLength;
+ if (pAttr == NULL) {
+ debug("Terminating extraction of attributes");
+ break;
+ }
+ localExtractedLength += attrSize;
+ p += attrSize;
+ bufsize -= attrSize;
+ sdp_attr_replace(rec, attrId, pAttr);
+ extractStatus = 0;
+ debug("Extract PDU, seqLength: %d localExtractedLength: %d",
+ seqlen, localExtractedLength);
+ }
+
+ if (extractStatus == 0) {
+ debug("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();
+ update_svclass_list();
+
+ /* 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;
+ 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));
+
+ debug("Svc Rec Handle: 0x%x", handle);
+
+ p += sizeof(uint32_t);
+ bufsize -= sizeof(uint32_t);
+
+ orec = sdp_record_find(handle);
+
+ debug("SvcRecOld: %p", orec);
+
+ if (orec) {
+ sdp_record_t *nrec = extract_pdu_server(BDADDR_ANY, p, bufsize, handle, &scanned);
+ if (nrec && handle == nrec->handle) {
+ update_db_timestamp();
+ update_svclass_list();
+ } else {
+ debug("SvcRecHandle : 0x%x", handle);
+ debug("SvcRecHandleNew : 0x%x", nrec->handle);
+ debug("SvcRecNew : %p", nrec);
+ debug("SvcRecOld : %p", orec);
+ debug("Failure to update, restore old value");
+
+ if (nrec)
+ sdp_record_free(nrec);
+ status = SDP_INVALID_SYNTAX;
+ }
+ } else
+ status = SDP_INVALID_RECORD_HANDLE;
+
+ 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();
+ update_svclass_list();
+ }
+ } else {
+ status = SDP_INVALID_RECORD_HANDLE;
+ debug("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 00000000..332b434d
--- /dev/null
+++ b/src/sdpd.h
@@ -0,0 +1,93 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2001-2002 Nokia Corporation
+ * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
+ * Copyright (C) 2002-2008 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>
+
+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);
+
+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))
+
+sdp_buf_t *sdp_get_cached_rsp(sdp_cont_state_t *cstate);
+void sdp_cstate_cache_init(void);
+void sdp_cstate_clean_buf(void);
+
+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(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(bdaddr_t *src, sdp_record_t *rec);
+int remove_record_from_server(uint32_t handle);
+
+typedef void (*service_classes_callback_t) (const bdaddr_t *bdaddr, uint8_t value);
+
+uint8_t get_service_classes(const bdaddr_t *bdaddr);
+void set_service_classes_callback(service_classes_callback_t callback);
+void create_ext_inquiry_response(const char *name, uint8_t *data);
diff --git a/src/security.c b/src/security.c
new file mode 100644
index 00000000..0bfbcbec
--- /dev/null
+++ b/src/security.c
@@ -0,0 +1,1037 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2000-2001 Qualcomm Incorporated
+ * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
+ * Copyright (C) 2002-2008 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 <string.h>
+#include <signal.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+
+#include <glib.h>
+
+#include <dbus/dbus.h>
+
+#include "hcid.h"
+#include "textfile.h"
+#include "adapter.h"
+#include "dbus-hci.h"
+
+struct g_io_info {
+ GIOChannel *channel;
+ int watch_id;
+ int pin_length;
+};
+
+static struct g_io_info io_data[HCI_MAX_DEV];
+
+static int pairing = HCID_PAIRING_MULTI;
+
+static GSList *hci_req_queue = NULL;
+
+struct hci_req_data *hci_req_data_new(int dev_id, const bdaddr_t *dba, uint16_t ogf, uint16_t ocf, int event, const void *cparam, int clen)
+{
+ struct hci_req_data *data;
+
+ data = g_new0(struct hci_req_data, 1);
+
+ data->cparam = g_malloc(clen);
+ memcpy(data->cparam, cparam, clen);
+
+ bacpy(&data->dba, dba);
+
+ data->dev_id = dev_id;
+ data->status = REQ_PENDING;
+ data->ogf = ogf;
+ data->ocf = ocf;
+ data->event = event;
+ data->clen = clen;
+
+ return data;
+}
+
+static int hci_req_find_by_devid(const void *data, const void *user_data)
+{
+ const struct hci_req_data *req = data;
+ const int *dev_id = user_data;
+
+ return (*dev_id - req->dev_id);
+}
+
+static void hci_req_queue_process(int dev_id)
+{
+ int dd, ret_val;
+
+ /* send the next pending cmd */
+ dd = hci_open_dev(dev_id);
+ do {
+ struct hci_req_data *data;
+ GSList *l = g_slist_find_custom(hci_req_queue, &dev_id, hci_req_find_by_devid);
+
+ if (!l)
+ break;
+
+ data = l->data;
+ data->status = REQ_SENT;
+
+ ret_val = hci_send_cmd(dd, data->ogf, data->ocf, data->clen, data->cparam);
+ if (ret_val < 0) {
+ hci_req_queue = g_slist_remove(hci_req_queue, data);
+ g_free(data->cparam);
+ g_free(data);
+ }
+
+ } while(ret_val < 0);
+
+ hci_close_dev(dd);
+}
+
+void hci_req_queue_append(struct hci_req_data *data)
+{
+ GSList *l;
+ struct hci_req_data *match;
+
+
+ hci_req_queue = g_slist_append(hci_req_queue, data);
+
+ l = g_slist_find_custom(hci_req_queue, &data->dev_id, hci_req_find_by_devid);
+ match = l->data;
+
+ if (match->status == REQ_SENT)
+ return;
+
+ hci_req_queue_process(data->dev_id);
+}
+
+void hci_req_queue_remove(int dev_id, bdaddr_t *dba)
+{
+ GSList *cur, *next;
+ struct hci_req_data *req;
+
+ for (cur = hci_req_queue; cur != NULL; cur = next) {
+ req = cur->data;
+ next = cur->next;
+ if ((req->dev_id != dev_id) || (bacmp(&req->dba, dba)))
+ continue;
+
+ hci_req_queue = g_slist_remove(hci_req_queue, req);
+ g_free(req->cparam);
+ g_free(req);
+ }
+}
+
+static void check_pending_hci_req(int dev_id, int event)
+{
+ struct hci_req_data *data;
+ GSList *l;
+
+ if (!hci_req_queue)
+ return;
+
+ /* find the first element(pending)*/
+ l = g_slist_find_custom(hci_req_queue, &dev_id, hci_req_find_by_devid);
+
+ if (!l)
+ return;
+
+ data = l->data;
+
+ /* skip if there is pending confirmation */
+ if (data->status == REQ_SENT) {
+ if (data->event != event)
+ return;
+
+ /* remove the confirmed cmd */
+ hci_req_queue = g_slist_remove(hci_req_queue, data);
+ g_free(data->cparam);
+ g_free(data);
+ }
+
+ hci_req_queue_process(dev_id);
+}
+
+static int get_handle(int dev, bdaddr_t *sba, bdaddr_t *dba, uint16_t *handle)
+{
+ struct hci_conn_list_req *cl;
+ struct hci_conn_info *ci;
+ char addr[18];
+ int i;
+
+ cl = g_malloc0(10 * sizeof(*ci) + sizeof(*cl));
+
+ ba2str(sba, addr);
+ cl->dev_id = hci_devid(addr);
+ cl->conn_num = 10;
+ ci = cl->conn_info;
+
+ if (ioctl(dev, HCIGETCONNLIST, (void *) cl) < 0) {
+ g_free(cl);
+ return -EIO;
+ }
+
+ for (i = 0; i < cl->conn_num; i++, ci++) {
+ if (bacmp(&ci->bdaddr, dba) == 0) {
+ *handle = ci->handle;
+ g_free(cl);
+ return 0;
+ }
+ }
+
+ g_free(cl);
+
+ return -ENOENT;
+}
+
+static inline int get_bdaddr(int dev, bdaddr_t *sba, uint16_t handle, bdaddr_t *dba)
+{
+ struct hci_conn_list_req *cl;
+ struct hci_conn_info *ci;
+ char addr[18];
+ int i;
+
+ cl = g_malloc0(10 * sizeof(*ci) + sizeof(*cl));
+
+ ba2str(sba, addr);
+ cl->dev_id = hci_devid(addr);
+ cl->conn_num = 10;
+ ci = cl->conn_info;
+
+ if (ioctl(dev, HCIGETCONNLIST, (void *) cl) < 0) {
+ g_free(cl);
+ return -EIO;
+ }
+
+ for (i = 0; i < cl->conn_num; i++, ci++)
+ if (ci->handle == handle) {
+ bacpy(dba, &ci->bdaddr);
+ g_free(cl);
+ return 0;
+ }
+
+ g_free(cl);
+
+ return -ENOENT;
+}
+
+static inline 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 inline 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);
+}
+
+/* Link Key handling */
+
+static void link_key_request(int dev, bdaddr_t *sba, bdaddr_t *dba)
+{
+ struct hci_auth_info_req req;
+ unsigned char key[16];
+ char sa[18], da[18];
+ uint8_t type;
+ int err;
+
+ ba2str(sba, sa); ba2str(dba, da);
+ info("link_key_request (sba=%s, dba=%s)", sa, da);
+
+ memset(&req, 0, sizeof(req));
+ bacpy(&req.bdaddr, dba);
+
+ err = ioctl(dev, HCIGETAUTHINFO, (unsigned long) &req);
+ if (err < 0 && errno != EINVAL)
+ debug("HCIGETAUTHINFO failed %s (%d)",
+ strerror(errno), errno);
+ else
+ req.type = 0x00;
+
+ debug("kernel auth requirements = 0x%02x", req.type);
+
+ err = read_link_key(sba, dba, key, &type);
+ if (err < 0) {
+ /* Link key not found */
+ hci_send_cmd(dev, OGF_LINK_CTL, OCF_LINK_KEY_NEG_REPLY, 6, dba);
+ } else {
+ /* Link key found */
+ link_key_reply_cp lr;
+ memcpy(lr.link_key, key, 16);
+ bacpy(&lr.bdaddr, dba);
+
+ debug("stored link key type = 0x%02x", type);
+
+ if ((type == 0x03 || type == 0x04) && (req.type & 0x01))
+ hci_send_cmd(dev, OGF_LINK_CTL,
+ OCF_LINK_KEY_NEG_REPLY, 6, dba);
+ else
+ hci_send_cmd(dev, OGF_LINK_CTL, OCF_LINK_KEY_REPLY,
+ LINK_KEY_REPLY_CP_SIZE, &lr);
+ }
+}
+
+static void link_key_notify(int dev, bdaddr_t *sba, void *ptr)
+{
+ evt_link_key_notify *evt = ptr;
+ bdaddr_t *dba = &evt->bdaddr;
+ char sa[18], da[18];
+ int dev_id, err;
+
+ ba2str(sba, sa); ba2str(dba, da);
+ info("link_key_notify (sba=%s, dba=%s)", sa, da);
+
+ dev_id = hci_devid(sa);
+
+ err = write_link_key(sba, dba, evt->link_key, evt->key_type,
+ io_data[dev_id].pin_length);
+ if (err < 0) {
+ uint16_t handle;
+
+ error("write_link_key: %s (%d)", strerror(-err), -err);
+
+ hcid_dbus_bonding_process_complete(sba, dba, HCI_MEMORY_FULL);
+
+ if (get_handle(dev, sba, dba, &handle) == 0) {
+ disconnect_cp cp;
+
+ memset(&cp, 0, sizeof(cp));
+ cp.handle = htobs(handle);
+ cp.reason = HCI_OE_LOW_RESOURCES;
+
+ hci_send_cmd(dev, OGF_LINK_CTL, OCF_DISCONNECT,
+ DISCONNECT_CP_SIZE, &cp);
+ }
+ } else
+ hcid_dbus_bonding_process_complete(sba, dba, 0);
+
+ io_data[dev_id].pin_length = -1;
+}
+
+static void return_link_keys(int dev, bdaddr_t *sba, void *ptr)
+{
+ evt_return_link_keys *evt = ptr;
+ uint8_t num = evt->num_keys;
+ unsigned char key[16];
+ char sa[18], da[18];
+ bdaddr_t dba;
+ int i;
+
+ ba2str(sba, sa);
+ ptr++;
+
+ for (i = 0; i < num; i++) {
+ bacpy(&dba, ptr); ba2str(&dba, da);
+ memcpy(key, ptr + 6, 16);
+
+ info("return_link_keys (sba=%s, dba=%s)", sa, da);
+
+ ptr += 22;
+ }
+}
+
+/* Simple Pairing handling */
+
+static void user_confirm_request(int dev, bdaddr_t *sba, void *ptr)
+{
+ evt_user_confirm_request *req = ptr;
+
+ if (hcid_dbus_user_confirm(sba, &req->bdaddr,
+ btohl(req->passkey)) < 0)
+ hci_send_cmd(dev, OGF_LINK_CTL,
+ OCF_USER_CONFIRM_NEG_REPLY, 6, ptr);
+}
+
+static void user_passkey_request(int dev, bdaddr_t *sba, void *ptr)
+{
+ evt_user_passkey_request *req = ptr;
+
+ if (hcid_dbus_user_passkey(sba, &req->bdaddr) < 0)
+ hci_send_cmd(dev, OGF_LINK_CTL,
+ OCF_USER_PASSKEY_NEG_REPLY, 6, ptr);
+}
+
+static void user_passkey_notify(int dev, bdaddr_t *sba, void *ptr)
+{
+ evt_user_passkey_notify *req = ptr;
+
+ hcid_dbus_user_notify(sba, &req->bdaddr, btohl(req->passkey));
+}
+
+static void remote_oob_data_request(int dev, bdaddr_t *sba, void *ptr)
+{
+ hci_send_cmd(dev, OGF_LINK_CTL, OCF_REMOTE_OOB_DATA_NEG_REPLY, 6, ptr);
+}
+
+static void io_capa_request(int dev, bdaddr_t *sba, bdaddr_t *dba)
+{
+ char sa[18], da[18];
+ uint8_t cap, auth;
+
+ ba2str(sba, sa); ba2str(dba, da);
+ info("io_capa_request (sba=%s, dba=%s)", sa, da);
+
+ if (hcid_dbus_get_io_cap(sba, dba, &cap, &auth) < 0) {
+ io_capability_neg_reply_cp cp;
+ memset(&cp, 0, sizeof(cp));
+ bacpy(&cp.bdaddr, dba);
+ cp.reason = HCI_PAIRING_NOT_ALLOWED;
+ hci_send_cmd(dev, OGF_LINK_CTL, OCF_IO_CAPABILITY_NEG_REPLY,
+ IO_CAPABILITY_NEG_REPLY_CP_SIZE, &cp);
+ } else {
+ io_capability_reply_cp cp;
+ memset(&cp, 0, sizeof(cp));
+ bacpy(&cp.bdaddr, dba);
+ cp.capability = cap;
+ cp.oob_data = 0x00;
+ cp.authentication = auth;
+ hci_send_cmd(dev, OGF_LINK_CTL, OCF_IO_CAPABILITY_REPLY,
+ IO_CAPABILITY_REPLY_CP_SIZE, &cp);
+ }
+}
+
+static void io_capa_response(int dev, bdaddr_t *sba, void *ptr)
+{
+ evt_io_capability_response *evt = ptr;
+ char sa[18], da[18];
+
+ ba2str(sba, sa); ba2str(&evt->bdaddr, da);
+ info("io_capa_response (sba=%s, dba=%s)", sa, da);
+
+ hcid_dbus_set_io_cap(sba, &evt->bdaddr,
+ evt->capability, evt->authentication);
+}
+
+/* PIN code handling */
+
+void set_pin_length(bdaddr_t *sba, int length)
+{
+ char addr[18];
+ int dev_id;
+
+ ba2str(sba, addr);
+ dev_id = hci_devid(addr);
+
+ io_data[dev_id].pin_length = length;
+}
+
+static void pin_code_request(int dev, bdaddr_t *sba, bdaddr_t *dba)
+{
+ pin_code_reply_cp pr;
+ struct hci_conn_info_req *cr;
+ struct hci_conn_info *ci;
+ unsigned char key[16];
+ char sa[18], da[18], pin[17];
+ int err, pinlen;
+
+ memset(&pr, 0, sizeof(pr));
+ bacpy(&pr.bdaddr, dba);
+
+ ba2str(sba, sa); ba2str(dba, da);
+ info("pin_code_request (sba=%s, dba=%s)", sa, da);
+
+ cr = g_malloc0(sizeof(*cr) + sizeof(*ci));
+
+ bacpy(&cr->bdaddr, dba);
+ cr->type = ACL_LINK;
+ if (ioctl(dev, HCIGETCONNINFO, (unsigned long) cr) < 0) {
+ error("Can't get conn info: %s (%d)", strerror(errno), errno);
+ goto reject;
+ }
+ ci = cr->conn_info;
+
+ memset(pin, 0, sizeof(pin));
+ pinlen = read_pin_code(sba, dba, pin);
+
+ if (pairing == HCID_PAIRING_ONCE) {
+ err = read_link_key(sba, dba, key, NULL);
+ if (!err) {
+ ba2str(dba, da);
+ error("PIN code request for already paired device %s", da);
+ goto reject;
+ }
+ } else if (pairing == HCID_PAIRING_NONE)
+ goto reject;
+
+ if (hcid.security == HCID_SEC_AUTO && !ci->out) {
+ set_pin_length(sba, hcid.pin_len);
+ memcpy(pr.pin_code, hcid.pin_code, hcid.pin_len);
+ pr.pin_len = hcid.pin_len;
+ hci_send_cmd(dev, OGF_LINK_CTL, OCF_PIN_CODE_REPLY,
+ PIN_CODE_REPLY_CP_SIZE, &pr);
+ } else {
+ if (pinlen > 0) {
+ set_pin_length(sba, pinlen);
+ memcpy(pr.pin_code, pin, pinlen);
+ pr.pin_len = pinlen;
+ hci_send_cmd(dev, OGF_LINK_CTL, OCF_PIN_CODE_REPLY,
+ PIN_CODE_REPLY_CP_SIZE, &pr);
+ } else {
+ /* Request PIN from passkey agent */
+ if (hcid_dbus_request_pin(dev, sba, ci) < 0)
+ goto reject;
+ }
+ }
+
+ g_free(cr);
+
+ return;
+
+reject:
+ g_free(cr);
+
+ hci_send_cmd(dev, OGF_LINK_CTL, OCF_PIN_CODE_NEG_REPLY, 6, dba);
+}
+
+static inline void cmd_status(int dev, bdaddr_t *sba, void *ptr)
+{
+ evt_cmd_status *evt = ptr;
+ uint16_t opcode = btohs(evt->opcode);
+
+ if (evt->status)
+ return;
+
+ if (opcode == cmd_opcode_pack(OGF_LINK_CTL, OCF_INQUIRY))
+ hcid_dbus_inquiry_start(sba);
+}
+
+static inline void cmd_complete(int dev, bdaddr_t *sba, void *ptr)
+{
+ evt_cmd_complete *evt = ptr;
+ uint16_t opcode = btohs(evt->opcode);
+ uint8_t status;
+
+ switch (opcode) {
+ case cmd_opcode_pack(OGF_LINK_CTL, OCF_PERIODIC_INQUIRY):
+ status = *((uint8_t *) ptr + EVT_CMD_COMPLETE_SIZE);
+ hcid_dbus_periodic_inquiry_start(sba, status);
+ break;
+ case cmd_opcode_pack(OGF_LINK_CTL, OCF_EXIT_PERIODIC_INQUIRY):
+ status = *((uint8_t *) ptr + EVT_CMD_COMPLETE_SIZE);
+ hcid_dbus_periodic_inquiry_exit(sba, status);
+ break;
+ case cmd_opcode_pack(OGF_LINK_CTL, OCF_INQUIRY_CANCEL):
+ hcid_dbus_inquiry_complete(sba);
+ break;
+ case cmd_opcode_pack(OGF_HOST_CTL, OCF_CHANGE_LOCAL_NAME):
+ hcid_dbus_setname_complete(sba);
+ break;
+ case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE):
+ hcid_dbus_setscan_enable_complete(sba);
+ break;
+ case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_CLASS_OF_DEV):
+ hcid_dbus_write_class_complete(sba);
+ break;
+ case cmd_opcode_pack(OGF_HOST_CTL, OCF_WRITE_SIMPLE_PAIRING_MODE):
+ hcid_dbus_write_simple_pairing_mode_complete(sba);
+ break;
+ };
+}
+
+static inline void remote_name_information(int dev, bdaddr_t *sba, void *ptr)
+{
+ evt_remote_name_req_complete *evt = ptr;
+ bdaddr_t dba;
+ char name[249];
+
+ memset(name, 0, sizeof(name));
+ bacpy(&dba, &evt->bdaddr);
+
+ if (!evt->status) {
+ char *end;
+ memcpy(name, evt->name, 248);
+ /* 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(sba, &dba, name);
+ }
+
+ hcid_dbus_remote_name(sba, &dba, evt->status, name);
+}
+
+static inline void remote_version_information(int dev, bdaddr_t *sba, void *ptr)
+{
+ evt_read_remote_version_complete *evt = ptr;
+ bdaddr_t dba;
+
+ if (evt->status)
+ return;
+
+ if (get_bdaddr(dev, sba, btohs(evt->handle), &dba) < 0)
+ return;
+
+ write_version_info(sba, &dba, btohs(evt->manufacturer),
+ evt->lmp_ver, btohs(evt->lmp_subver));
+}
+
+static inline void inquiry_complete(int dev, bdaddr_t *sba, void *ptr)
+{
+ hcid_dbus_inquiry_complete(sba);
+}
+
+static inline void inquiry_result(int dev, bdaddr_t *sba, int plen, void *ptr)
+{
+ uint8_t num = *(uint8_t *) ptr++;
+ int i;
+
+ for (i = 0; i < num; i++) {
+ inquiry_info *info = ptr;
+ uint32_t class = info->dev_class[0]
+ | (info->dev_class[1] << 8)
+ | (info->dev_class[2] << 16);
+
+ hcid_dbus_inquiry_result(sba, &info->bdaddr, class, 0, NULL);
+
+ update_lastseen(sba, &info->bdaddr);
+
+ ptr += INQUIRY_INFO_SIZE;
+ }
+}
+
+static inline void inquiry_result_with_rssi(int dev, bdaddr_t *sba, int plen, void *ptr)
+{
+ uint8_t num = *(uint8_t *) ptr++;
+ int i;
+
+ if (!num)
+ return;
+
+ if ((plen - 1) / num == INQUIRY_INFO_WITH_RSSI_AND_PSCAN_MODE_SIZE) {
+ for (i = 0; i < num; i++) {
+ inquiry_info_with_rssi_and_pscan_mode *info = ptr;
+ uint32_t class = info->dev_class[0]
+ | (info->dev_class[1] << 8)
+ | (info->dev_class[2] << 16);
+
+ hcid_dbus_inquiry_result(sba, &info->bdaddr,
+ class, info->rssi, NULL);
+
+ update_lastseen(sba, &info->bdaddr);
+
+ ptr += INQUIRY_INFO_WITH_RSSI_AND_PSCAN_MODE_SIZE;
+ }
+ } else {
+ for (i = 0; i < num; i++) {
+ inquiry_info_with_rssi *info = ptr;
+ uint32_t class = info->dev_class[0]
+ | (info->dev_class[1] << 8)
+ | (info->dev_class[2] << 16);
+
+ hcid_dbus_inquiry_result(sba, &info->bdaddr,
+ class, info->rssi, NULL);
+
+ update_lastseen(sba, &info->bdaddr);
+
+ ptr += INQUIRY_INFO_WITH_RSSI_SIZE;
+ }
+ }
+}
+
+static inline void extended_inquiry_result(int dev, bdaddr_t *sba, int plen, void *ptr)
+{
+ uint8_t num = *(uint8_t *) ptr++;
+ int i;
+
+ for (i = 0; i < num; i++) {
+ extended_inquiry_info *info = ptr;
+ uint32_t class = info->dev_class[0]
+ | (info->dev_class[1] << 8)
+ | (info->dev_class[2] << 16);
+
+ hcid_dbus_inquiry_result(sba, &info->bdaddr, class,
+ info->rssi, info->data);
+
+ update_lastseen(sba, &info->bdaddr);
+
+ ptr += EXTENDED_INQUIRY_INFO_SIZE;
+ }
+}
+
+static inline void remote_features_information(int dev, bdaddr_t *sba, void *ptr)
+{
+ evt_read_remote_features_complete *evt = ptr;
+ bdaddr_t dba;
+
+ if (evt->status)
+ return;
+
+ if (get_bdaddr(dev, sba, btohs(evt->handle), &dba) < 0)
+ return;
+
+ write_features_info(sba, &dba, evt->features);
+}
+
+static inline void conn_complete(int dev, int dev_id, bdaddr_t *sba, void *ptr)
+{
+ evt_conn_complete *evt = ptr;
+ char filename[PATH_MAX];
+ remote_name_req_cp cp_name;
+ struct hci_req_data *data;
+ char local_addr[18], peer_addr[18], *str;
+
+ if (evt->link_type != ACL_LINK)
+ return;
+
+ hcid_dbus_conn_complete(sba, evt->status, btohs(evt->handle),
+ &evt->bdaddr);
+
+ if (evt->status)
+ return;
+
+ update_lastused(sba, &evt->bdaddr);
+
+ /* Request remote name */
+ memset(&cp_name, 0, sizeof(cp_name));
+ bacpy(&cp_name.bdaddr, &evt->bdaddr);
+ cp_name.pscan_rep_mode = 0x02;
+
+ data = hci_req_data_new(dev_id, &evt->bdaddr, OGF_LINK_CTL,
+ OCF_REMOTE_NAME_REQ, EVT_REMOTE_NAME_REQ_COMPLETE,
+ &cp_name, REMOTE_NAME_REQ_CP_SIZE);
+
+ hci_req_queue_append(data);
+
+ /* check if the remote version needs be requested */
+ ba2str(sba, local_addr);
+ ba2str(&evt->bdaddr, peer_addr);
+
+ create_name(filename, sizeof(filename), STORAGEDIR, local_addr, "manufacturers");
+
+ str = textfile_get(filename, peer_addr);
+ if (!str) {
+ read_remote_version_cp cp;
+
+ memset(&cp, 0, sizeof(cp));
+ cp.handle = evt->handle;
+
+ data = hci_req_data_new(dev_id, &evt->bdaddr, OGF_LINK_CTL,
+ OCF_READ_REMOTE_VERSION, EVT_READ_REMOTE_VERSION_COMPLETE,
+ &cp, READ_REMOTE_VERSION_CP_SIZE);
+
+ hci_req_queue_append(data);
+ } else
+ free(str);
+}
+
+static inline void disconn_complete(int dev, bdaddr_t *sba, void *ptr)
+{
+ evt_disconn_complete *evt = ptr;
+
+ hcid_dbus_disconn_complete(sba, evt->status, btohs(evt->handle),
+ evt->reason);
+}
+
+static inline void auth_complete(int dev, bdaddr_t *sba, void *ptr)
+{
+ evt_auth_complete *evt = ptr;
+ bdaddr_t dba;
+
+ if (get_bdaddr(dev, sba, btohs(evt->handle), &dba) < 0)
+ return;
+
+ if (evt->status)
+ hcid_dbus_bonding_process_complete(sba, &dba, evt->status);
+}
+
+static inline void conn_request(int dev, bdaddr_t *sba, void *ptr)
+{
+ evt_conn_request *evt = ptr;
+ uint32_t class = evt->dev_class[0] | (evt->dev_class[1] << 8)
+ | (evt->dev_class[2] << 16);
+
+ hcid_dbus_remote_class(sba, &evt->bdaddr, class);
+
+ write_remote_class(sba, &evt->bdaddr, class);
+}
+
+static gboolean io_security_event(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+ unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr = buf;
+ struct hci_dev_info *di = data;
+ int type, dev;
+ size_t len;
+ hci_event_hdr *eh;
+ GIOError err;
+
+ if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR)) {
+ g_io_channel_unref(chan);
+ return FALSE;
+ }
+
+ if ((err = g_io_channel_read(chan, (gchar *) buf, sizeof(buf), &len))) {
+ if (err == G_IO_ERROR_AGAIN)
+ return TRUE;
+ g_io_channel_unref(chan);
+ return FALSE;
+ }
+
+ type = *ptr++;
+
+ if (type != HCI_EVENT_PKT)
+ return TRUE;
+
+ eh = (hci_event_hdr *) ptr;
+ ptr += HCI_EVENT_HDR_SIZE;
+
+ dev = g_io_channel_unix_get_fd(chan);
+
+ ioctl(dev, HCIGETDEVINFO, (void *) di);
+
+ if (hci_test_bit(HCI_RAW, &di->flags))
+ return TRUE;
+
+ switch (eh->evt) {
+ case EVT_CMD_STATUS:
+ cmd_status(dev, &di->bdaddr, ptr);
+ break;
+
+ case EVT_CMD_COMPLETE:
+ cmd_complete(dev, &di->bdaddr, ptr);
+ break;
+
+ case EVT_REMOTE_NAME_REQ_COMPLETE:
+ remote_name_information(dev, &di->bdaddr, ptr);
+ break;
+
+ case EVT_READ_REMOTE_VERSION_COMPLETE:
+ remote_version_information(dev, &di->bdaddr, ptr);
+ break;
+
+ case EVT_READ_REMOTE_FEATURES_COMPLETE:
+ remote_features_information(dev, &di->bdaddr, ptr);
+ break;
+
+ case EVT_INQUIRY_COMPLETE:
+ inquiry_complete(dev, &di->bdaddr, ptr);
+ break;
+
+ case EVT_INQUIRY_RESULT:
+ inquiry_result(dev, &di->bdaddr, eh->plen, ptr);
+ break;
+
+ case EVT_INQUIRY_RESULT_WITH_RSSI:
+ inquiry_result_with_rssi(dev, &di->bdaddr, eh->plen, ptr);
+ break;
+
+ case EVT_EXTENDED_INQUIRY_RESULT:
+ extended_inquiry_result(dev, &di->bdaddr, eh->plen, ptr);
+ break;
+
+ case EVT_CONN_COMPLETE:
+ conn_complete(dev, di->dev_id, &di->bdaddr, ptr);
+ break;
+
+ case EVT_DISCONN_COMPLETE:
+ disconn_complete(dev, &di->bdaddr, ptr);
+ break;
+
+ case EVT_AUTH_COMPLETE:
+ auth_complete(dev, &di->bdaddr, ptr);
+ break;
+
+ case EVT_CONN_REQUEST:
+ conn_request(dev, &di->bdaddr, ptr);
+ break;
+ }
+
+ /* Check for pending command request */
+ check_pending_hci_req(di->dev_id, eh->evt);
+
+ if (hci_test_bit(HCI_SECMGR, &di->flags))
+ return TRUE;
+
+ switch (eh->evt) {
+ case EVT_PIN_CODE_REQ:
+ pin_code_request(dev, &di->bdaddr, (bdaddr_t *) ptr);
+ break;
+
+ case EVT_LINK_KEY_REQ:
+ link_key_request(dev, &di->bdaddr, (bdaddr_t *) ptr);
+ break;
+
+ case EVT_LINK_KEY_NOTIFY:
+ link_key_notify(dev, &di->bdaddr, ptr);
+ break;
+
+ case EVT_RETURN_LINK_KEYS:
+ return_link_keys(dev, &di->bdaddr, ptr);
+ break;
+
+ case EVT_IO_CAPABILITY_REQUEST:
+ io_capa_request(dev, &di->bdaddr, (bdaddr_t *) ptr);
+ break;
+
+ case EVT_IO_CAPABILITY_RESPONSE:
+ io_capa_response(dev, &di->bdaddr, ptr);
+ break;
+
+ case EVT_USER_CONFIRM_REQUEST:
+ user_confirm_request(dev, &di->bdaddr, ptr);
+ break;
+
+ case EVT_USER_PASSKEY_REQUEST:
+ user_passkey_request(dev, &di->bdaddr, ptr);
+ break;
+
+ case EVT_USER_PASSKEY_NOTIFY:
+ user_passkey_notify(dev, &di->bdaddr, ptr);
+ break;
+
+ case EVT_REMOTE_OOB_DATA_REQUEST:
+ remote_oob_data_request(dev, &di->bdaddr, ptr);
+ break;
+ }
+
+ return TRUE;
+}
+
+void start_security_manager(int hdev)
+{
+ GIOChannel *chan = io_data[hdev].channel;
+ struct hci_dev_info *di;
+ struct hci_filter flt;
+ read_stored_link_key_cp cp;
+ int dev;
+
+ if (chan)
+ return;
+
+ info("Starting security manager %d", hdev);
+
+ if ((dev = hci_open_dev(hdev)) < 0) {
+ error("Can't open device hci%d: %s (%d)",
+ hdev, strerror(errno), errno);
+ return;
+ }
+
+ /* Set filter */
+ hci_filter_clear(&flt);
+ hci_filter_set_ptype(HCI_EVENT_PKT, &flt);
+ hci_filter_set_event(EVT_CMD_STATUS, &flt);
+ hci_filter_set_event(EVT_CMD_COMPLETE, &flt);
+ hci_filter_set_event(EVT_PIN_CODE_REQ, &flt);
+ hci_filter_set_event(EVT_LINK_KEY_REQ, &flt);
+ hci_filter_set_event(EVT_LINK_KEY_NOTIFY, &flt);
+ hci_filter_set_event(EVT_RETURN_LINK_KEYS, &flt);
+ hci_filter_set_event(EVT_IO_CAPABILITY_REQUEST, &flt);
+ hci_filter_set_event(EVT_IO_CAPABILITY_RESPONSE, &flt);
+ hci_filter_set_event(EVT_USER_CONFIRM_REQUEST, &flt);
+ hci_filter_set_event(EVT_USER_PASSKEY_REQUEST, &flt);
+ hci_filter_set_event(EVT_REMOTE_OOB_DATA_REQUEST, &flt);
+ hci_filter_set_event(EVT_USER_PASSKEY_NOTIFY, &flt);
+ hci_filter_set_event(EVT_KEYPRESS_NOTIFY, &flt);
+ hci_filter_set_event(EVT_SIMPLE_PAIRING_COMPLETE, &flt);
+ hci_filter_set_event(EVT_AUTH_COMPLETE, &flt);
+ hci_filter_set_event(EVT_REMOTE_NAME_REQ_COMPLETE, &flt);
+ hci_filter_set_event(EVT_READ_REMOTE_VERSION_COMPLETE, &flt);
+ hci_filter_set_event(EVT_READ_REMOTE_FEATURES_COMPLETE, &flt);
+ hci_filter_set_event(EVT_REMOTE_HOST_FEATURES_NOTIFY, &flt);
+ hci_filter_set_event(EVT_INQUIRY_COMPLETE, &flt);
+ hci_filter_set_event(EVT_INQUIRY_RESULT, &flt);
+ hci_filter_set_event(EVT_INQUIRY_RESULT_WITH_RSSI, &flt);
+ hci_filter_set_event(EVT_EXTENDED_INQUIRY_RESULT, &flt);
+ hci_filter_set_event(EVT_CONN_REQUEST, &flt);
+ hci_filter_set_event(EVT_CONN_COMPLETE, &flt);
+ hci_filter_set_event(EVT_DISCONN_COMPLETE, &flt);
+ if (setsockopt(dev, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) {
+ error("Can't set filter on hci%d: %s (%d)",
+ hdev, strerror(errno), errno);
+ close(dev);
+ return;
+ }
+
+ di = g_new(struct hci_dev_info, 1);
+ if (hci_devinfo(hdev, di) < 0) {
+ error("Can't get device info: %s (%d)",
+ strerror(errno), errno);
+ close(dev);
+ g_free(di);
+ return;
+ }
+
+ chan = g_io_channel_unix_new(dev);
+ g_io_channel_set_close_on_unref(chan, TRUE);
+ io_data[hdev].watch_id = g_io_add_watch_full(chan, G_PRIORITY_HIGH,
+ G_IO_IN | G_IO_NVAL | G_IO_HUP | G_IO_ERR,
+ io_security_event, di, (GDestroyNotify) g_free);
+ io_data[hdev].channel = chan;
+ io_data[hdev].pin_length = -1;
+
+ if (hci_test_bit(HCI_RAW, &di->flags))
+ return;
+
+ bacpy(&cp.bdaddr, BDADDR_ANY);
+ cp.read_all = 1;
+
+ hci_send_cmd(dev, OGF_HOST_CTL, OCF_READ_STORED_LINK_KEY,
+ READ_STORED_LINK_KEY_CP_SIZE, (void *) &cp);
+}
+
+void stop_security_manager(int hdev)
+{
+ GIOChannel *chan = io_data[hdev].channel;
+
+ if (!chan)
+ return;
+
+ info("Stopping security manager %d", hdev);
+
+ g_source_remove(io_data[hdev].watch_id);
+ g_io_channel_unref(io_data[hdev].channel);
+ io_data[hdev].watch_id = -1;
+ io_data[hdev].channel = NULL;
+ io_data[hdev].pin_length = -1;
+}
+
+void init_security_data(void)
+{
+ pairing = hcid.pairing;
+}
diff --git a/src/server.c b/src/server.c
new file mode 100644
index 00000000..da240112
--- /dev/null
+++ b/src/server.c
@@ -0,0 +1,68 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 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 <glib.h>
+
+#include "server.h"
+
+static GSList *servers = NULL;
+
+int bt_register_server(struct bt_server *server)
+{
+ servers = g_slist_append(servers, server);
+
+ return 0;
+}
+
+void bt_unregister_server(struct bt_server *server)
+{
+ servers = g_slist_remove(servers, server);
+}
+
+void __probe_servers(const char *adapter)
+{
+ GSList *list;
+
+ for (list = servers; list; list = list->next) {
+ struct bt_server *server = list->data;
+
+ if (server->probe)
+ server->probe(adapter);
+ }
+}
+
+void __remove_servers(const char *adapter)
+{
+ GSList *list;
+
+ for (list = servers; list; list = list->next) {
+ struct bt_server *server = list->data;
+
+ if (server->remove)
+ server->remove(adapter);
+ }
+}
diff --git a/src/server.h b/src/server.h
new file mode 100644
index 00000000..f60ab88b
--- /dev/null
+++ b/src/server.h
@@ -0,0 +1,31 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 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 bt_server {
+ const char *uuid;
+ int (*probe) (const char *adapter);
+ void (*remove) (const char *adapter);
+};
+
+int bt_register_server(struct bt_server *server);
+void bt_unregister_server(struct bt_server *server);
diff --git a/src/service-did.xml b/src/service-did.xml
new file mode 100644
index 00000000..52eb68c0
--- /dev/null
+++ b/src/service-did.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<record>
+ <attribute id="0x0001">
+ <sequence>
+ <uuid value="0x1200"/>
+ </sequence>
+ </attribute>
+
+ <attribute id="0x0200">
+ <uint16 value="0x0102" name="id"/>
+ </attribute>
+
+ <attribute id="0x0201">
+ <uint16 value="0x0a12" name="vendor"/>
+ </attribute>
+
+ <attribute id="0x0202">
+ <uint16 value="0x4711" name="product"/>
+ </attribute>
+
+ <attribute id="0x0203">
+ <uint16 value="0x0000" name="version"/>
+ </attribute>
+
+ <attribute id="0x0204">
+ <boolean value="true"/>
+ </attribute>
+
+ <attribute id="0x0205">
+ <uint16 value="0x0002" name="source"/>
+ </attribute>
+</record>
diff --git a/src/service-ftp.xml b/src/service-ftp.xml
new file mode 100644
index 00000000..1bda8857
--- /dev/null
+++ b/src/service-ftp.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<record>
+ <attribute id="0x0001">
+ <sequence>
+ <uuid value="0x1106"/>
+ </sequence>
+ </attribute>
+
+ <attribute id="0x0004">
+ <sequence>
+ <sequence>
+ <uuid value="0x0100"/>
+ </sequence>
+ <sequence>
+ <uuid value="0x0003"/>
+ <uint8 value="23" name="channel"/>
+ </sequence>
+ <sequence>
+ <uuid value="0x0008"/>
+ </sequence>
+ </sequence>
+ </attribute>
+
+ <attribute id="0x0009">
+ <sequence>
+ <sequence>
+ <uuid value="0x1106"/>
+ <uint16 value="0x0100" name="version"/>
+ </sequence>
+ </sequence>
+ </attribute>
+
+ <attribute id="0x0100">
+ <text value="OBEX File Transfer" name="name"/>
+ </attribute>
+</record>
diff --git a/src/service-opp.xml b/src/service-opp.xml
new file mode 100644
index 00000000..351b4a41
--- /dev/null
+++ b/src/service-opp.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<record>
+ <attribute id="0x0001">
+ <sequence>
+ <uuid value="0x1105"/>
+ </sequence>
+ </attribute>
+
+ <attribute id="0x0004">
+ <sequence>
+ <sequence>
+ <uuid value="0x0100"/>
+ </sequence>
+ <sequence>
+ <uuid value="0x0003"/>
+ <uint8 value="23" name="channel"/>
+ </sequence>
+ <sequence>
+ <uuid value="0x0008"/>
+ </sequence>
+ </sequence>
+ </attribute>
+
+ <attribute id="0x0009">
+ <sequence>
+ <sequence>
+ <uuid value="0x1105"/>
+ <uint16 value="0x0100" name="version"/>
+ </sequence>
+ </sequence>
+ </attribute>
+
+ <attribute id="0x0100">
+ <text value="OBEX Object Push" name="name"/>
+ </attribute>
+
+ <attribute id="0x0303">
+ <sequence>
+ <uint8 value="0x01"/>
+ <uint8 value="0x01"/>
+ <uint8 value="0x02"/>
+ <uint8 value="0x03"/>
+ <uint8 value="0x04"/>
+ <uint8 value="0x05"/>
+ <uint8 value="0x06"/>
+ <uint8 value="0xff"/>
+ </sequence>
+ </attribute>
+</record>
diff --git a/src/service-record.dtd b/src/service-record.dtd
new file mode 100644
index 00000000..f53be5d0
--- /dev/null
+++ b/src/service-record.dtd
@@ -0,0 +1,66 @@
+<!ELEMENT record (attribute)*>
+
+<!ELEMENT attribute (sequence|alternate|text|url|uuid|boolean|uint8|uint16|uint32|uint64|nil)+>
+<!ATTLIST attribute id CDATA #REQUIRED>
+
+<!ELEMENT sequence (sequence|alternate|text|url|uuid|boolean|uint8|uint16|uint32|uint64|uint128|int8|int16|int32|int64|int128|nil)+>
+
+<!ELEMENT alternate (sequence|alternate|text|url|uuid|boolean|uint8|uint16|uint32|uint64|uint128|int8|int16|int32|int64|int128|nil)+>
+
+<!ELEMENT text EMPTY>
+<!ATTLIST text value CDATA #REQUIRED>
+<!ATTLIST text name CDATA>
+<!ATTLIST text encoding (normal|hex) "normal">
+
+<!ELEMENT url EMPTY>
+<!ATTLIST url value CDATA #REQUIRED>
+<!ATTLIST url name CDATA>
+
+<!ELEMENT uuid EMPTY>
+<!ATTLIST uuid value CDATA #REQUIRED>
+
+<!ELEMENT boolean EMPTY>
+<!ATTLIST boolean value CDATA #REQUIRED>
+<!ATTLIST boolean name CDATA>
+
+<!ELEMENT uint8 EMPTY>
+<!ATTLIST uint8 value CDATA #REQUIRED>
+<!ATTLIST uint8 name CDATA>
+
+<!ELEMENT uint16 EMPTY>
+<!ATTLIST uint16 value CDATA #REQUIRED>
+<!ATTLIST uint16 name CDATA>
+
+<!ELEMENT uint32 EMPTY>
+<!ATTLIST uint32 value CDATA #REQUIRED>
+<!ATTLIST uint32 name CDATA>
+
+<!ELEMENT uint64 EMPTY>
+<!ATTLIST uint64 value CDATA #REQUIRED>
+<!ATTLIST uint64 name CDATA>
+
+<!ELEMENT uint128 EMPTY>
+<!ATTLIST uint128 value CDATA #REQUIRED>
+<!ATTLIST uint128 name CDATA>
+
+<!ELEMENT int8 EMPTY>
+<!ATTLIST int8 value CDATA #REQUIRED>
+<!ATTLIST int8 name CDATA>
+
+<!ELEMENT int16 EMPTY>
+<!ATTLIST int16 value CDATA #REQUIRED>
+<!ATTLIST int16 name CDATA>
+
+<!ELEMENT int32 EMPTY>
+<!ATTLIST int32 value CDATA #REQUIRED>
+<!ATTLIST int32 name CDATA>
+
+<!ELEMENT int64 EMPTY>
+<!ATTLIST int64 value CDATA #REQUIRED>
+<!ATTLIST int64 name CDATA>
+
+<!ELEMENT int128 EMPTY>
+<!ATTLIST int128 value CDATA #REQUIRED>
+<!ATTLIST int128 name CDATA>
+
+<!ELEMENT nil EMPTY>
diff --git a/src/service-spp.xml b/src/service-spp.xml
new file mode 100644
index 00000000..2b156c3f
--- /dev/null
+++ b/src/service-spp.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<record>
+ <attribute id="0x0001">
+ <sequence>
+ <uuid value="0x1101"/>
+ </sequence>
+ </attribute>
+
+ <attribute id="0x0004">
+ <sequence>
+ <sequence>
+ <uuid value="0x0100"/>
+ </sequence>
+ <sequence>
+ <uuid value="0x0003"/>
+ <uint8 value="23" name="channel"/>
+ </sequence>
+ </sequence>
+ </attribute>
+
+ <attribute id="0x0100">
+ <text value="COM5" name="name"/>
+ </attribute>
+</record>
diff --git a/src/simple-agent b/src/simple-agent
new file mode 100755
index 00000000..0d3dc1f7
--- /dev/null
+++ b/src/simple-agent
@@ -0,0 +1,112 @@
+#!/usr/bin/python
+
+import gobject
+
+import sys
+import dbus
+import dbus.service
+import dbus.mainloop.glib
+
+class Rejected(dbus.DBusException):
+ _dbus_error_name = "org.bluez.Error.Rejected"
+
+class Agent(dbus.service.Object):
+ exit_on_release = True
+
+ def set_exit_on_release(self, exit_on_release):
+ self.exit_on_release = exit_on_release
+
+ @dbus.service.method("org.bluez.Agent",
+ in_signature="", out_signature="")
+ def Release(self):
+ print "Release"
+ if self.exit_on_release:
+ mainloop.quit()
+
+ @dbus.service.method("org.bluez.Agent",
+ in_signature="os", out_signature="")
+ def Authorize(self, device, uuid):
+ print "Authorize (%s, %s)" % (device, uuid)
+
+ @dbus.service.method("org.bluez.Agent",
+ in_signature="o", out_signature="s")
+ def RequestPinCode(self, device):
+ print "RequestPinCode (%s)" % (device)
+ return raw_input("Enter PIN Code: ")
+
+ @dbus.service.method("org.bluez.Agent",
+ in_signature="o", out_signature="u")
+ def RequestPasskey(self, device):
+ print "RequestPasskey (%s)" % (device)
+ passkey = raw_input("Enter passkey: ")
+ return dbus.UInt32(passkey)
+
+ @dbus.service.method("org.bluez.Agent",
+ in_signature="ou", out_signature="")
+ def DisplayPasskey(self, device, passkey):
+ print "DisplayPasskey (%s, %d)" % (device, passkey)
+
+ @dbus.service.method("org.bluez.Agent",
+ in_signature="ou", out_signature="")
+ def RequestConfirmation(self, device, passkey):
+ print "RequestConfirmation (%s, %d)" % (device, passkey)
+ confirm = raw_input("Confirm passkey (yes/no): ")
+ if (confirm == "yes"):
+ return
+ raise Rejected("Passkey doesn't match")
+
+ @dbus.service.method("org.bluez.Agent",
+ in_signature="s", out_signature="")
+ def ConfirmModeChange(self, mode):
+ print "ConfirmModeChange (%s)" % (mode)
+
+ @dbus.service.method("org.bluez.Agent",
+ in_signature="", out_signature="")
+ def Cancel(self):
+ print "Cancel"
+
+def create_device_reply(device):
+ print "New device (%s)" % (device)
+ mainloop.quit()
+
+def create_device_error(error):
+ print "Creating device failed: %s" % (error)
+ mainloop.quit()
+
+if __name__ == '__main__':
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ bus = dbus.SystemBus()
+ manager = dbus.Interface(bus.get_object("org.bluez", "/"),
+ "org.bluez.Manager")
+
+ if len(sys.argv) > 1:
+ path = manager.FindAdapter(sys.argv[1])
+ else:
+ path = manager.DefaultAdapter()
+
+ adapter = dbus.Interface(bus.get_object("org.bluez", path),
+ "org.bluez.Adapter")
+
+ path = "/test/agent"
+ agent = Agent(bus, path)
+
+ mainloop = gobject.MainLoop()
+
+ if len(sys.argv) > 2:
+ if len(sys.argv) > 3:
+ device = adapter.FindDevice(sys.argv[2])
+ adapter.RemoveDevice(device)
+
+ agent.set_exit_on_release(False)
+ adapter.CreatePairedDevice(sys.argv[2], path, "DisplayYesNo",
+ reply_handler=create_device_reply,
+ error_handler=create_device_error)
+ else:
+ adapter.RegisterAgent(path, "DisplayYesNo")
+ print "Agent registered"
+
+ mainloop.run()
+
+ #adapter.UnregisterAgent(path)
+ #print "Agent unregistered"
diff --git a/src/simple-service b/src/simple-service
new file mode 100755
index 00000000..5279a3a6
--- /dev/null
+++ b/src/simple-service
@@ -0,0 +1,127 @@
+#!/usr/bin/python
+
+import sys
+import time
+import dbus
+
+xml = ' \
+<?xml version="1.0" encoding="UTF-8" ?> \
+<record> \
+ <attribute id="0x0001"> \
+ <sequence> \
+ <uuid value="0x1101"/> \
+ </sequence> \
+ </attribute> \
+ \
+ <attribute id="0x0002"> \
+ <uint32 value="0"/> \
+ </attribute> \
+ \
+ <attribute id="0x0003"> \
+ <uuid value="00001101-0000-1000-8000-00805f9b34fb"/> \
+ </attribute> \
+ \
+ <attribute id="0x0004"> \
+ <sequence> \
+ <sequence> \
+ <uuid value="0x0100"/> \
+ </sequence> \
+ <sequence> \
+ <uuid value="0x0003"/> \
+ <uint8 value="23"/> \
+ </sequence> \
+ </sequence> \
+ </attribute> \
+ \
+ <attribute id="0x0005"> \
+ <sequence> \
+ <uuid value="0x1002"/> \
+ </sequence> \
+ </attribute> \
+ \
+ <attribute id="0x0006"> \
+ <sequence> \
+ <uint16 value="0x656e"/> \
+ <uint16 value="0x006a"/> \
+ <uint16 value="0x0100"/> \
+ </sequence> \
+ </attribute> \
+ \
+ <attribute id="0x0007"> \
+ <uint32 value="0"/> \
+ </attribute> \
+ \
+ <attribute id="0x0008"> \
+ <uint8 value="0xff"/> \
+ </attribute> \
+ \
+ <attribute id="0x0009"> \
+ <sequence> \
+ <sequence> \
+ <uuid value="0x1101"/> \
+ <uint16 value="0x0100"/> \
+ </sequence> \
+ </sequence> \
+ </attribute> \
+ \
+ <attribute id="0x000a"> \
+ <url value="http://www.bluez.org/"/> \
+ </attribute> \
+ \
+ <attribute id="0x000b"> \
+ <url value="http://www.bluez.org/"/> \
+ </attribute> \
+ \
+ <attribute id="0x000c"> \
+ <url value="http://www.bluez.org/"/> \
+ </attribute> \
+ \
+ <attribute id="0x0100"> \
+ <text value="Serial Port"/> \
+ </attribute> \
+ \
+ <attribute id="0x0101"> \
+ <text value="Serial Port Service"/> \
+ </attribute> \
+ \
+ <attribute id="0x0102"> \
+ <text value="BlueZ"/> \
+ </attribute> \
+ \
+ <attribute id="0x0200"> \
+ <sequence> \
+ <uint16 value="0x0100"/> \
+ </sequence> \
+ </attribute> \
+ \
+ <attribute id="0x0201"> \
+ <uint32 value="0"/> \
+ </attribute> \
+</record> \
+'
+
+bus = dbus.SystemBus()
+manager = dbus.Interface(bus.get_object("org.bluez", "/"),
+ "org.bluez.Manager")
+
+if len(sys.argv) > 1:
+ path = manager.FindAdapter(sys.argv[1])
+else:
+ path = manager.DefaultAdapter()
+
+adapter = dbus.Interface(bus.get_object("org.bluez", path),
+ "org.bluez.Adapter")
+
+handle = adapter.AddServiceRecord(xml)
+
+print "Service record with handle 0x%04x added" % (handle)
+
+print "Press CTRL-C to remove service record"
+
+try:
+ time.sleep(1000)
+ print "Terminating session"
+except:
+ pass
+
+adapter.RemoveServiceRecord(dbus.UInt32(handle))
diff --git a/src/storage.c b/src/storage.c
new file mode 100644
index 00000000..ded9a535
--- /dev/null
+++ b/src/storage.c
@@ -0,0 +1,784 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 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 "hcid.h"
+
+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 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(bdaddr_t *bdaddr, int *timeout)
+{
+ char filename[PATH_MAX + 1], *str;
+
+ create_filename(filename, PATH_MAX, bdaddr, "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_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(bdaddr_t *bdaddr, char *mode, int length)
+{
+ char filename[PATH_MAX + 1], *str;
+
+ create_filename(filename, PATH_MAX, bdaddr, "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(bdaddr_t *bdaddr, char *mode, int length)
+{
+ char filename[PATH_MAX + 1], *str;
+
+ create_filename(filename, PATH_MAX, bdaddr, "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, 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(bdaddr_t *local, bdaddr_t *peer, char *name)
+{
+ char filename[PATH_MAX + 1], addr[18], *str;
+ int len;
+
+ create_filename(filename, PATH_MAX, local, "names");
+
+ ba2str(peer, addr);
+ str = textfile_get(filename, addr);
+ 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 write_l2cap_info(bdaddr_t *local, bdaddr_t *peer,
+ uint16_t mtu_result, uint16_t mtu,
+ uint16_t mask_result, uint32_t mask)
+{
+ char filename[PATH_MAX + 1], addr[18], str[18];
+
+ if (mask_result)
+ snprintf(str, sizeof(str), "%d -1", mtu_result ? -1 : mtu);
+ else
+ snprintf(str, sizeof(str), "%d 0x%08x", mtu_result ? -1 : mtu, mask);
+
+ create_filename(filename, PATH_MAX, local, "l2cap");
+
+ create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+ ba2str(peer, addr);
+ return textfile_put(filename, addr, str);
+}
+
+int read_l2cap_info(bdaddr_t *local, bdaddr_t *peer,
+ uint16_t *mtu_result, uint16_t *mtu,
+ uint16_t *mask_result, uint32_t *mask)
+{
+ char filename[PATH_MAX + 1], addr[18], *str, *space, *msk;
+
+ create_filename(filename, PATH_MAX, local, "l2cap");
+
+ ba2str(peer, addr);
+ str = textfile_get(filename, addr);
+ if (!str)
+ return -ENOENT;
+
+ space = strchr(str, ' ');
+ if (!space) {
+ free(str);
+ return -ENOENT;
+ }
+
+ msk = space + 1;
+ *space = '\0';
+
+ if (mtu_result && mtu) {
+ if (str[0] == '-')
+ *mtu_result = 0x0001;
+ else {
+ *mtu_result = 0;
+ *mtu = (uint16_t) strtol(str, NULL, 0);
+ }
+ }
+
+ if (mask_result && mask) {
+ if (msk[0] == '-')
+ *mask_result = 0x0001;
+ else {
+ *mask_result = 0;
+ *mask = (uint32_t) strtol(msk, NULL, 16);
+ }
+ }
+
+ 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 *features)
+{
+ char filename[PATH_MAX + 1], addr[18], str[17];
+ int i;
+
+ memset(str, 0, sizeof(str));
+ for (i = 0; i < 8; i++)
+ sprintf(str + (i * 2), "%2.2X", features[i]);
+
+ create_filename(filename, PATH_MAX, local, "features");
+
+ create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+ ba2str(peer, addr);
+ return textfile_put(filename, addr, str);
+}
+
+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;
+
+ 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_length(bdaddr_t *local, bdaddr_t *peer)
+{
+ char filename[PATH_MAX + 1], addr[18], *str;
+ int len;
+
+ create_filename(filename, PATH_MAX, local, "linkkeys");
+
+ ba2str(peer, addr);
+ str = textfile_get(filename, addr);
+ if (!str)
+ return -ENOENT;
+
+ if (strlen(str) < 36) {
+ free(str);
+ return -ENOENT;
+ }
+
+ len = atoi(str + 35);
+
+ free(str);
+
+ return len;
+}
+
+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(bdaddr_t *local, const char *addr, const char *service,
+ gboolean trust)
+{
+ char filename[PATH_MAX + 1], *str;
+ GSList *services = NULL, *match;
+ gboolean trusted;
+ int ret;
+
+ create_filename(filename, PATH_MAX, local, "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);
+ if (str)
+ 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);
+
+ if (str)
+ 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 *pdata, *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;
+
+ pdata = (char *)buf.data;
+ 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 *fetch_record(const gchar *src, const gchar *dst, const uint32_t handle)
+{
+ char filename[PATH_MAX + 1], key[28], tmp[3],*str;
+ sdp_record_t *rec;
+ int size, i, len;
+ uint8_t *pdata;
+
+ 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;
+
+ size = strlen(str)/2;
+ pdata = g_malloc0(size);
+
+ 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, &len);
+
+ free(str);
+ free(pdata);
+
+ 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);
+}
diff --git a/src/telephony.c b/src/telephony.c
new file mode 100644
index 00000000..f68b97de
--- /dev/null
+++ b/src/telephony.c
@@ -0,0 +1,44 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 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 <glib.h>
+
+#include "telephony.h"
+
+static GSList *drivers = NULL;
+
+int bt_telephony_register_driver(struct bt_telephony_driver *driver)
+{
+ drivers = g_slist_append(drivers, driver);
+
+ return 0;
+}
+
+void bt_telephony_unregister_driver(struct bt_telephony_driver *driver)
+{
+ drivers = g_slist_remove(drivers, driver);
+}
diff --git a/src/telephony.h b/src/telephony.h
new file mode 100644
index 00000000..b360a621
--- /dev/null
+++ b/src/telephony.h
@@ -0,0 +1,26 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2004-2008 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 bt_telephony_driver {
+ const char *name;
+};
diff --git a/src/test-adapter b/src/test-adapter
new file mode 100755
index 00000000..a4612257
--- /dev/null
+++ b/src/test-adapter
@@ -0,0 +1,90 @@
+#!/usr/bin/python
+
+import sys
+import dbus
+import time
+
+bus = dbus.SystemBus()
+
+manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.bluez.Manager")
+
+adapter = dbus.Interface(bus.get_object("org.bluez", manager.DefaultAdapter()),
+ "org.bluez.Adapter")
+
+if (len(sys.argv) < 2):
+ print "Usage: %s <command>" % (sys.argv[0])
+ print ""
+ print " address"
+ print " name [name]"
+ print " mode [mode]"
+ print " requestmode <mode>"
+ print " discoverabletimeout [timeout]"
+ print " periodicdiscovery [on/off]"
+ print " addservicerecord <file>"
+ sys.exit(1)
+
+if (sys.argv[1] == "address"):
+ properties = adapter.GetProperties()
+ print properties["Address"]
+ sys.exit(0)
+
+if (sys.argv[1] == "name"):
+ if (len(sys.argv) < 3):
+ properties = adapter.GetProperties()
+ print properties["Name"]
+ else:
+ adapter.SetProperty("Name", sys.argv[2])
+ sys.exit(0)
+
+if (sys.argv[1] == "mode"):
+ if (len(sys.argv) < 3):
+ properties = adapter.GetProperties()
+ print properties["Mode"]
+ else:
+ adapter.SetProperty("Mode", sys.argv[2])
+ sys.exit(0)
+
+if (sys.argv[1] == "requestmode"):
+ if (len(sys.argv) < 3):
+ print "Need mode parameter"
+ else:
+ adapter.RequestMode(sys.argv[2])
+ sys.exit(0)
+
+if (sys.argv[1] == "discoverabletimeout"):
+ if (len(sys.argv) < 3):
+ properties = adapter.GetProperties()
+ print properties["DiscoverableTimeout"]
+ else:
+ adapter.SetProperty("DiscoverableTimeout", sys.argv[2])
+ sys.exit(0)
+
+if (sys.argv[1] == "periodicdiscovery"):
+ if (len(sys.argv) < 3):
+ properties = adapter.GetProperties()
+ print properties["PeriodicDiscovery"]
+ else:
+ if (sys.argv[2] == "on"):
+ value = dbus.Boolean(1)
+ elif (sys.argv[2] == "off"):
+ value = dbus.Boolean(0)
+ else:
+ value = dbus.Boolean(sys.argv[2])
+ adapter.SetProperty("PeriodicDiscovery", value)
+ time.sleep(120)
+ sys.exit(0)
+
+if (sys.argv[1] == "addservicerecord"):
+ if (len(sys.argv) < 3):
+ print "Need file parameter"
+ else:
+ f = open(sys.argv[2])
+ record = f.read()
+ f.close()
+ handle = adapter.AddServiceRecord(record)
+ print "0x%x" % (handle)
+ time.sleep(120)
+ sys.exit(0)
+
+print "Unknown command"
+sys.exit(1)
diff --git a/src/test-device b/src/test-device
new file mode 100755
index 00000000..05a23d37
--- /dev/null
+++ b/src/test-device
@@ -0,0 +1,124 @@
+#!/usr/bin/python
+
+import sys
+import dbus
+import re
+
+bus = dbus.SystemBus()
+
+manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.bluez.Manager")
+
+adapter = dbus.Interface(bus.get_object("org.bluez", manager.DefaultAdapter()),
+ "org.bluez.Adapter")
+
+if (len(sys.argv) < 2):
+ print "Usage: %s <command>" % (sys.argv[0])
+ print ""
+ print " list"
+ print " create <address>"
+ print " remove <path>"
+ print " discover <address> [pattern]"
+ print " class <address>"
+ print " name <address>"
+ print " alias <address> [alias]"
+ print " trusted <address> [yes/no]"
+ sys.exit(1)
+
+if (sys.argv[1] == "list"):
+ list = adapter.ListDevices()
+ print list
+ sys.exit(0)
+
+if (sys.argv[1] == "create"):
+ if (len(sys.argv) < 3):
+ print "Need address parameter"
+ else:
+ device = adapter.CreateDevice(sys.argv[2])
+ print device
+ sys.exit(0)
+
+if (sys.argv[1] == "remove"):
+ if (len(sys.argv) < 3):
+ print "Need object path parameter"
+ else:
+ adapter.RemoveDevice(sys.argv[2])
+ sys.exit(0)
+
+if (sys.argv[1] == "discover"):
+ if (len(sys.argv) < 3):
+ print "Need address parameter"
+ else:
+ path = adapter.FindDevice(sys.argv[2])
+ device = dbus.Interface(bus.get_object("org.bluez", path),
+ "org.bluez.Device")
+ if (len(sys.argv) < 4):
+ pattern = ""
+ else:
+ pattern = sys.argv[3]
+ services = device.DiscoverServices(pattern);
+ for key in services.keys():
+ p = re.compile(">.*?<")
+ xml = p.sub("><", services[key].replace("\n", ""))
+ print "[ 0x%5x ]" % (key)
+ print xml
+ print
+ sys.exit(0)
+
+if (sys.argv[1] == "class"):
+ if (len(sys.argv) < 3):
+ print "Need address parameter"
+ else:
+ path = adapter.FindDevice(sys.argv[2])
+ device = dbus.Interface(bus.get_object("org.bluez", path),
+ "org.bluez.Device")
+ properties = device.GetProperties()
+ print "0x%06x" % (properties["Class"])
+ sys.exit(0)
+
+if (sys.argv[1] == "name"):
+ if (len(sys.argv) < 3):
+ print "Need address parameter"
+ else:
+ path = adapter.FindDevice(sys.argv[2])
+ device = dbus.Interface(bus.get_object("org.bluez", path),
+ "org.bluez.Device")
+ properties = device.GetProperties()
+ print properties["Name"]
+ sys.exit(0)
+
+if (sys.argv[1] == "alias"):
+ if (len(sys.argv) < 3):
+ print "Need address parameter"
+ else:
+ path = adapter.FindDevice(sys.argv[2])
+ device = dbus.Interface(bus.get_object("org.bluez", path),
+ "org.bluez.Device")
+ if (len(sys.argv) < 4):
+ properties = device.GetProperties()
+ print properties["Alias"]
+ else:
+ device.SetProperty("Alias", sys.argv[3])
+ sys.exit(0)
+
+if (sys.argv[1] == "trusted"):
+ if (len(sys.argv) < 3):
+ print "Need address parameter"
+ else:
+ path = adapter.FindDevice(sys.argv[2])
+ device = dbus.Interface(bus.get_object("org.bluez", path),
+ "org.bluez.Device")
+ if (len(sys.argv) < 4):
+ properties = device.GetProperties()
+ print properties["Trusted"]
+ else:
+ if (sys.argv[3] == "yes"):
+ value = dbus.Boolean(1)
+ elif (sys.argv[3] == "no"):
+ value = dbus.Boolean(0)
+ else:
+ value = dbus.Boolean(sys.argv[3])
+ device.SetProperty("Trusted", value)
+ sys.exit(0)
+
+print "Unknown command"
+sys.exit(1)
diff --git a/src/test-discovery b/src/test-discovery
new file mode 100755
index 00000000..874de66f
--- /dev/null
+++ b/src/test-discovery
@@ -0,0 +1,43 @@
+#!/usr/bin/python
+
+import gobject
+
+import dbus
+import dbus.mainloop.glib
+
+def device_found(address, properties):
+ print "[ " + address + " ]"
+
+ for key in properties.keys():
+ value = properties[key]
+ if (key == "Class"):
+ print " %s = 0x%06x" % (key, value)
+ else:
+ print " %s = %s" % (key, value)
+
+def discovery_completed():
+ mainloop.quit()
+
+if __name__ == '__main__':
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ bus = dbus.SystemBus()
+ manager = dbus.Interface(bus.get_object("org.bluez", "/"),
+ "org.bluez.Manager")
+
+ path = manager.DefaultAdapter()
+ adapter = dbus.Interface(bus.get_object("org.bluez", path),
+ "org.bluez.Adapter")
+
+ bus.add_signal_receiver(device_found,
+ dbus_interface = "org.bluez.Adapter",
+ signal_name = "DeviceFound")
+
+ bus.add_signal_receiver(discovery_completed,
+ dbus_interface = "org.bluez.Adapter",
+ signal_name = "DiscoveryCompleted")
+
+ adapter.DiscoverDevices()
+
+ mainloop = gobject.MainLoop()
+ mainloop.run()
diff --git a/src/test-manager b/src/test-manager
new file mode 100755
index 00000000..759b6a48
--- /dev/null
+++ b/src/test-manager
@@ -0,0 +1,27 @@
+#!/usr/bin/python
+
+import gobject
+
+import dbus
+import dbus.mainloop.glib
+
+def adapter_added(path):
+ print "Adapter with path %s added" % (path)
+
+def adapter_removed(path):
+ print "Adapter with path %s removed" % (path)
+
+if __name__ == "__main__":
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ bus = dbus.SystemBus()
+
+ manager = dbus.Interface(bus.get_object('org.bluez', '/org/bluez'),
+ 'org.bluez.Manager')
+
+ manager.connect_to_signal("AdapterAdded", adapter_added)
+
+ manager.connect_to_signal("AdapterRemoved", adapter_removed)
+
+ mainloop = gobject.MainLoop()
+ mainloop.run()