summaryrefslogtreecommitdiff
path: root/src/device.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/device.c')
-rw-r--r--src/device.c2388
1 files changed, 2388 insertions, 0 deletions
diff --git a/src/device.c b/src/device.c
new file mode 100644
index 0000000..771a908
--- /dev/null
+++ b/src/device.c
@@ -0,0 +1,2388 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/uuid.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+#include "textfile.h"
+
+#include "att.h"
+#include "hcid.h"
+#include "adapter.h"
+#include "device.h"
+#include "dbus-common.h"
+#include "event.h"
+#include "error.h"
+#include "glib-helper.h"
+#include "gattrib.h"
+#include "gatt.h"
+#include "agent.h"
+#include "sdp-xml.h"
+#include "storage.h"
+#include "btio.h"
+
+#define DISCONNECT_TIMER 2
+#define DISCOVERY_TIMER 2
+
+/* When all services should trust a remote device */
+#define GLOBAL_TRUST "[all]"
+
+struct btd_disconnect_data {
+ guint id;
+ disconnect_watch watch;
+ void *user_data;
+ GDestroyNotify destroy;
+};
+
+struct bonding_req {
+ DBusConnection *conn;
+ DBusMessage *msg;
+ GIOChannel *io;
+ guint listener_id;
+ struct btd_device *device;
+};
+
+struct authentication_req {
+ auth_type_t type;
+ void *cb;
+ struct agent *agent;
+ struct btd_device *device;
+};
+
+struct browse_req {
+ DBusConnection *conn;
+ DBusMessage *msg;
+ GAttrib *attrib;
+ GIOChannel *io;
+ struct btd_device *device;
+ GSList *match_uuids;
+ GSList *profiles_added;
+ GSList *profiles_removed;
+ sdp_list_t *records;
+ int search_uuid;
+ int reconnect_attempt;
+ guint listener_id;
+};
+
+struct btd_device {
+ bdaddr_t bdaddr;
+ device_type_t type;
+ gchar *path;
+ char name[MAX_NAME_LENGTH + 1];
+ char *alias;
+ struct btd_adapter *adapter;
+ GSList *uuids;
+ GSList *services; /* Primary services path */
+ GSList *primaries; /* List of primary services */
+ GSList *drivers; /* List of device drivers */
+ GSList *watches; /* List of disconnect_data */
+ gboolean temporary;
+ struct agent *agent;
+ guint disconn_timer;
+ guint discov_timer;
+ struct browse_req *browse; /* service discover request */
+ struct bonding_req *bonding;
+ struct authentication_req *authr; /* authentication request */
+ GSList *disconnects; /* disconnects message */
+
+ gboolean connected;
+
+ /* Whether were creating a security mode 3 connection */
+ gboolean secmode3;
+
+ sdp_list_t *tmp_records;
+
+ gboolean trusted;
+ gboolean paired;
+ gboolean blocked;
+
+ gboolean authorizing;
+ gint ref;
+};
+
+static uint16_t uuid_list[] = {
+ L2CAP_UUID,
+ PNP_INFO_SVCLASS_ID,
+ PUBLIC_BROWSE_GROUP,
+ 0
+};
+
+static GSList *device_drivers = NULL;
+
+static void browse_request_free(struct browse_req *req)
+{
+ if (req->listener_id)
+ g_dbus_remove_watch(req->conn, req->listener_id);
+ if (req->msg)
+ dbus_message_unref(req->msg);
+ if (req->conn)
+ dbus_connection_unref(req->conn);
+ if (req->device)
+ btd_device_unref(req->device);
+ g_slist_foreach(req->profiles_added, (GFunc) g_free, NULL);
+ g_slist_free(req->profiles_added);
+ g_slist_free(req->profiles_removed);
+ if (req->records)
+ sdp_list_free(req->records, (sdp_free_func_t) sdp_record_free);
+
+ if (req->io) {
+ g_attrib_unref(req->attrib);
+ g_io_channel_unref(req->io);
+ g_io_channel_shutdown(req->io, FALSE, NULL);
+ }
+
+ g_free(req);
+}
+
+static void browse_request_cancel(struct browse_req *req)
+{
+ struct btd_device *device = req->device;
+ struct btd_adapter *adapter = device->adapter;
+ bdaddr_t src;
+
+ if (device_is_creating(device, NULL))
+ device_set_temporary(device, TRUE);
+
+ adapter_get_address(adapter, &src);
+
+ bt_cancel_discovery(&src, &device->bdaddr);
+
+ device->browse = NULL;
+ browse_request_free(req);
+}
+
+static void device_free(gpointer user_data)
+{
+ struct btd_device *device = user_data;
+ struct btd_adapter *adapter = device->adapter;
+ struct agent *agent = adapter_get_agent(adapter);
+
+ if (device->agent)
+ agent_free(device->agent);
+
+ if (agent && (agent_is_busy(agent, device) ||
+ agent_is_busy(agent, device->authr)))
+ agent_cancel(agent);
+
+ g_slist_foreach(device->services, (GFunc) g_free, NULL);
+ g_slist_free(device->services);
+
+ g_slist_foreach(device->uuids, (GFunc) g_free, NULL);
+ g_slist_free(device->uuids);
+
+ g_slist_foreach(device->primaries, (GFunc) g_free, NULL);
+ g_slist_free(device->primaries);
+
+ if (device->tmp_records)
+ sdp_list_free(device->tmp_records,
+ (sdp_free_func_t) sdp_record_free);
+
+ if (device->disconn_timer)
+ g_source_remove(device->disconn_timer);
+
+ if (device->discov_timer)
+ g_source_remove(device->discov_timer);
+
+ DBG("%p", device);
+
+ g_free(device->authr);
+ g_free(device->path);
+ g_free(device->alias);
+ g_free(device);
+}
+
+gboolean device_is_paired(struct btd_device *device)
+{
+ return device->paired;
+}
+
+gboolean device_is_trusted(struct btd_device *device)
+{
+ return device->trusted;
+}
+
+static DBusMessage *get_properties(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ struct btd_device *device = user_data;
+ struct btd_adapter *adapter = device->adapter;
+ DBusMessage *reply;
+ DBusMessageIter iter;
+ DBusMessageIter dict;
+ bdaddr_t src;
+ char name[MAX_NAME_LENGTH + 1], srcaddr[18], dstaddr[18];
+ char **str;
+ const char *ptr;
+ dbus_bool_t boolean;
+ uint32_t class;
+ int i;
+ GSList *l;
+
+ ba2str(&device->bdaddr, dstaddr);
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ /* Address */
+ ptr = dstaddr;
+ dict_append_entry(&dict, "Address", DBUS_TYPE_STRING, &ptr);
+
+ /* Name */
+ ptr = NULL;
+ memset(name, 0, sizeof(name));
+ adapter_get_address(adapter, &src);
+ ba2str(&src, srcaddr);
+
+ ptr = device->name;
+ dict_append_entry(&dict, "Name", DBUS_TYPE_STRING, &ptr);
+
+ /* Alias (fallback to name or address) */
+ if (device->alias != NULL)
+ ptr = device->alias;
+ else if (strlen(ptr) == 0) {
+ g_strdelimit(dstaddr, ":", '-');
+ ptr = dstaddr;
+ }
+
+ dict_append_entry(&dict, "Alias", DBUS_TYPE_STRING, &ptr);
+
+ /* Class */
+ if (read_remote_class(&src, &device->bdaddr, &class) == 0) {
+ const char *icon = class_to_icon(class);
+
+ dict_append_entry(&dict, "Class", DBUS_TYPE_UINT32, &class);
+
+ if (icon)
+ dict_append_entry(&dict, "Icon",
+ DBUS_TYPE_STRING, &icon);
+ }
+
+ /* Paired */
+ boolean = device_is_paired(device);
+ dict_append_entry(&dict, "Paired", DBUS_TYPE_BOOLEAN, &boolean);
+
+ /* Trusted */
+ boolean = device_is_trusted(device);
+ dict_append_entry(&dict, "Trusted", DBUS_TYPE_BOOLEAN, &boolean);
+
+ /* Blocked */
+ boolean = device->blocked;
+ dict_append_entry(&dict, "Blocked", DBUS_TYPE_BOOLEAN, &boolean);
+
+ /* Connected */
+ dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN,
+ &device->connected);
+
+ /* UUIDs */
+ str = g_new0(char *, g_slist_length(device->uuids) + 1);
+ for (i = 0, l = device->uuids; l; l = l->next, i++)
+ str[i] = l->data;
+ dict_append_array(&dict, "UUIDs", DBUS_TYPE_STRING, &str, i);
+ g_free(str);
+
+ /* Services */
+ str = g_new0(char *, g_slist_length(device->services) + 1);
+ for (i = 0, l = device->services; l; l = l->next, i++)
+ str[i] = l->data;
+ dict_append_array(&dict, "Services", DBUS_TYPE_OBJECT_PATH, &str, i);
+ g_free(str);
+
+ /* Adapter */
+ ptr = adapter_get_path(adapter);
+ dict_append_entry(&dict, "Adapter", DBUS_TYPE_OBJECT_PATH, &ptr);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static DBusMessage *set_alias(DBusConnection *conn, DBusMessage *msg,
+ const char *alias, void *data)
+{
+ struct btd_device *device = data;
+ struct btd_adapter *adapter = device->adapter;
+ char srcaddr[18], dstaddr[18];
+ bdaddr_t src;
+ int err;
+
+ /* No change */
+ if ((device->alias == NULL && g_str_equal(alias, "")) ||
+ g_strcmp0(device->alias, alias) == 0)
+ return dbus_message_new_method_return(msg);
+
+ adapter_get_address(adapter, &src);
+ ba2str(&src, srcaddr);
+ ba2str(&device->bdaddr, dstaddr);
+
+ /* Remove alias if empty string */
+ err = write_device_alias(srcaddr, dstaddr,
+ g_str_equal(alias, "") ? NULL : alias);
+ if (err < 0)
+ return btd_error_failed(msg, strerror(-err));
+
+ g_free(device->alias);
+ device->alias = g_str_equal(alias, "") ? NULL : g_strdup(alias);
+
+ emit_property_changed(conn, dbus_message_get_path(msg),
+ DEVICE_INTERFACE, "Alias",
+ DBUS_TYPE_STRING, &alias);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *set_trust(DBusConnection *conn, DBusMessage *msg,
+ gboolean value, void *data)
+{
+ struct btd_device *device = data;
+ struct btd_adapter *adapter = device->adapter;
+ char srcaddr[18], dstaddr[18];
+ bdaddr_t src;
+ int err;
+
+ if (device->trusted == value)
+ return dbus_message_new_method_return(msg);
+
+ adapter_get_address(adapter, &src);
+ ba2str(&src, srcaddr);
+ ba2str(&device->bdaddr, dstaddr);
+
+ err = write_trust(srcaddr, dstaddr, GLOBAL_TRUST, value);
+ if (err < 0)
+ return btd_error_failed(msg, strerror(-err));
+
+ device->trusted = value;
+
+ emit_property_changed(conn, dbus_message_get_path(msg),
+ DEVICE_INTERFACE, "Trusted",
+ DBUS_TYPE_BOOLEAN, &value);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static void driver_remove(struct btd_device_driver *driver,
+ struct btd_device *device)
+{
+ driver->remove(device);
+
+ device->drivers = g_slist_remove(device->drivers, driver);
+}
+
+static gboolean do_disconnect(gpointer user_data)
+{
+ struct btd_device *device = user_data;
+
+ device->disconn_timer = 0;
+
+ btd_adapter_disconnect_device(device->adapter, &device->bdaddr);
+
+ return FALSE;
+}
+
+static int device_block(DBusConnection *conn, struct btd_device *device)
+{
+ int err;
+ bdaddr_t src;
+
+ if (device->blocked)
+ return 0;
+
+ if (device->connected)
+ do_disconnect(device);
+
+ g_slist_foreach(device->drivers, (GFunc) driver_remove, device);
+
+ err = btd_adapter_block_address(device->adapter, &device->bdaddr);
+ if (err < 0)
+ return err;
+
+ device->blocked = TRUE;
+
+ adapter_get_address(device->adapter, &src);
+
+ err = write_blocked(&src, &device->bdaddr, TRUE);
+ if (err < 0)
+ error("write_blocked(): %s (%d)", strerror(-err), -err);
+
+ device_set_temporary(device, FALSE);
+
+ emit_property_changed(conn, device->path, DEVICE_INTERFACE, "Blocked",
+ DBUS_TYPE_BOOLEAN, &device->blocked);
+
+ return 0;
+}
+
+static int device_unblock(DBusConnection *conn, struct btd_device *device,
+ gboolean silent)
+{
+ int err;
+ bdaddr_t src;
+
+ if (!device->blocked)
+ return 0;
+
+ err = btd_adapter_unblock_address(device->adapter, &device->bdaddr);
+ if (err < 0)
+ return err;
+
+ device->blocked = FALSE;
+
+ adapter_get_address(device->adapter, &src);
+
+ err = write_blocked(&src, &device->bdaddr, FALSE);
+ if (err < 0)
+ error("write_blocked(): %s (%d)", strerror(-err), -err);
+
+ if (!silent) {
+ emit_property_changed(conn, device->path,
+ DEVICE_INTERFACE, "Blocked",
+ DBUS_TYPE_BOOLEAN, &device->blocked);
+ device_probe_drivers(device, device->uuids);
+ }
+
+ return 0;
+}
+
+static DBusMessage *set_blocked(DBusConnection *conn, DBusMessage *msg,
+ gboolean value, void *data)
+{
+ struct btd_device *device = data;
+ int err;
+
+ if (value)
+ err = device_block(conn, device);
+ else
+ err = device_unblock(conn, device, FALSE);
+
+ switch (-err) {
+ case 0:
+ return dbus_message_new_method_return(msg);
+ case EINVAL:
+ return btd_error_failed(msg, "Kernel lacks blacklist support");
+ default:
+ return btd_error_failed(msg, strerror(-err));
+ }
+}
+
+static DBusMessage *set_property(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ DBusMessageIter iter;
+ DBusMessageIter sub;
+ const char *property;
+
+ if (!dbus_message_iter_init(msg, &iter))
+ return btd_error_invalid_args(msg);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+ return btd_error_invalid_args(msg);
+
+ dbus_message_iter_get_basic(&iter, &property);
+ dbus_message_iter_next(&iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
+ return btd_error_invalid_args(msg);
+ dbus_message_iter_recurse(&iter, &sub);
+
+ if (g_str_equal("Trusted", property)) {
+ dbus_bool_t value;
+ if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
+ return btd_error_invalid_args(msg);
+ dbus_message_iter_get_basic(&sub, &value);
+
+ return set_trust(conn, msg, value, data);
+ } else if (g_str_equal("Alias", property)) {
+ const char *alias;
+
+ if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING)
+ return btd_error_invalid_args(msg);
+ dbus_message_iter_get_basic(&sub, &alias);
+
+ return set_alias(conn, msg, alias, data);
+ } else if (g_str_equal("Blocked", property)) {
+ dbus_bool_t value;
+
+ if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
+ return btd_error_invalid_args(msg);
+
+ dbus_message_iter_get_basic(&sub, &value);
+
+ return set_blocked(conn, msg, value, data);
+ }
+
+ return btd_error_invalid_args(msg);
+}
+
+static void discover_services_req_exit(DBusConnection *conn, void *user_data)
+{
+ struct browse_req *req = user_data;
+
+ DBG("DiscoverServices requestor exited");
+
+ browse_request_cancel(req);
+}
+
+static DBusMessage *discover_services(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ struct btd_device *device = user_data;
+ const char *pattern;
+ int err;
+
+ if (device->browse)
+ return btd_error_in_progress(msg);
+
+ if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern,
+ DBUS_TYPE_INVALID) == FALSE)
+ return btd_error_invalid_args(msg);
+
+ if (strlen(pattern) == 0) {
+ err = device_browse_sdp(device, conn, msg, NULL, FALSE);
+ if (err < 0)
+ goto fail;
+ } else {
+ uuid_t uuid;
+
+ if (bt_string2uuid(&uuid, pattern) < 0)
+ return btd_error_invalid_args(msg);
+
+ sdp_uuid128_to_uuid(&uuid);
+
+ err = device_browse_sdp(device, conn, msg, &uuid, FALSE);
+ if (err < 0)
+ goto fail;
+ }
+
+ return NULL;
+
+fail:
+ return btd_error_failed(msg, strerror(-err));
+}
+
+static const char *browse_request_get_requestor(struct browse_req *req)
+{
+ if (!req->msg)
+ return NULL;
+
+ return dbus_message_get_sender(req->msg);
+}
+
+static void iter_append_record(DBusMessageIter *dict, uint32_t handle,
+ const char *record)
+{
+ DBusMessageIter entry;
+
+ dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY,
+ NULL, &entry);
+
+ dbus_message_iter_append_basic(&entry, DBUS_TYPE_UINT32, &handle);
+
+ dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &record);
+
+ dbus_message_iter_close_container(dict, &entry);
+}
+
+static void discover_services_reply(struct browse_req *req, int err,
+ sdp_list_t *recs)
+{
+ DBusMessage *reply;
+ DBusMessageIter iter, dict;
+ sdp_list_t *seq;
+
+ if (err) {
+ const char *err_if;
+
+ if (err == -EHOSTDOWN)
+ err_if = ERROR_INTERFACE ".ConnectionAttemptFailed";
+ else
+ err_if = ERROR_INTERFACE ".Failed";
+
+ reply = dbus_message_new_error(req->msg, err_if,
+ strerror(-err));
+ g_dbus_send_message(req->conn, reply);
+ return;
+ }
+
+ reply = dbus_message_new_method_return(req->msg);
+ if (!reply)
+ return;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_UINT32_AS_STRING DBUS_TYPE_STRING_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ for (seq = recs; seq; seq = seq->next) {
+ sdp_record_t *rec = (sdp_record_t *) seq->data;
+ GString *result;
+
+ if (!rec)
+ break;
+
+ result = g_string_new(NULL);
+
+ convert_sdp_record_to_xml(rec, result,
+ (void *) g_string_append);
+
+ if (result->len)
+ iter_append_record(&dict, rec->handle, result->str);
+
+ g_string_free(result, TRUE);
+ }
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ g_dbus_send_message(req->conn, reply);
+}
+
+static DBusMessage *cancel_discover(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ struct btd_device *device = user_data;
+ const char *sender = dbus_message_get_sender(msg);
+ const char *requestor;
+
+ if (!device->browse)
+ return btd_error_does_not_exist(msg);
+
+ if (!dbus_message_is_method_call(device->browse->msg, DEVICE_INTERFACE,
+ "DiscoverServices"))
+ return btd_error_not_authorized(msg);
+
+ requestor = browse_request_get_requestor(device->browse);
+
+ /* only the discover requestor can cancel the inquiry process */
+ if (!requestor || !g_str_equal(requestor, sender))
+ return btd_error_not_authorized(msg);
+
+ discover_services_reply(device->browse, -ECANCELED, NULL);
+
+ browse_request_cancel(device->browse);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static void bonding_request_cancel(struct bonding_req *bonding)
+{
+ struct btd_device *device = bonding->device;
+ struct btd_adapter *adapter = device->adapter;
+
+ adapter_cancel_bonding(adapter, &device->bdaddr);
+}
+
+void device_request_disconnect(struct btd_device *device, DBusMessage *msg)
+{
+ DBusConnection *conn = get_dbus_connection();
+
+ if (device->bonding)
+ bonding_request_cancel(device->bonding);
+
+ if (device->browse) {
+ discover_services_reply(device->browse, -ECANCELED, NULL);
+ browse_request_cancel(device->browse);
+ }
+
+ if (msg)
+ device->disconnects = g_slist_append(device->disconnects,
+ dbus_message_ref(msg));
+
+ if (device->disconn_timer)
+ return;
+
+ while (device->watches) {
+ struct btd_disconnect_data *data = device->watches->data;
+
+ if (data->watch)
+ /* temporary is set if device is going to be removed */
+ data->watch(device, device->temporary,
+ data->user_data);
+
+ /* Check if the watch has been removed by callback function */
+ if (!g_slist_find(device->watches, data))
+ continue;
+
+ device->watches = g_slist_remove(device->watches, data);
+ g_free(data);
+ }
+
+ device->disconn_timer = g_timeout_add_seconds(DISCONNECT_TIMER,
+ do_disconnect, device);
+
+ g_dbus_emit_signal(conn, device->path,
+ DEVICE_INTERFACE, "DisconnectRequested",
+ DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *disconnect(DBusConnection *conn, DBusMessage *msg,
+ void *user_data)
+{
+ struct btd_device *device = user_data;
+
+ if (!device->connected)
+ return btd_error_not_connected(msg);
+
+ device_request_disconnect(device, msg);
+
+ return NULL;
+}
+
+static GDBusMethodTable device_methods[] = {
+ { "GetProperties", "", "a{sv}", get_properties },
+ { "SetProperty", "sv", "", set_property },
+ { "DiscoverServices", "s", "a{us}", discover_services,
+ G_DBUS_METHOD_FLAG_ASYNC},
+ { "CancelDiscovery", "", "", cancel_discover },
+ { "Disconnect", "", "", disconnect,
+ G_DBUS_METHOD_FLAG_ASYNC},
+ { }
+};
+
+static GDBusSignalTable device_signals[] = {
+ { "PropertyChanged", "sv" },
+ { "DisconnectRequested", "" },
+ { }
+};
+
+gboolean device_is_connected(struct btd_device *device)
+{
+ return device->connected;
+}
+
+void device_add_connection(struct btd_device *device, DBusConnection *conn)
+{
+ if (device->connected) {
+ char addr[18];
+ ba2str(&device->bdaddr, addr);
+ error("Device %s is already connected", addr);
+ return;
+ }
+
+ device->connected = TRUE;
+
+ emit_property_changed(conn, device->path,
+ DEVICE_INTERFACE, "Connected",
+ DBUS_TYPE_BOOLEAN, &device->connected);
+}
+
+void device_remove_connection(struct btd_device *device, DBusConnection *conn)
+{
+ if (!device->connected) {
+ char addr[18];
+ ba2str(&device->bdaddr, addr);
+ error("Device %s isn't connected", addr);
+ return;
+ }
+
+ device->connected = FALSE;
+
+ if (device->disconn_timer > 0) {
+ g_source_remove(device->disconn_timer);
+ device->disconn_timer = 0;
+ }
+
+ while (device->disconnects) {
+ DBusMessage *msg = device->disconnects->data;
+
+ g_dbus_send_reply(conn, msg, DBUS_TYPE_INVALID);
+ device->disconnects = g_slist_remove(device->disconnects, msg);
+ }
+
+ emit_property_changed(conn, device->path,
+ DEVICE_INTERFACE, "Connected",
+ DBUS_TYPE_BOOLEAN, &device->connected);
+}
+
+guint device_add_disconnect_watch(struct btd_device *device,
+ disconnect_watch watch, void *user_data,
+ GDestroyNotify destroy)
+{
+ struct btd_disconnect_data *data;
+ static guint id = 0;
+
+ data = g_new0(struct btd_disconnect_data, 1);
+ data->id = ++id;
+ data->watch = watch;
+ data->user_data = user_data;
+ data->destroy = destroy;
+
+ device->watches = g_slist_append(device->watches, data);
+
+ return data->id;
+}
+
+void device_remove_disconnect_watch(struct btd_device *device, guint id)
+{
+ GSList *l;
+
+ for (l = device->watches; l; l = l->next) {
+ struct btd_disconnect_data *data = l->data;
+
+ if (data->id == id) {
+ device->watches = g_slist_remove(device->watches,
+ data);
+ if (data->destroy)
+ data->destroy(data->user_data);
+ g_free(data);
+ return;
+ }
+ }
+}
+
+struct btd_device *device_create(DBusConnection *conn,
+ struct btd_adapter *adapter,
+ const gchar *address, device_type_t type)
+{
+ gchar *address_up;
+ struct btd_device *device;
+ const gchar *adapter_path = adapter_get_path(adapter);
+ bdaddr_t src;
+ char srcaddr[18], alias[MAX_NAME_LENGTH + 1];
+
+ device = g_try_malloc0(sizeof(struct btd_device));
+ if (device == NULL)
+ return NULL;
+
+ address_up = g_ascii_strup(address, -1);
+ device->path = g_strdup_printf("%s/dev_%s", adapter_path, address_up);
+ g_strdelimit(device->path, ":", '_');
+ g_free(address_up);
+
+ DBG("Creating device %s", device->path);
+
+ if (g_dbus_register_interface(conn, device->path, DEVICE_INTERFACE,
+ device_methods, device_signals, NULL,
+ device, device_free) == FALSE) {
+ device_free(device);
+ return NULL;
+ }
+
+ str2ba(address, &device->bdaddr);
+ device->adapter = adapter;
+ device->type = type;
+ adapter_get_address(adapter, &src);
+ ba2str(&src, srcaddr);
+ read_device_name(srcaddr, address, device->name);
+ if (read_device_alias(srcaddr, address, alias, sizeof(alias)) == 0)
+ device->alias = g_strdup(alias);
+ device->trusted = read_trust(&src, address, GLOBAL_TRUST);
+
+ if (read_blocked(&src, &device->bdaddr))
+ device_block(conn, device);
+
+ if (read_link_key(&src, &device->bdaddr, NULL, NULL) == 0)
+ device->paired = TRUE;
+
+ return btd_device_ref(device);
+}
+
+void device_set_name(struct btd_device *device, const char *name)
+{
+ DBusConnection *conn = get_dbus_connection();
+
+ if (strncmp(name, device->name, MAX_NAME_LENGTH) == 0)
+ return;
+
+ strncpy(device->name, name, MAX_NAME_LENGTH);
+
+ emit_property_changed(conn, device->path,
+ DEVICE_INTERFACE, "Name",
+ DBUS_TYPE_STRING, &name);
+
+ if (device->alias != NULL)
+ return;
+
+ emit_property_changed(conn, device->path,
+ DEVICE_INTERFACE, "Alias",
+ DBUS_TYPE_STRING, &name);
+}
+
+void device_get_name(struct btd_device *device, char *name, size_t len)
+{
+ strncpy(name, device->name, len);
+}
+
+device_type_t device_get_type(struct btd_device *device)
+{
+ return device->type;
+}
+
+void device_remove_bonding(struct btd_device *device)
+{
+ char filename[PATH_MAX + 1];
+ char srcaddr[18], dstaddr[18];
+ bdaddr_t bdaddr;
+
+ adapter_get_address(device->adapter, &bdaddr);
+ ba2str(&bdaddr, srcaddr);
+ ba2str(&device->bdaddr, dstaddr);
+
+ create_name(filename, PATH_MAX, STORAGEDIR, srcaddr,
+ "linkkeys");
+
+ /* Delete the link key from storage */
+ textfile_casedel(filename, dstaddr);
+
+ btd_adapter_remove_bonding(device->adapter, &device->bdaddr);
+}
+
+static void device_remove_stored(struct btd_device *device)
+{
+ bdaddr_t src;
+ char addr[18];
+ DBusConnection *conn = get_dbus_connection();
+
+ adapter_get_address(device->adapter, &src);
+ ba2str(&device->bdaddr, addr);
+
+ if (device->paired)
+ device_remove_bonding(device);
+ delete_entry(&src, "profiles", addr);
+ delete_entry(&src, "trusts", addr);
+ delete_entry(&src, "types", addr);
+ delete_entry(&src, "primary", addr);
+ delete_all_records(&src, &device->bdaddr);
+ delete_device_service(&src, &device->bdaddr);
+
+ if (device->blocked)
+ device_unblock(conn, device, TRUE);
+}
+
+void device_remove(struct btd_device *device, gboolean remove_stored)
+{
+
+ DBG("Removing device %s", device->path);
+
+ if (device->agent)
+ agent_free(device->agent);
+
+ if (device->bonding) {
+ uint8_t status;
+
+ if (device->connected)
+ status = HCI_OE_USER_ENDED_CONNECTION;
+ else
+ status = HCI_PAGE_TIMEOUT;
+
+ device_cancel_bonding(device, status);
+ }
+
+ if (device->browse) {
+ discover_services_reply(device->browse, -ECANCELED, NULL);
+ browse_request_cancel(device->browse);
+ }
+
+ if (device->connected)
+ do_disconnect(device);
+
+ if (remove_stored)
+ device_remove_stored(device);
+
+ g_slist_foreach(device->drivers, (GFunc) driver_remove, device);
+ g_slist_free(device->drivers);
+ device->drivers = NULL;
+
+ btd_device_unref(device);
+}
+
+gint device_address_cmp(struct btd_device *device, const gchar *address)
+{
+ char addr[18];
+
+ ba2str(&device->bdaddr, addr);
+ return strcasecmp(addr, address);
+}
+
+static gboolean record_has_uuid(const sdp_record_t *rec,
+ const char *profile_uuid)
+{
+ sdp_list_t *pat;
+
+ for (pat = rec->pattern; pat != NULL; pat = pat->next) {
+ char *uuid;
+ int ret;
+
+ uuid = bt_uuid2string(pat->data);
+ if (!uuid)
+ continue;
+
+ ret = strcasecmp(uuid, profile_uuid);
+
+ g_free(uuid);
+
+ if (ret == 0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static GSList *device_match_pattern(struct btd_device *device,
+ const char *match_uuid,
+ GSList *profiles)
+{
+ GSList *l, *uuids = NULL;
+
+ for (l = profiles; l; l = l->next) {
+ char *profile_uuid = l->data;
+ const sdp_record_t *rec;
+
+ rec = btd_device_get_record(device, profile_uuid);
+ if (!rec)
+ continue;
+
+ if (record_has_uuid(rec, match_uuid))
+ uuids = g_slist_append(uuids, profile_uuid);
+ }
+
+ return uuids;
+}
+
+static GSList *device_match_driver(struct btd_device *device,
+ struct btd_device_driver *driver,
+ GSList *profiles)
+{
+ const char **uuid;
+ GSList *uuids = NULL;
+
+ for (uuid = driver->uuids; *uuid; uuid++) {
+ GSList *match;
+
+ /* skip duplicated uuids */
+ if (g_slist_find_custom(uuids, *uuid,
+ (GCompareFunc) strcasecmp))
+ continue;
+
+ /* match profile driver */
+ match = g_slist_find_custom(profiles, *uuid,
+ (GCompareFunc) strcasecmp);
+ if (match) {
+ uuids = g_slist_append(uuids, match->data);
+ continue;
+ }
+
+ /* match pattern driver */
+ match = device_match_pattern(device, *uuid, profiles);
+ uuids = g_slist_concat(uuids, match);
+ }
+
+ return uuids;
+}
+
+void device_probe_drivers(struct btd_device *device, GSList *profiles)
+{
+ GSList *list;
+ char addr[18];
+ int err;
+
+ ba2str(&device->bdaddr, addr);
+
+ if (device->blocked) {
+ DBG("Skipping drivers for blocked device %s", addr);
+ goto add_uuids;
+ }
+
+ DBG("Probing drivers for %s", addr);
+
+ for (list = device_drivers; list; list = list->next) {
+ struct btd_device_driver *driver = list->data;
+ GSList *probe_uuids;
+
+ probe_uuids = device_match_driver(device, driver, profiles);
+
+ if (!probe_uuids)
+ continue;
+
+ err = driver->probe(device, probe_uuids);
+ if (err < 0) {
+ error("%s driver probe failed for device %s",
+ driver->name, addr);
+ g_slist_free(probe_uuids);
+ continue;
+ }
+
+ device->drivers = g_slist_append(device->drivers, driver);
+ g_slist_free(probe_uuids);
+ }
+
+add_uuids:
+ for (list = profiles; list; list = list->next) {
+ GSList *l = g_slist_find_custom(device->uuids, list->data,
+ (GCompareFunc) strcasecmp);
+ if (l)
+ continue;
+
+ device->uuids = g_slist_insert_sorted(device->uuids,
+ g_strdup(list->data),
+ (GCompareFunc) strcasecmp);
+ }
+}
+
+static void device_remove_drivers(struct btd_device *device, GSList *uuids)
+{
+ struct btd_adapter *adapter = device_get_adapter(device);
+ GSList *list, *next;
+ char srcaddr[18], dstaddr[18];
+ bdaddr_t src;
+ sdp_list_t *records;
+
+ adapter_get_address(adapter, &src);
+ ba2str(&src, srcaddr);
+ ba2str(&device->bdaddr, dstaddr);
+
+ records = read_records(&src, &device->bdaddr);
+
+ DBG("Removing drivers for %s", dstaddr);
+
+ for (list = device->drivers; list; list = next) {
+ struct btd_device_driver *driver = list->data;
+ const char **uuid;
+
+ next = list->next;
+
+ for (uuid = driver->uuids; *uuid; uuid++) {
+ if (!g_slist_find_custom(uuids, *uuid,
+ (GCompareFunc) strcasecmp))
+ continue;
+
+ DBG("UUID %s was removed from device %s",
+ *uuid, dstaddr);
+
+ driver->remove(device);
+ device->drivers = g_slist_remove(device->drivers,
+ driver);
+ break;
+ }
+ }
+
+ for (list = uuids; list; list = list->next) {
+ sdp_record_t *rec;
+
+ device->uuids = g_slist_remove(device->uuids, list->data);
+
+ rec = find_record_in_list(records, list->data);
+ if (!rec)
+ continue;
+
+ delete_record(srcaddr, dstaddr, rec->handle);
+
+ records = sdp_list_remove(records, rec);
+ sdp_record_free(rec);
+
+ }
+
+ if (records)
+ sdp_list_free(records, (sdp_free_func_t) sdp_record_free);
+}
+
+static void services_changed(struct btd_device *device)
+{
+ DBusConnection *conn = get_dbus_connection();
+ char **uuids;
+ GSList *l;
+ int i;
+
+ uuids = g_new0(char *, g_slist_length(device->uuids) + 1);
+ for (i = 0, l = device->uuids; l; l = l->next, i++)
+ uuids[i] = l->data;
+
+ emit_array_property_changed(conn, device->path, DEVICE_INTERFACE,
+ "UUIDs", DBUS_TYPE_STRING, &uuids, i);
+
+ g_free(uuids);
+}
+
+static int rec_cmp(const void *a, const void *b)
+{
+ const sdp_record_t *r1 = a;
+ const sdp_record_t *r2 = b;
+
+ return r1->handle - r2->handle;
+}
+
+static void update_services(struct browse_req *req, sdp_list_t *recs)
+{
+ struct btd_device *device = req->device;
+ struct btd_adapter *adapter = device_get_adapter(device);
+ sdp_list_t *seq;
+ char srcaddr[18], dstaddr[18];
+ bdaddr_t src;
+
+ adapter_get_address(adapter, &src);
+ ba2str(&src, srcaddr);
+ ba2str(&device->bdaddr, dstaddr);
+
+ for (seq = recs; seq; seq = seq->next) {
+ sdp_record_t *rec = (sdp_record_t *) seq->data;
+ sdp_list_t *svcclass = NULL;
+ gchar *profile_uuid;
+ GSList *l;
+
+ if (!rec)
+ break;
+
+ if (sdp_get_service_classes(rec, &svcclass) < 0)
+ continue;
+
+ /* Check for empty service classes list */
+ if (svcclass == NULL) {
+ DBG("Skipping record with no service classes");
+ continue;
+ }
+
+ /* Extract the first element and skip the remainning */
+ profile_uuid = bt_uuid2string(svcclass->data);
+ if (!profile_uuid) {
+ sdp_list_free(svcclass, free);
+ continue;
+ }
+
+ if (!strcasecmp(profile_uuid, PNP_UUID)) {
+ uint16_t source, vendor, product, version;
+ sdp_data_t *pdlist;
+
+ pdlist = sdp_data_get(rec, SDP_ATTR_VENDOR_ID_SOURCE);
+ source = pdlist ? pdlist->val.uint16 : 0x0000;
+
+ pdlist = sdp_data_get(rec, SDP_ATTR_VENDOR_ID);
+ vendor = pdlist ? pdlist->val.uint16 : 0x0000;
+
+ pdlist = sdp_data_get(rec, SDP_ATTR_PRODUCT_ID);
+ product = pdlist ? pdlist->val.uint16 : 0x0000;
+
+ pdlist = sdp_data_get(rec, SDP_ATTR_VERSION);
+ version = pdlist ? pdlist->val.uint16 : 0x0000;
+
+ if (source || vendor || product || version)
+ store_device_id(srcaddr, dstaddr, source,
+ vendor, product, version);
+ }
+
+ /* Check for duplicates */
+ if (sdp_list_find(req->records, rec, rec_cmp)) {
+ g_free(profile_uuid);
+ sdp_list_free(svcclass, free);
+ continue;
+ }
+
+ store_record(srcaddr, dstaddr, rec);
+
+ /* Copy record */
+ req->records = sdp_list_append(req->records,
+ sdp_copy_record(rec));
+
+ l = g_slist_find_custom(device->uuids, profile_uuid,
+ (GCompareFunc) strcmp);
+ if (!l)
+ req->profiles_added =
+ g_slist_append(req->profiles_added,
+ profile_uuid);
+ else {
+ req->profiles_removed =
+ g_slist_remove(req->profiles_removed,
+ l->data);
+ g_free(profile_uuid);
+ }
+
+ sdp_list_free(svcclass, free);
+ }
+}
+
+static void store_profiles(struct btd_device *device)
+{
+ struct btd_adapter *adapter = device->adapter;
+ bdaddr_t src;
+ char *str;
+
+ adapter_get_address(adapter, &src);
+
+ if (!device->uuids) {
+ write_device_profiles(&src, &device->bdaddr, "");
+ return;
+ }
+
+ str = bt_list2string(device->uuids);
+ write_device_profiles(&src, &device->bdaddr, str);
+ g_free(str);
+}
+
+static void create_device_reply(struct btd_device *device, struct browse_req *req)
+{
+ DBusMessage *reply;
+
+ reply = dbus_message_new_method_return(req->msg);
+ if (!reply)
+ return;
+
+ dbus_message_append_args(reply, DBUS_TYPE_OBJECT_PATH, &device->path,
+ DBUS_TYPE_INVALID);
+
+ g_dbus_send_message(req->conn, reply);
+}
+
+static void search_cb(sdp_list_t *recs, int err, gpointer user_data)
+{
+ struct browse_req *req = user_data;
+ struct btd_device *device = req->device;
+ char addr[18];
+
+ ba2str(&device->bdaddr, addr);
+
+ if (err < 0) {
+ error("%s: error updating services: %s (%d)",
+ addr, strerror(-err), -err);
+ goto send_reply;
+ }
+
+ update_services(req, recs);
+
+ if (device->tmp_records)
+ sdp_list_free(device->tmp_records,
+ (sdp_free_func_t) sdp_record_free);
+
+ device->tmp_records = req->records;
+ req->records = NULL;
+
+ if (!req->profiles_added && !req->profiles_removed) {
+ DBG("%s: No service update", addr);
+ goto send_reply;
+ }
+
+ /* Probe matching drivers for services added */
+ if (req->profiles_added)
+ device_probe_drivers(device, req->profiles_added);
+
+ /* Remove drivers for services removed */
+ if (req->profiles_removed)
+ device_remove_drivers(device, req->profiles_removed);
+
+ /* Propagate services changes */
+ services_changed(req->device);
+
+send_reply:
+ if (!req->msg)
+ goto cleanup;
+
+ if (dbus_message_is_method_call(req->msg, DEVICE_INTERFACE,
+ "DiscoverServices"))
+ discover_services_reply(req, err, device->tmp_records);
+ else if (dbus_message_is_method_call(req->msg, ADAPTER_INTERFACE,
+ "CreatePairedDevice"))
+ create_device_reply(device, req);
+ else if (dbus_message_is_method_call(req->msg, ADAPTER_INTERFACE,
+ "CreateDevice")) {
+ if (err < 0) {
+ DBusMessage *reply;
+ reply = btd_error_failed(req->msg, strerror(-err));
+ g_dbus_send_message(req->conn, reply);
+ goto cleanup;
+ }
+
+ create_device_reply(device, req);
+ device_set_temporary(device, FALSE);
+ }
+
+cleanup:
+ if (!device->temporary) {
+ bdaddr_t sba, dba;
+
+ adapter_get_address(device->adapter, &sba);
+ device_get_address(device, &dba);
+
+ store_profiles(device);
+ write_device_type(&sba, &dba, device->type);
+ }
+
+ device->browse = NULL;
+ browse_request_free(req);
+}
+
+static void browse_cb(sdp_list_t *recs, int err, gpointer user_data)
+{
+ struct browse_req *req = user_data;
+ struct btd_device *device = req->device;
+ struct btd_adapter *adapter = device->adapter;
+ bdaddr_t src;
+ uuid_t uuid;
+
+ /* If we have a valid response and req->search_uuid == 2, then L2CAP
+ * UUID & PNP searching was successful -- we are done */
+ if (err < 0 || (req->search_uuid == 2 && req->records)) {
+ if (err == -ECONNRESET && req->reconnect_attempt < 1) {
+ req->search_uuid--;
+ req->reconnect_attempt++;
+ } else
+ goto done;
+ }
+
+ update_services(req, recs);
+
+ adapter_get_address(adapter, &src);
+
+ /* Search for mandatory uuids */
+ if (uuid_list[req->search_uuid]) {
+ sdp_uuid16_create(&uuid, uuid_list[req->search_uuid++]);
+ bt_search_service(&src, &device->bdaddr, &uuid,
+ browse_cb, user_data, NULL);
+ return;
+ }
+
+done:
+ search_cb(recs, err, user_data);
+}
+
+static void init_browse(struct browse_req *req, gboolean reverse)
+{
+ GSList *l;
+
+ /* If we are doing reverse-SDP don't try to detect removed profiles
+ * since some devices hide their service records while they are
+ * connected
+ */
+ if (reverse)
+ return;
+
+ for (l = req->device->uuids; l; l = l->next)
+ req->profiles_removed = g_slist_append(req->profiles_removed,
+ l->data);
+}
+
+static char *primary_list_to_string(GSList *primary_list)
+{
+ GString *services;
+ GSList *l;
+
+ services = g_string_new(NULL);
+
+ for (l = primary_list; l; l = l->next) {
+ struct att_primary *primary = l->data;
+ char service[64];
+
+ memset(service, 0, sizeof(service));
+
+ snprintf(service, sizeof(service), "%04X#%04X#%s ",
+ primary->start, primary->end, primary->uuid);
+
+ services = g_string_append(services, service);
+ }
+
+ return g_string_free(services, FALSE);
+}
+
+static void primary_cb(GSList *services, guint8 status, gpointer user_data)
+{
+ struct browse_req *req = user_data;
+ struct btd_device *device = req->device;
+ struct btd_adapter *adapter = device->adapter;
+ GSList *l, *uuids = NULL;
+ bdaddr_t dba, sba;
+ char *str;
+
+ if (status) {
+ DBusMessage *reply;
+ reply = btd_error_failed(req->msg, att_ecode2str(status));
+ g_dbus_send_message(req->conn, reply);
+ goto done;
+ }
+
+ services_changed(device);
+ device_set_temporary(device, FALSE);
+
+ for (l = services; l; l = l->next) {
+ struct att_primary *prim = l->data;
+ uuids = g_slist_append(uuids, prim->uuid);
+ device_add_primary(device, prim);
+ }
+
+ device_probe_drivers(device, uuids);
+ g_slist_free(uuids);
+
+ create_device_reply(device, req);
+
+ str = primary_list_to_string(services);
+
+ adapter_get_address(adapter, &sba);
+ device_get_address(device, &dba);
+
+ write_device_type(&sba, &dba, device->type);
+ write_device_services(&sba, &dba, str);
+ g_free(str);
+
+done:
+ device->browse = NULL;
+ browse_request_free(req);
+}
+
+static void gatt_connect_cb(GIOChannel *io, GError *gerr, gpointer user_data)
+{
+ struct browse_req *req = user_data;
+ struct btd_device *device = req->device;
+
+ if (gerr) {
+ DBusMessage *reply;
+
+ DBG("%s", gerr->message);
+
+ reply = btd_error_failed(req->msg, gerr->message);
+ g_dbus_send_message(req->conn, reply);
+
+ device->browse = NULL;
+ browse_request_free(req);
+
+ return;
+ }
+
+ req->attrib = g_attrib_new(io);
+ gatt_discover_primary(req->attrib, NULL, primary_cb, req);
+}
+
+int device_browse_primary(struct btd_device *device, DBusConnection *conn,
+ DBusMessage *msg, gboolean secure)
+{
+ struct btd_adapter *adapter = device->adapter;
+ struct browse_req *req;
+ BtIOSecLevel sec_level;
+ bdaddr_t src;
+
+ if (device->browse)
+ return -EBUSY;
+
+ req = g_new0(struct browse_req, 1);
+ req->device = btd_device_ref(device);
+
+ adapter_get_address(adapter, &src);
+
+ sec_level = secure ? BT_IO_SEC_HIGH : BT_IO_SEC_LOW;
+
+ req->io = bt_io_connect(BT_IO_L2CAP, gatt_connect_cb, req, NULL, NULL,
+ BT_IO_OPT_SOURCE_BDADDR, &src,
+ BT_IO_OPT_DEST_BDADDR, &device->bdaddr,
+ BT_IO_OPT_CID, GATT_CID,
+ BT_IO_OPT_SEC_LEVEL, sec_level,
+ BT_IO_OPT_INVALID);
+
+ if (req->io == NULL ) {
+ browse_request_free(req);
+ return -EIO;
+ }
+
+ if (conn == NULL)
+ conn = get_dbus_connection();
+
+ req->conn = dbus_connection_ref(conn);
+ device->browse = req;
+
+ if (msg) {
+ const char *sender = dbus_message_get_sender(msg);
+
+ req->msg = dbus_message_ref(msg);
+ /* Track the request owner to cancel it
+ * automatically if the owner exits */
+ req->listener_id = g_dbus_add_disconnect_watch(conn,
+ sender,
+ discover_services_req_exit,
+ req, NULL);
+ }
+
+ return 0;
+}
+
+int device_browse_sdp(struct btd_device *device, DBusConnection *conn,
+ DBusMessage *msg, uuid_t *search, gboolean reverse)
+{
+ struct btd_adapter *adapter = device->adapter;
+ struct browse_req *req;
+ bt_callback_t cb;
+ bdaddr_t src;
+ uuid_t uuid;
+ int err;
+
+ if (device->browse)
+ return -EBUSY;
+
+ adapter_get_address(adapter, &src);
+
+ req = g_new0(struct browse_req, 1);
+ req->device = btd_device_ref(device);
+ if (search) {
+ memcpy(&uuid, search, sizeof(uuid_t));
+ cb = search_cb;
+ } else {
+ sdp_uuid16_create(&uuid, uuid_list[req->search_uuid++]);
+ init_browse(req, reverse);
+ cb = browse_cb;
+ }
+
+ err = bt_search_service(&src, &device->bdaddr, &uuid, cb, req, NULL);
+ if (err < 0) {
+ browse_request_free(req);
+ return err;
+ }
+
+ if (conn == NULL)
+ conn = get_dbus_connection();
+
+ req->conn = dbus_connection_ref(conn);
+ device->browse = req;
+
+ if (msg) {
+ const char *sender = dbus_message_get_sender(msg);
+
+ req->msg = dbus_message_ref(msg);
+ /* Track the request owner to cancel it
+ * automatically if the owner exits */
+ req->listener_id = g_dbus_add_disconnect_watch(conn,
+ sender,
+ discover_services_req_exit,
+ req, NULL);
+ }
+
+ return err;
+}
+
+struct btd_adapter *device_get_adapter(struct btd_device *device)
+{
+ if (!device)
+ return NULL;
+
+ return device->adapter;
+}
+
+void device_get_address(struct btd_device *device, bdaddr_t *bdaddr)
+{
+ bacpy(bdaddr, &device->bdaddr);
+}
+
+const gchar *device_get_path(struct btd_device *device)
+{
+ if (!device)
+ return NULL;
+
+ return device->path;
+}
+
+struct agent *device_get_agent(struct btd_device *device)
+{
+ if (!device)
+ return NULL;
+
+ if (device->agent)
+ return device->agent;
+
+ return adapter_get_agent(device->adapter);
+}
+
+gboolean device_is_busy(struct btd_device *device)
+{
+ return device->browse ? TRUE : FALSE;
+}
+
+gboolean device_is_temporary(struct btd_device *device)
+{
+ return device->temporary;
+}
+
+void device_set_temporary(struct btd_device *device, gboolean temporary)
+{
+ if (!device)
+ return;
+
+ DBG("temporary %d", temporary);
+
+ device->temporary = temporary;
+}
+
+void device_set_type(struct btd_device *device, device_type_t type)
+{
+ if (!device)
+ return;
+
+ device->type = type;
+}
+
+static gboolean start_discovery(gpointer user_data)
+{
+ struct btd_device *device = user_data;
+
+ device_browse_sdp(device, NULL, NULL, NULL, TRUE);
+
+ device->discov_timer = 0;
+
+ return FALSE;
+}
+
+static DBusMessage *new_authentication_return(DBusMessage *msg, int status)
+{
+ switch (status) {
+ case 0x00: /* success */
+ return dbus_message_new_method_return(msg);
+
+ case 0x04: /* page timeout */
+ return dbus_message_new_error(msg,
+ ERROR_INTERFACE ".ConnectionAttemptFailed",
+ "Page Timeout");
+ case 0x08: /* connection timeout */
+ return dbus_message_new_error(msg,
+ ERROR_INTERFACE ".ConnectionAttemptFailed",
+ "Connection Timeout");
+ case 0x10: /* connection accept timeout */
+ case 0x22: /* LMP response timeout */
+ case 0x28: /* instant passed - is this a timeout? */
+ return dbus_message_new_error(msg,
+ ERROR_INTERFACE ".AuthenticationTimeout",
+ "Authentication Timeout");
+ case 0x17: /* too frequent pairing attempts */
+ return dbus_message_new_error(msg,
+ ERROR_INTERFACE ".RepeatedAttempts",
+ "Repeated Attempts");
+
+ case 0x06:
+ case 0x18: /* pairing not allowed (e.g. gw rejected attempt) */
+ return dbus_message_new_error(msg,
+ ERROR_INTERFACE ".AuthenticationRejected",
+ "Authentication Rejected");
+
+ case 0x07: /* memory capacity */
+ case 0x09: /* connection limit */
+ case 0x0a: /* synchronous connection limit */
+ case 0x0d: /* limited resources */
+ case 0x13: /* user ended the connection */
+ case 0x14: /* terminated due to low resources */
+ case 0x16: /* connection terminated */
+ return dbus_message_new_error(msg,
+ ERROR_INTERFACE ".AuthenticationCanceled",
+ "Authentication Canceled");
+
+ case 0x05: /* authentication failure */
+ case 0x0E: /* rejected due to security reasons - is this auth failure? */
+ case 0x25: /* encryption mode not acceptable - is this auth failure? */
+ case 0x26: /* link key cannot be changed - is this auth failure? */
+ case 0x29: /* pairing with unit key unsupported - is this auth failure? */
+ case 0x2f: /* insufficient security - is this auth failure? */
+ default:
+ return dbus_message_new_error(msg,
+ ERROR_INTERFACE ".AuthenticationFailed",
+ "Authentication Failed");
+ }
+}
+
+static void bonding_request_free(struct bonding_req *bonding)
+{
+ struct btd_device *device;
+
+ if (!bonding)
+ return;
+
+ if (bonding->listener_id)
+ g_dbus_remove_watch(bonding->conn, bonding->listener_id);
+
+ if (bonding->msg)
+ dbus_message_unref(bonding->msg);
+
+ if (bonding->conn)
+ dbus_connection_unref(bonding->conn);
+
+ if (bonding->io)
+ g_io_channel_unref(bonding->io);
+
+ device = bonding->device;
+ g_free(bonding);
+
+ if (!device)
+ return;
+
+ device->bonding = NULL;
+
+ adapter_resume_discovery(device->adapter);
+
+ if (!device->agent)
+ return;
+
+ agent_cancel(device->agent);
+ agent_free(device->agent);
+ device->agent = NULL;
+}
+
+void device_set_paired(struct btd_device *device, gboolean value)
+{
+ DBusConnection *conn = get_dbus_connection();
+
+ if (device->paired == value)
+ return;
+
+ device->paired = value;
+
+ emit_property_changed(conn, device->path, DEVICE_INTERFACE, "Paired",
+ DBUS_TYPE_BOOLEAN, &value);
+}
+
+static void device_agent_removed(struct agent *agent, void *user_data)
+{
+ struct btd_device *device = user_data;
+
+ device->agent = NULL;
+
+ if (device->authr)
+ device->authr->agent = NULL;
+}
+
+static struct bonding_req *bonding_request_new(DBusConnection *conn,
+ DBusMessage *msg,
+ struct btd_device *device,
+ const char *agent_path,
+ uint8_t capability)
+{
+ struct bonding_req *bonding;
+ const char *name = dbus_message_get_sender(msg);
+ struct agent *agent;
+ char addr[18];
+
+ ba2str(&device->bdaddr, addr);
+ DBG("Requesting bonding for %s", addr);
+
+ if (!agent_path)
+ goto proceed;
+
+ agent = agent_create(device->adapter, name, agent_path,
+ capability,
+ device_agent_removed,
+ device);
+ if (!agent) {
+ error("Unable to create a new agent");
+ return NULL;
+ }
+
+ device->agent = agent;
+
+ DBG("Temporary agent registered for %s at %s:%s",
+ addr, name, agent_path);
+
+proceed:
+ bonding = g_new0(struct bonding_req, 1);
+
+ bonding->conn = dbus_connection_ref(conn);
+ bonding->msg = dbus_message_ref(msg);
+
+ adapter_suspend_discovery(device->adapter);
+
+ return bonding;
+}
+
+static void create_bond_req_exit(DBusConnection *conn, void *user_data)
+{
+ struct btd_device *device = user_data;
+ char addr[18];
+
+ ba2str(&device->bdaddr, addr);
+ DBG("%s: requestor exited before bonding was completed", addr);
+
+ if (device->authr)
+ device_cancel_authentication(device, FALSE);
+
+ if (device->bonding) {
+ device->bonding->listener_id = 0;
+ device_request_disconnect(device, NULL);
+ }
+}
+
+DBusMessage *device_create_bonding(struct btd_device *device,
+ DBusConnection *conn,
+ DBusMessage *msg,
+ const char *agent_path,
+ uint8_t capability)
+{
+ char filename[PATH_MAX + 1];
+ char *str, srcaddr[18], dstaddr[18];
+ struct btd_adapter *adapter = device->adapter;
+ struct bonding_req *bonding;
+ bdaddr_t src;
+ int err;
+
+ adapter_get_address(adapter, &src);
+ ba2str(&src, srcaddr);
+ ba2str(&device->bdaddr, dstaddr);
+
+ if (device->bonding)
+ return btd_error_in_progress(msg);
+
+ /* check if a link key already exists */
+ create_name(filename, PATH_MAX, STORAGEDIR, srcaddr,
+ "linkkeys");
+
+ str = textfile_caseget(filename, dstaddr);
+ if (str) {
+ free(str);
+ return btd_error_already_exists(msg);
+ }
+
+ err = adapter_create_bonding(adapter, &device->bdaddr, capability);
+ if (err < 0)
+ return btd_error_failed(msg, strerror(-err));
+
+ bonding = bonding_request_new(conn, msg, device, agent_path,
+ capability);
+ if (!bonding) {
+ adapter_cancel_bonding(adapter, &device->bdaddr);
+ return NULL;
+ }
+
+ bonding->listener_id = g_dbus_add_disconnect_watch(conn,
+ dbus_message_get_sender(msg),
+ create_bond_req_exit, device,
+ NULL);
+
+ device->bonding = bonding;
+ bonding->device = device;
+
+ return NULL;
+}
+
+void device_simple_pairing_complete(struct btd_device *device, uint8_t status)
+{
+ struct authentication_req *auth = device->authr;
+
+ if (auth && auth->type == AUTH_TYPE_NOTIFY && auth->agent)
+ agent_cancel(auth->agent);
+}
+
+static void device_auth_req_free(struct btd_device *device)
+{
+ g_free(device->authr);
+ device->authr = NULL;
+}
+
+void device_bonding_complete(struct btd_device *device, uint8_t status)
+{
+ struct bonding_req *bonding = device->bonding;
+ struct authentication_req *auth = device->authr;
+
+ DBG("bonding %p status 0x%02x", bonding, status);
+
+ if (auth && auth->type == AUTH_TYPE_NOTIFY && auth->agent)
+ agent_cancel(auth->agent);
+
+ if (status) {
+ device_cancel_authentication(device, TRUE);
+ device_cancel_bonding(device, status);
+ return;
+ }
+
+ device_auth_req_free(device);
+
+ /* If we're already paired nothing more is needed */
+ if (device->paired)
+ return;
+
+ device_set_paired(device, TRUE);
+
+ /* If we were initiators start service discovery immediately.
+ * However if the other end was the initator wait a few seconds
+ * before SDP. This is due to potential IOP issues if the other
+ * end starts doing SDP at the same time as us */
+ if (bonding) {
+ DBG("Proceeding with service discovery");
+ /* If we are initiators remove any discovery timer and just
+ * start discovering services directly */
+ if (device->discov_timer) {
+ g_source_remove(device->discov_timer);
+ device->discov_timer = 0;
+ }
+
+ device_browse_sdp(device, bonding->conn, bonding->msg,
+ NULL, FALSE);
+
+ bonding_request_free(bonding);
+ } else {
+ if (!device->browse && !device->discov_timer &&
+ main_opts.reverse_sdp) {
+ /* If we are not initiators and there is no currently
+ * active discovery or discovery timer, set discovery
+ * timer */
+ DBG("setting timer for reverse service discovery");
+ device->discov_timer = g_timeout_add_seconds(
+ DISCOVERY_TIMER,
+ start_discovery,
+ device);
+ }
+ }
+}
+
+gboolean device_is_creating(struct btd_device *device, const char *sender)
+{
+ DBusMessage *msg;
+
+ if (device->bonding && device->bonding->msg)
+ msg = device->bonding->msg;
+ else if (device->browse && device->browse->msg)
+ msg = device->browse->msg;
+ else
+ return FALSE;
+
+ if (!dbus_message_is_method_call(msg, ADAPTER_INTERFACE,
+ "CreatePairedDevice") &&
+ !dbus_message_is_method_call(msg, ADAPTER_INTERFACE,
+ "CreateDevice"))
+ return FALSE;
+
+ if (sender == NULL)
+ return TRUE;
+
+ return g_str_equal(sender, dbus_message_get_sender(msg));
+}
+
+gboolean device_is_bonding(struct btd_device *device, const char *sender)
+{
+ struct bonding_req *bonding = device->bonding;
+
+ if (!device->bonding)
+ return FALSE;
+
+ if (!sender)
+ return TRUE;
+
+ return g_str_equal(sender, dbus_message_get_sender(bonding->msg));
+}
+
+void device_cancel_bonding(struct btd_device *device, uint8_t status)
+{
+ struct bonding_req *bonding = device->bonding;
+ DBusMessage *reply;
+ char addr[18];
+
+ if (!bonding)
+ return;
+
+ ba2str(&device->bdaddr, addr);
+ DBG("Canceling bonding request for %s", addr);
+
+ if (device->authr)
+ device_cancel_authentication(device, FALSE);
+
+ reply = new_authentication_return(bonding->msg, status);
+ g_dbus_send_message(bonding->conn, reply);
+
+ bonding_request_cancel(bonding);
+ bonding_request_free(bonding);
+}
+
+static void pincode_cb(struct agent *agent, DBusError *err,
+ const char *pincode, void *data)
+{
+ struct authentication_req *auth = data;
+ struct btd_device *device = auth->device;
+
+ /* No need to reply anything if the authentication already failed */
+ if (auth->cb == NULL)
+ return;
+
+ ((agent_pincode_cb) auth->cb)(agent, err, pincode, device);
+
+ device->authr->cb = NULL;
+ device->authr->agent = NULL;
+}
+
+static void confirm_cb(struct agent *agent, DBusError *err, void *data)
+{
+ struct authentication_req *auth = data;
+ struct btd_device *device = auth->device;
+
+ /* No need to reply anything if the authentication already failed */
+ if (auth->cb == NULL)
+ return;
+
+ ((agent_cb) auth->cb)(agent, err, device);
+
+ device->authr->cb = NULL;
+ device->authr->agent = NULL;
+}
+
+static void passkey_cb(struct agent *agent, DBusError *err,
+ uint32_t passkey, void *data)
+{
+ struct authentication_req *auth = data;
+ struct btd_device *device = auth->device;
+
+ /* No need to reply anything if the authentication already failed */
+ if (auth->cb == NULL)
+ return;
+
+ ((agent_passkey_cb) auth->cb)(agent, err, passkey, device);
+
+ device->authr->cb = NULL;
+ device->authr->agent = NULL;
+}
+
+int device_request_authentication(struct btd_device *device, auth_type_t type,
+ uint32_t passkey, void *cb)
+{
+ struct authentication_req *auth;
+ struct agent *agent;
+ char addr[18];
+ int err;
+
+ ba2str(&device->bdaddr, addr);
+ DBG("Requesting agent authentication for %s", addr);
+
+ if (device->authr) {
+ error("Authentication already requested for %s", addr);
+ return -EALREADY;
+ }
+
+ agent = device_get_agent(device);
+ if (!agent) {
+ error("No agent available for request type %d", type);
+ return -EPERM;
+ }
+
+ auth = g_new0(struct authentication_req, 1);
+ auth->agent = agent;
+ auth->device = device;
+ auth->cb = cb;
+ auth->type = type;
+ device->authr = auth;
+
+ switch (type) {
+ case AUTH_TYPE_PINCODE:
+ err = agent_request_pincode(agent, device, pincode_cb,
+ auth, NULL);
+ break;
+ case AUTH_TYPE_PASSKEY:
+ err = agent_request_passkey(agent, device, passkey_cb,
+ auth, NULL);
+ break;
+ case AUTH_TYPE_CONFIRM:
+ err = agent_request_confirmation(agent, device, passkey,
+ confirm_cb, auth, NULL);
+ break;
+ case AUTH_TYPE_NOTIFY:
+ err = agent_display_passkey(agent, device, passkey);
+ break;
+ default:
+ err = -EINVAL;
+ }
+
+ if (err < 0) {
+ error("Failed requesting authentication");
+ device_auth_req_free(device);
+ }
+
+ return err;
+}
+
+static void cancel_authentication(struct authentication_req *auth)
+{
+ struct btd_device *device;
+ struct agent *agent;
+ DBusError err;
+
+ if (!auth || !auth->cb)
+ return;
+
+ device = auth->device;
+ agent = auth->agent;
+
+ dbus_error_init(&err);
+ dbus_set_error_const(&err, "org.bluez.Error.Canceled", NULL);
+
+ switch (auth->type) {
+ case AUTH_TYPE_PINCODE:
+ ((agent_pincode_cb) auth->cb)(agent, &err, NULL, device);
+ break;
+ case AUTH_TYPE_CONFIRM:
+ ((agent_cb) auth->cb)(agent, &err, device);
+ break;
+ case AUTH_TYPE_PASSKEY:
+ ((agent_passkey_cb) auth->cb)(agent, &err, 0, device);
+ break;
+ case AUTH_TYPE_NOTIFY:
+ /* User Notify doesn't require any reply */
+ break;
+ }
+
+ dbus_error_free(&err);
+ auth->cb = NULL;
+}
+
+void device_cancel_authentication(struct btd_device *device, gboolean aborted)
+{
+ struct authentication_req *auth = device->authr;
+ char addr[18];
+
+ if (!auth)
+ return;
+
+ ba2str(&device->bdaddr, addr);
+ DBG("Canceling authentication request for %s", addr);
+
+ if (auth->agent)
+ agent_cancel(auth->agent);
+
+ if (!aborted)
+ cancel_authentication(auth);
+
+ device_auth_req_free(device);
+}
+
+gboolean device_is_authenticating(struct btd_device *device)
+{
+ return (device->authr != NULL);
+}
+
+gboolean device_is_authorizing(struct btd_device *device)
+{
+ return device->authorizing;
+}
+
+void device_set_authorizing(struct btd_device *device, gboolean auth)
+{
+ device->authorizing = auth;
+}
+
+void btd_device_add_service(struct btd_device *device, const char *path)
+{
+ if (g_slist_find_custom(device->services, path, (GCompareFunc) strcmp))
+ return;
+
+ device->services = g_slist_append(device->services, g_strdup(path));
+}
+
+void device_add_primary(struct btd_device *device, struct att_primary *prim)
+{
+ device->primaries = g_slist_append(device->primaries, prim);
+}
+
+GSList *btd_device_get_primaries(struct btd_device *device)
+{
+ return device->primaries;
+}
+
+void btd_device_add_uuid(struct btd_device *device, const char *uuid)
+{
+ GSList *uuid_list;
+ char *new_uuid;
+
+ if (g_slist_find_custom(device->uuids, uuid,
+ (GCompareFunc) strcasecmp))
+ return;
+
+ new_uuid = g_strdup(uuid);
+ uuid_list = g_slist_append(NULL, new_uuid);
+
+ device_probe_drivers(device, uuid_list);
+
+ g_free(new_uuid);
+ g_slist_free(uuid_list);
+
+ store_profiles(device);
+ services_changed(device);
+}
+
+const sdp_record_t *btd_device_get_record(struct btd_device *device,
+ const char *uuid)
+{
+ bdaddr_t src;
+
+ if (device->tmp_records) {
+ const sdp_record_t *record;
+
+ record = find_record_in_list(device->tmp_records, uuid);
+ if (record != NULL)
+ return record;
+ }
+
+ adapter_get_address(device->adapter, &src);
+
+ device->tmp_records = read_records(&src, &device->bdaddr);
+ if (!device->tmp_records)
+ return NULL;
+
+ return find_record_in_list(device->tmp_records, uuid);
+}
+
+int btd_register_device_driver(struct btd_device_driver *driver)
+{
+ device_drivers = g_slist_append(device_drivers, driver);
+
+ return 0;
+}
+
+void btd_unregister_device_driver(struct btd_device_driver *driver)
+{
+ device_drivers = g_slist_remove(device_drivers, driver);
+}
+
+struct btd_device *btd_device_ref(struct btd_device *device)
+{
+ device->ref++;
+
+ DBG("%p: ref=%d", device, device->ref);
+
+ return device;
+}
+
+void btd_device_unref(struct btd_device *device)
+{
+ DBusConnection *conn = get_dbus_connection();
+ gchar *path;
+
+ device->ref--;
+
+ DBG("%p: ref=%d", device, device->ref);
+
+ if (device->ref > 0)
+ return;
+
+ path = g_strdup(device->path);
+
+ g_dbus_unregister_interface(conn, path, DEVICE_INTERFACE);
+
+ g_free(path);
+}