diff options
Diffstat (limited to 'src/device.c')
-rw-r--r-- | src/device.c | 2388 |
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); +} |