| /* |
| * |
| * 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 <inttypes.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <stdbool.h> |
| #include <sys/ioctl.h> |
| #include <sys/file.h> |
| #include <sys/stat.h> |
| #include <dirent.h> |
| |
| #include <bluetooth/bluetooth.h> |
| #include <bluetooth/hci.h> |
| #include <bluetooth/sdp.h> |
| #include <bluetooth/sdp_lib.h> |
| |
| #include <glib.h> |
| #include <dbus/dbus.h> |
| #include <gdbus/gdbus.h> |
| |
| #include "log.h" |
| #include "textfile.h" |
| |
| #include "lib/uuid.h" |
| #include "lib/mgmt.h" |
| #include "src/shared/mgmt.h" |
| #include "src/shared/util.h" |
| |
| #include "hcid.h" |
| #include "sdpd.h" |
| #include "adapter.h" |
| #include "device.h" |
| #include "profile.h" |
| #include "dbus-common.h" |
| #include "error.h" |
| #include "uuid-helper.h" |
| #include "agent.h" |
| #include "storage.h" |
| #include "attrib/gattrib.h" |
| #include "attrib/att.h" |
| #include "attrib/gatt.h" |
| #include "attrib-server.h" |
| #include "eir.h" |
| |
| #define ADAPTER_INTERFACE "org.bluez.Adapter1" |
| |
| #define MODE_OFF 0x00 |
| #define MODE_CONNECTABLE 0x01 |
| #define MODE_DISCOVERABLE 0x02 |
| #define MODE_UNKNOWN 0xff |
| |
| #define CONN_SCAN_TIMEOUT (3) |
| #define IDLE_DISCOV_TIMEOUT (5) |
| #define TEMP_DEV_TIMEOUT (3 * 60) |
| #define BONDING_TIMEOUT (2 * 60) |
| |
| static DBusConnection *dbus_conn = NULL; |
| |
| static GList *adapter_list = NULL; |
| static unsigned int adapter_remaining = 0; |
| static bool powering_down = false; |
| |
| static GSList *adapters = NULL; |
| |
| static struct mgmt *mgmt_master = NULL; |
| |
| static uint8_t mgmt_version = 0; |
| static uint8_t mgmt_revision = 0; |
| |
| static GSList *adapter_drivers = NULL; |
| |
| struct link_key_info { |
| bdaddr_t bdaddr; |
| unsigned char key[16]; |
| uint8_t type; |
| uint8_t pin_len; |
| }; |
| |
| struct smp_ltk_info { |
| bdaddr_t bdaddr; |
| uint8_t bdaddr_type; |
| uint8_t authenticated; |
| bool master; |
| uint8_t enc_size; |
| uint16_t ediv; |
| uint64_t rand; |
| uint8_t val[16]; |
| }; |
| |
| struct irk_info { |
| bdaddr_t bdaddr; |
| uint8_t bdaddr_type; |
| uint8_t val[16]; |
| }; |
| |
| struct watch_client { |
| struct btd_adapter *adapter; |
| char *owner; |
| guint watch; |
| }; |
| |
| struct service_auth { |
| guint id; |
| unsigned int svc_id; |
| service_auth_cb cb; |
| void *user_data; |
| const char *uuid; |
| struct btd_device *device; |
| struct btd_adapter *adapter; |
| struct agent *agent; /* NULL for queued auths */ |
| }; |
| |
| struct btd_adapter_pin_cb_iter { |
| GSList *it; /* current callback function */ |
| unsigned int attempt; /* numer of times it() was called */ |
| /* When the iterator reaches the end, it is NULL and attempt is 0 */ |
| }; |
| |
| struct btd_adapter { |
| int ref_count; |
| |
| uint16_t dev_id; |
| struct mgmt *mgmt; |
| |
| bdaddr_t bdaddr; /* controller Bluetooth address */ |
| uint32_t dev_class; /* controller class of device */ |
| char *name; /* controller device name */ |
| char *short_name; /* controller short name */ |
| uint32_t supported_settings; /* controller supported settings */ |
| uint32_t current_settings; /* current controller settings */ |
| |
| char *path; /* adapter object path */ |
| uint8_t major_class; /* configured major class */ |
| uint8_t minor_class; /* configured minor class */ |
| char *system_name; /* configured system name */ |
| char *modalias; /* device id (modalias) */ |
| bool stored_discoverable; /* stored discoverable mode */ |
| uint32_t discoverable_timeout; /* discoverable time(sec) */ |
| uint32_t pairable_timeout; /* pairable time(sec) */ |
| |
| char *current_alias; /* current adapter name alias */ |
| char *stored_alias; /* stored adapter name alias */ |
| |
| bool discovering; /* discovering property state */ |
| uint8_t discovery_type; /* current active discovery type */ |
| uint8_t discovery_enable; /* discovery enabled/disabled */ |
| bool discovery_suspended; /* discovery has been suspended */ |
| GSList *discovery_list; /* list of discovery clients */ |
| GSList *discovery_found; /* list of found devices */ |
| guint discovery_idle_timeout; /* timeout between discovery runs */ |
| guint passive_scan_timeout; /* timeout between passive scans */ |
| guint temp_devices_timeout; /* timeout for temporary devices */ |
| |
| guint pairable_timeout_id; /* pairable timeout id */ |
| guint auth_idle_id; /* Pending authorization dequeue */ |
| GQueue *auths; /* Ongoing and pending auths */ |
| bool pincode_requested; /* PIN requested during last bonding */ |
| GSList *connections; /* Connected devices */ |
| GSList *devices; /* Devices structure pointers */ |
| GSList *connect_list; /* Devices to connect when found */ |
| struct btd_device *connect_le; /* LE device waiting to be connected */ |
| sdp_list_t *services; /* Services associated to adapter */ |
| |
| gboolean initialized; |
| |
| GSList *pin_callbacks; |
| |
| GSList *drivers; |
| GSList *profiles; |
| |
| struct oob_handler *oob_handler; |
| |
| unsigned int load_ltks_id; |
| guint load_ltks_timeout; |
| |
| unsigned int confirm_name_id; |
| guint confirm_name_timeout; |
| |
| unsigned int pair_device_id; |
| guint pair_device_timeout; |
| |
| bool is_default; /* true if adapter is default one */ |
| }; |
| |
| static struct btd_adapter *btd_adapter_lookup(uint16_t index) |
| { |
| GList *list; |
| |
| for (list = g_list_first(adapter_list); list; |
| list = g_list_next(list)) { |
| struct btd_adapter *adapter = list->data; |
| |
| if (adapter->dev_id == index) |
| return adapter; |
| } |
| |
| return NULL; |
| } |
| |
| struct btd_adapter *btd_adapter_get_default(void) |
| { |
| GList *list; |
| |
| for (list = g_list_first(adapter_list); list; |
| list = g_list_next(list)) { |
| struct btd_adapter *adapter = list->data; |
| |
| if (adapter->is_default) |
| return adapter; |
| } |
| |
| return NULL; |
| } |
| |
| bool btd_adapter_is_default(struct btd_adapter *adapter) |
| { |
| if (!adapter) |
| return false; |
| |
| return adapter->is_default; |
| } |
| |
| uint16_t btd_adapter_get_index(struct btd_adapter *adapter) |
| { |
| if (!adapter) |
| return MGMT_INDEX_NONE; |
| |
| return adapter->dev_id; |
| } |
| |
| static gboolean process_auth_queue(gpointer user_data); |
| |
| static void dev_class_changed_callback(uint16_t index, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| const struct mgmt_cod *rp = param; |
| uint8_t appearance[3]; |
| uint32_t dev_class; |
| |
| if (length < sizeof(*rp)) { |
| error("Wrong size of class of device changed parameters"); |
| return; |
| } |
| |
| dev_class = rp->val[0] | (rp->val[1] << 8) | (rp->val[2] << 16); |
| |
| if (dev_class == adapter->dev_class) |
| return; |
| |
| DBG("Class: 0x%06x", dev_class); |
| |
| adapter->dev_class = dev_class; |
| |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "Class"); |
| |
| appearance[0] = rp->val[0]; |
| appearance[1] = rp->val[1] & 0x1f; /* removes service class */ |
| appearance[2] = rp->val[2]; |
| |
| attrib_gap_set(adapter, GATT_CHARAC_APPEARANCE, appearance, 2); |
| } |
| |
| static void set_dev_class_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| error("Failed to set device class: %s (0x%02x)", |
| mgmt_errstr(status), status); |
| return; |
| } |
| |
| /* |
| * The parameters are identical and also the task that is |
| * required in both cases. So it is safe to just call the |
| * event handling functions here. |
| */ |
| dev_class_changed_callback(adapter->dev_id, length, param, adapter); |
| } |
| |
| static void set_dev_class(struct btd_adapter *adapter) |
| { |
| struct mgmt_cp_set_dev_class cp; |
| |
| /* |
| * If the controller does not support BR/EDR operation, |
| * there is no point in trying to set a major and minor |
| * class value. |
| * |
| * This is an optimization for Low Energy only controllers. |
| */ |
| if (!(adapter->supported_settings & MGMT_SETTING_BREDR)) |
| return; |
| |
| memset(&cp, 0, sizeof(cp)); |
| |
| /* |
| * Silly workaround for a really stupid kernel bug :( |
| * |
| * All current kernel versions assign the major and minor numbers |
| * straight to dev_class[0] and dev_class[1] without considering |
| * the proper bit shifting. |
| * |
| * To make this work, shift the value in userspace for now until |
| * we get a fixed kernel version. |
| */ |
| cp.major = adapter->major_class & 0x1f; |
| cp.minor = adapter->minor_class << 2; |
| |
| DBG("sending set device class command for index %u", adapter->dev_id); |
| |
| if (mgmt_send(adapter->mgmt, MGMT_OP_SET_DEV_CLASS, |
| adapter->dev_id, sizeof(cp), &cp, |
| set_dev_class_complete, adapter, NULL) > 0) |
| return; |
| |
| error("Failed to set class of device for index %u", adapter->dev_id); |
| } |
| |
| void btd_adapter_set_class(struct btd_adapter *adapter, uint8_t major, |
| uint8_t minor) |
| { |
| if (adapter->major_class == major && adapter->minor_class == minor) |
| return; |
| |
| DBG("class: major %u minor %u", major, minor); |
| |
| adapter->major_class = major; |
| adapter->minor_class = minor; |
| |
| set_dev_class(adapter); |
| } |
| |
| static uint8_t get_mode(const char *mode) |
| { |
| if (strcasecmp("off", mode) == 0) |
| return MODE_OFF; |
| else if (strcasecmp("connectable", mode) == 0) |
| return MODE_CONNECTABLE; |
| else if (strcasecmp("discoverable", mode) == 0) |
| return MODE_DISCOVERABLE; |
| else |
| return MODE_UNKNOWN; |
| } |
| |
| static void store_adapter_info(struct btd_adapter *adapter) |
| { |
| GKeyFile *key_file; |
| char filename[PATH_MAX + 1]; |
| char address[18]; |
| char *str; |
| gsize length = 0; |
| gboolean discoverable; |
| |
| key_file = g_key_file_new(); |
| |
| if (adapter->pairable_timeout != main_opts.pairto) |
| g_key_file_set_integer(key_file, "General", "PairableTimeout", |
| adapter->pairable_timeout); |
| |
| if ((adapter->current_settings & MGMT_SETTING_DISCOVERABLE) && |
| !adapter->discoverable_timeout) |
| discoverable = TRUE; |
| else |
| discoverable = FALSE; |
| |
| g_key_file_set_boolean(key_file, "General", "Discoverable", |
| discoverable); |
| |
| if (adapter->discoverable_timeout != main_opts.discovto) |
| g_key_file_set_integer(key_file, "General", |
| "DiscoverableTimeout", |
| adapter->discoverable_timeout); |
| |
| if (adapter->stored_alias) |
| g_key_file_set_string(key_file, "General", "Alias", |
| adapter->stored_alias); |
| |
| ba2str(&adapter->bdaddr, address); |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/settings", address); |
| filename[PATH_MAX] = '\0'; |
| |
| create_file(filename, S_IRUSR | S_IWUSR); |
| |
| str = g_key_file_to_data(key_file, &length, NULL); |
| g_file_set_contents(filename, str, length, NULL); |
| g_free(str); |
| |
| g_key_file_free(key_file); |
| } |
| |
| static void trigger_pairable_timeout(struct btd_adapter *adapter); |
| static void adapter_start(struct btd_adapter *adapter); |
| static void adapter_stop(struct btd_adapter *adapter); |
| static void trigger_passive_scanning(struct btd_adapter *adapter); |
| |
| static void settings_changed(struct btd_adapter *adapter, uint32_t settings) |
| { |
| uint32_t changed_mask; |
| |
| changed_mask = adapter->current_settings ^ settings; |
| |
| adapter->current_settings = settings; |
| |
| DBG("Changed settings: 0x%08x", changed_mask); |
| |
| if (changed_mask & MGMT_SETTING_POWERED) { |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "Powered"); |
| |
| if (adapter->current_settings & MGMT_SETTING_POWERED) { |
| adapter_start(adapter); |
| } else { |
| adapter_stop(adapter); |
| |
| if (powering_down) { |
| adapter_remaining--; |
| |
| if (!adapter_remaining) |
| btd_exit(); |
| } |
| } |
| } |
| |
| if (changed_mask & MGMT_SETTING_LE) { |
| if ((adapter->current_settings & MGMT_SETTING_POWERED) && |
| (adapter->current_settings & MGMT_SETTING_LE)) |
| trigger_passive_scanning(adapter); |
| } |
| |
| if (changed_mask & MGMT_SETTING_CONNECTABLE) |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "Connectable"); |
| |
| if (changed_mask & MGMT_SETTING_DISCOVERABLE) { |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "Discoverable"); |
| |
| store_adapter_info(adapter); |
| } |
| |
| if (changed_mask & MGMT_SETTING_PAIRABLE) { |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "Pairable"); |
| |
| trigger_pairable_timeout(adapter); |
| } |
| } |
| |
| static void new_settings_callback(uint16_t index, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| uint32_t settings; |
| |
| if (length < sizeof(settings)) { |
| error("Wrong size of new settings parameters"); |
| return; |
| } |
| |
| settings = get_le32(param); |
| |
| if (settings == adapter->current_settings) |
| return; |
| |
| DBG("Settings: 0x%08x", settings); |
| |
| settings_changed(adapter, settings); |
| } |
| |
| static void set_mode_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| error("Failed to set mode: %s (0x%02x)", |
| mgmt_errstr(status), status); |
| return; |
| } |
| |
| /* |
| * The parameters are identical and also the task that is |
| * required in both cases. So it is safe to just call the |
| * event handling functions here. |
| */ |
| new_settings_callback(adapter->dev_id, length, param, adapter); |
| } |
| |
| static bool set_mode(struct btd_adapter *adapter, uint16_t opcode, |
| uint8_t mode) |
| { |
| struct mgmt_mode cp; |
| |
| memset(&cp, 0, sizeof(cp)); |
| cp.val = mode; |
| |
| DBG("sending set mode command for index %u", adapter->dev_id); |
| |
| if (mgmt_send(adapter->mgmt, opcode, |
| adapter->dev_id, sizeof(cp), &cp, |
| set_mode_complete, adapter, NULL) > 0) |
| return true; |
| |
| error("Failed to set mode for index %u", adapter->dev_id); |
| |
| return false; |
| } |
| |
| static bool set_discoverable(struct btd_adapter *adapter, uint8_t mode, |
| uint16_t timeout) |
| { |
| struct mgmt_cp_set_discoverable cp; |
| |
| memset(&cp, 0, sizeof(cp)); |
| cp.val = mode; |
| cp.timeout = htobs(timeout); |
| |
| DBG("sending set mode command for index %u", adapter->dev_id); |
| |
| if (mgmt_send(adapter->mgmt, MGMT_OP_SET_DISCOVERABLE, |
| adapter->dev_id, sizeof(cp), &cp, |
| set_mode_complete, adapter, NULL) > 0) |
| return true; |
| |
| error("Failed to set mode for index %u", adapter->dev_id); |
| |
| return false; |
| } |
| |
| static gboolean pairable_timeout_handler(gpointer user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| adapter->pairable_timeout_id = 0; |
| |
| set_mode(adapter, MGMT_OP_SET_PAIRABLE, 0x00); |
| |
| return FALSE; |
| } |
| |
| static void trigger_pairable_timeout(struct btd_adapter *adapter) |
| { |
| if (adapter->pairable_timeout_id > 0) { |
| g_source_remove(adapter->pairable_timeout_id); |
| adapter->pairable_timeout_id = 0; |
| } |
| |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "PairableTimeout"); |
| |
| if (!(adapter->current_settings & MGMT_SETTING_PAIRABLE)) |
| return; |
| |
| if (adapter->pairable_timeout > 0) |
| g_timeout_add_seconds(adapter->pairable_timeout, |
| pairable_timeout_handler, adapter); |
| } |
| |
| static void local_name_changed_callback(uint16_t index, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| const struct mgmt_cp_set_local_name *rp = param; |
| |
| if (length < sizeof(*rp)) { |
| error("Wrong size of local name changed parameters"); |
| return; |
| } |
| |
| if (!g_strcmp0(adapter->short_name, (const char *) rp->short_name) && |
| !g_strcmp0(adapter->name, (const char *) rp->name)) |
| return; |
| |
| DBG("Name: %s", rp->name); |
| DBG("Short name: %s", rp->short_name); |
| |
| g_free(adapter->name); |
| adapter->name = g_strdup((const char *) rp->name); |
| |
| g_free(adapter->short_name); |
| adapter->short_name = g_strdup((const char *) rp->short_name); |
| |
| /* |
| * Changing the name (even manually via HCI) will update the |
| * current alias property. |
| * |
| * In case the name is empty, use the short name. |
| * |
| * There is a difference between the stored alias (which is |
| * configured by the user) and the current alias. The current |
| * alias is temporary for the lifetime of the daemon. |
| */ |
| if (adapter->name && adapter->name[0] != '\0') { |
| g_free(adapter->current_alias); |
| adapter->current_alias = g_strdup(adapter->name); |
| } else { |
| g_free(adapter->current_alias); |
| adapter->current_alias = g_strdup(adapter->short_name); |
| } |
| |
| DBG("Current alias: %s", adapter->current_alias); |
| |
| if (!adapter->current_alias) |
| return; |
| |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "Alias"); |
| |
| attrib_gap_set(adapter, GATT_CHARAC_DEVICE_NAME, |
| (const uint8_t *) adapter->current_alias, |
| strlen(adapter->current_alias)); |
| } |
| |
| static void set_local_name_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| error("Failed to set local name: %s (0x%02x)", |
| mgmt_errstr(status), status); |
| return; |
| } |
| |
| /* |
| * The parameters are identical and also the task that is |
| * required in both cases. So it is safe to just call the |
| * event handling functions here. |
| */ |
| local_name_changed_callback(adapter->dev_id, length, param, adapter); |
| } |
| |
| static int set_name(struct btd_adapter *adapter, const char *name) |
| { |
| struct mgmt_cp_set_local_name cp; |
| char maxname[MAX_NAME_LENGTH + 1]; |
| |
| memset(maxname, 0, sizeof(maxname)); |
| strncpy(maxname, name, MAX_NAME_LENGTH); |
| |
| if (!g_utf8_validate(maxname, -1, NULL)) { |
| error("Name change failed: supplied name isn't valid UTF-8"); |
| return -EINVAL; |
| } |
| |
| memset(&cp, 0, sizeof(cp)); |
| strncpy((char *) cp.name, maxname, sizeof(cp.name) - 1); |
| |
| DBG("sending set local name command for index %u", adapter->dev_id); |
| |
| if (mgmt_send(adapter->mgmt, MGMT_OP_SET_LOCAL_NAME, |
| adapter->dev_id, sizeof(cp), &cp, |
| set_local_name_complete, adapter, NULL) > 0) |
| return 0; |
| |
| error("Failed to set local name for index %u", adapter->dev_id); |
| |
| return -EIO; |
| } |
| |
| int adapter_set_name(struct btd_adapter *adapter, const char *name) |
| { |
| if (g_strcmp0(adapter->system_name, name) == 0) |
| return 0; |
| |
| DBG("name: %s", name); |
| |
| g_free(adapter->system_name); |
| adapter->system_name = g_strdup(name); |
| |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "Name"); |
| |
| /* alias is preferred over system name */ |
| if (adapter->stored_alias) |
| return 0; |
| |
| DBG("alias: %s", name); |
| |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "Alias"); |
| |
| return set_name(adapter, name); |
| } |
| |
| struct btd_device *btd_adapter_find_device(struct btd_adapter *adapter, |
| const bdaddr_t *dst, |
| uint8_t bdaddr_type) |
| { |
| struct device_addr_type addr; |
| struct btd_device *device; |
| GSList *list; |
| |
| if (!adapter) |
| return NULL; |
| |
| bacpy(&addr.bdaddr, dst); |
| addr.bdaddr_type = bdaddr_type; |
| |
| list = g_slist_find_custom(adapter->devices, &addr, |
| device_addr_type_cmp); |
| if (!list) |
| return NULL; |
| |
| device = list->data; |
| |
| return device; |
| } |
| |
| static void uuid_to_uuid128(uuid_t *uuid128, const uuid_t *uuid) |
| { |
| if (uuid->type == SDP_UUID16) |
| sdp_uuid16_to_uuid128(uuid128, uuid); |
| else if (uuid->type == SDP_UUID32) |
| sdp_uuid32_to_uuid128(uuid128, uuid); |
| else |
| memcpy(uuid128, uuid, sizeof(*uuid)); |
| } |
| |
| static bool is_supported_uuid(const uuid_t *uuid) |
| { |
| uuid_t tmp; |
| |
| /* mgmt versions from 1.3 onwards support all types of UUIDs */ |
| if (MGMT_VERSION(mgmt_version, mgmt_revision) >= MGMT_VERSION(1, 3)) |
| return true; |
| |
| uuid_to_uuid128(&tmp, uuid); |
| |
| if (!sdp_uuid128_to_uuid(&tmp)) |
| return false; |
| |
| if (tmp.type != SDP_UUID16) |
| return false; |
| |
| return true; |
| } |
| |
| static void add_uuid_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| error("Failed to add UUID: %s (0x%02x)", |
| mgmt_errstr(status), status); |
| return; |
| } |
| |
| /* |
| * The parameters are identical and also the task that is |
| * required in both cases. So it is safe to just call the |
| * event handling functions here. |
| */ |
| dev_class_changed_callback(adapter->dev_id, length, param, adapter); |
| |
| if (adapter->initialized) |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "UUIDs"); |
| } |
| |
| static int add_uuid(struct btd_adapter *adapter, uuid_t *uuid, uint8_t svc_hint) |
| { |
| struct mgmt_cp_add_uuid cp; |
| uuid_t uuid128; |
| uint128_t uint128; |
| |
| if (!is_supported_uuid(uuid)) { |
| warn("Ignoring unsupported UUID for addition"); |
| return 0; |
| } |
| |
| uuid_to_uuid128(&uuid128, uuid); |
| |
| ntoh128((uint128_t *) uuid128.value.uuid128.data, &uint128); |
| htob128(&uint128, (uint128_t *) cp.uuid); |
| cp.svc_hint = svc_hint; |
| |
| DBG("sending add uuid command for index %u", adapter->dev_id); |
| |
| if (mgmt_send(adapter->mgmt, MGMT_OP_ADD_UUID, |
| adapter->dev_id, sizeof(cp), &cp, |
| add_uuid_complete, adapter, NULL) > 0) |
| return 0; |
| |
| error("Failed to add UUID for index %u", adapter->dev_id); |
| |
| return -EIO; |
| } |
| |
| static void remove_uuid_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| error("Failed to remove UUID: %s (0x%02x)", |
| mgmt_errstr(status), status); |
| return; |
| } |
| |
| /* |
| * The parameters are identical and also the task that is |
| * required in both cases. So it is safe to just call the |
| * event handling functions here. |
| */ |
| dev_class_changed_callback(adapter->dev_id, length, param, adapter); |
| |
| if (adapter->initialized) |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "UUIDs"); |
| } |
| |
| static int remove_uuid(struct btd_adapter *adapter, uuid_t *uuid) |
| { |
| struct mgmt_cp_remove_uuid cp; |
| uuid_t uuid128; |
| uint128_t uint128; |
| |
| if (!is_supported_uuid(uuid)) { |
| warn("Ignoring unsupported UUID for removal"); |
| return 0; |
| } |
| |
| uuid_to_uuid128(&uuid128, uuid); |
| |
| ntoh128((uint128_t *) uuid128.value.uuid128.data, &uint128); |
| htob128(&uint128, (uint128_t *) cp.uuid); |
| |
| DBG("sending remove uuid command for index %u", adapter->dev_id); |
| |
| if (mgmt_send(adapter->mgmt, MGMT_OP_REMOVE_UUID, |
| adapter->dev_id, sizeof(cp), &cp, |
| remove_uuid_complete, adapter, NULL) > 0) |
| return 0; |
| |
| error("Failed to remove UUID for index %u", adapter->dev_id); |
| |
| return -EIO; |
| } |
| |
| static void clear_uuids_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| error("Failed to clear UUIDs: %s (0x%02x)", |
| mgmt_errstr(status), status); |
| return; |
| } |
| |
| /* |
| * The parameters are identical and also the task that is |
| * required in both cases. So it is safe to just call the |
| * event handling functions here. |
| */ |
| dev_class_changed_callback(adapter->dev_id, length, param, adapter); |
| } |
| |
| static int clear_uuids(struct btd_adapter *adapter) |
| { |
| struct mgmt_cp_remove_uuid cp; |
| |
| memset(&cp, 0, sizeof(cp)); |
| |
| DBG("sending clear uuids command for index %u", adapter->dev_id); |
| |
| if (mgmt_send(adapter->mgmt, MGMT_OP_REMOVE_UUID, |
| adapter->dev_id, sizeof(cp), &cp, |
| clear_uuids_complete, adapter, NULL) > 0) |
| return 0; |
| |
| error("Failed to clear UUIDs for index %u", adapter->dev_id); |
| |
| return -EIO; |
| } |
| |
| static uint8_t get_uuid_mask(uuid_t *uuid) |
| { |
| if (uuid->type != SDP_UUID16) |
| return 0; |
| |
| switch (uuid->value.uuid16) { |
| case DIALUP_NET_SVCLASS_ID: |
| case CIP_SVCLASS_ID: |
| return 0x42; /* Telephony & Networking */ |
| case IRMC_SYNC_SVCLASS_ID: |
| case OBEX_OBJPUSH_SVCLASS_ID: |
| case OBEX_FILETRANS_SVCLASS_ID: |
| case IRMC_SYNC_CMD_SVCLASS_ID: |
| case PBAP_PSE_SVCLASS_ID: |
| return 0x10; /* Object Transfer */ |
| case HEADSET_SVCLASS_ID: |
| case HANDSFREE_SVCLASS_ID: |
| return 0x20; /* Audio */ |
| case CORDLESS_TELEPHONY_SVCLASS_ID: |
| case INTERCOM_SVCLASS_ID: |
| case FAX_SVCLASS_ID: |
| case SAP_SVCLASS_ID: |
| /* |
| * Setting the telephony bit for the handsfree audio gateway |
| * role is not required by the HFP specification, but the |
| * Nokia 616 carkit is just plain broken! It will refuse |
| * pairing without this bit set. |
| */ |
| case HANDSFREE_AGW_SVCLASS_ID: |
| return 0x40; /* Telephony */ |
| case AUDIO_SOURCE_SVCLASS_ID: |
| case VIDEO_SOURCE_SVCLASS_ID: |
| return 0x08; /* Capturing */ |
| case AUDIO_SINK_SVCLASS_ID: |
| case VIDEO_SINK_SVCLASS_ID: |
| return 0x04; /* Rendering */ |
| case PANU_SVCLASS_ID: |
| case NAP_SVCLASS_ID: |
| case GN_SVCLASS_ID: |
| return 0x02; /* Networking */ |
| default: |
| return 0; |
| } |
| } |
| |
| static int uuid_cmp(const void *a, const void *b) |
| { |
| const sdp_record_t *rec = a; |
| const uuid_t *uuid = b; |
| |
| return sdp_uuid_cmp(&rec->svclass, uuid); |
| } |
| |
| static void adapter_service_insert(struct btd_adapter *adapter, sdp_record_t *rec) |
| { |
| sdp_list_t *browse_list = NULL; |
| uuid_t browse_uuid; |
| gboolean new_uuid; |
| |
| DBG("%s", adapter->path); |
| |
| /* skip record without a browse group */ |
| if (sdp_get_browse_groups(rec, &browse_list) < 0) { |
| DBG("skipping record without browse group"); |
| return; |
| } |
| |
| sdp_uuid16_create(&browse_uuid, PUBLIC_BROWSE_GROUP); |
| |
| /* skip record without public browse group */ |
| if (!sdp_list_find(browse_list, &browse_uuid, sdp_uuid_cmp)) |
| goto done; |
| |
| if (sdp_list_find(adapter->services, &rec->svclass, uuid_cmp) == NULL) |
| new_uuid = TRUE; |
| else |
| new_uuid = FALSE; |
| |
| adapter->services = sdp_list_insert_sorted(adapter->services, rec, |
| record_sort); |
| |
| if (new_uuid) { |
| uint8_t svc_hint = get_uuid_mask(&rec->svclass); |
| add_uuid(adapter, &rec->svclass, svc_hint); |
| } |
| |
| done: |
| sdp_list_free(browse_list, free); |
| } |
| |
| int adapter_service_add(struct btd_adapter *adapter, sdp_record_t *rec) |
| { |
| int ret; |
| |
| DBG("%s", adapter->path); |
| |
| ret = add_record_to_server(&adapter->bdaddr, rec); |
| if (ret < 0) |
| return ret; |
| |
| adapter_service_insert(adapter, rec); |
| |
| return 0; |
| } |
| |
| void adapter_service_remove(struct btd_adapter *adapter, uint32_t handle) |
| { |
| sdp_record_t *rec = sdp_record_find(handle); |
| |
| DBG("%s", adapter->path); |
| |
| if (!rec) |
| return; |
| |
| adapter->services = sdp_list_remove(adapter->services, rec); |
| |
| if (sdp_list_find(adapter->services, &rec->svclass, uuid_cmp) == NULL) |
| remove_uuid(adapter, &rec->svclass); |
| |
| remove_record_from_server(rec->handle); |
| } |
| |
| static struct btd_device *adapter_create_device(struct btd_adapter *adapter, |
| const bdaddr_t *bdaddr, |
| uint8_t bdaddr_type) |
| { |
| struct btd_device *device; |
| |
| device = device_create(adapter, bdaddr, bdaddr_type); |
| if (!device) |
| return NULL; |
| |
| btd_device_set_temporary(device, TRUE); |
| |
| adapter->devices = g_slist_append(adapter->devices, device); |
| |
| return device; |
| } |
| |
| static void service_auth_cancel(struct service_auth *auth) |
| { |
| DBusError derr; |
| |
| if (auth->svc_id > 0) |
| device_remove_svc_complete_callback(auth->device, |
| auth->svc_id); |
| |
| dbus_error_init(&derr); |
| dbus_set_error_const(&derr, ERROR_INTERFACE ".Canceled", NULL); |
| |
| auth->cb(&derr, auth->user_data); |
| |
| dbus_error_free(&derr); |
| |
| if (auth->agent != NULL) |
| agent_unref(auth->agent); |
| |
| g_free(auth); |
| } |
| |
| void btd_adapter_remove_device(struct btd_adapter *adapter, |
| struct btd_device *dev) |
| { |
| GList *l; |
| |
| adapter->connect_list = g_slist_remove(adapter->connect_list, dev); |
| |
| adapter->devices = g_slist_remove(adapter->devices, dev); |
| |
| adapter->discovery_found = g_slist_remove(adapter->discovery_found, |
| dev); |
| |
| adapter->connections = g_slist_remove(adapter->connections, dev); |
| |
| if (adapter->connect_le == dev) |
| adapter->connect_le = NULL; |
| |
| l = adapter->auths->head; |
| while (l != NULL) { |
| struct service_auth *auth = l->data; |
| GList *next = g_list_next(l); |
| |
| if (auth->device != dev) { |
| l = next; |
| continue; |
| } |
| |
| g_queue_delete_link(adapter->auths, l); |
| l = next; |
| |
| service_auth_cancel(auth); |
| } |
| |
| device_remove(dev, TRUE); |
| } |
| |
| struct btd_device *btd_adapter_get_device(struct btd_adapter *adapter, |
| const bdaddr_t *addr, |
| uint8_t addr_type) |
| { |
| struct btd_device *device; |
| |
| if (!adapter) |
| return NULL; |
| |
| device = btd_adapter_find_device(adapter, addr, addr_type); |
| if (device) |
| return device; |
| |
| return adapter_create_device(adapter, addr, addr_type); |
| } |
| |
| sdp_list_t *btd_adapter_get_services(struct btd_adapter *adapter) |
| { |
| return adapter->services; |
| } |
| |
| static void passive_scanning_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| const struct mgmt_cp_start_discovery *rp = param; |
| |
| DBG("status 0x%02x", status); |
| |
| if (length < sizeof(*rp)) { |
| error("Wrong size of start scanning return parameters"); |
| return; |
| } |
| |
| if (status == MGMT_STATUS_SUCCESS) { |
| adapter->discovery_type = rp->type; |
| adapter->discovery_enable = 0x01; |
| } |
| } |
| |
| static gboolean passive_scanning_timeout(gpointer user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| struct mgmt_cp_start_discovery cp; |
| |
| adapter->passive_scan_timeout = 0; |
| |
| cp.type = (1 << BDADDR_LE_PUBLIC) | (1 << BDADDR_LE_RANDOM); |
| |
| mgmt_send(adapter->mgmt, MGMT_OP_START_DISCOVERY, |
| adapter->dev_id, sizeof(cp), &cp, |
| passive_scanning_complete, adapter, NULL); |
| |
| return FALSE; |
| } |
| |
| static void trigger_passive_scanning(struct btd_adapter *adapter) |
| { |
| if (!(adapter->current_settings & MGMT_SETTING_LE)) |
| return; |
| |
| DBG(""); |
| |
| if (adapter->passive_scan_timeout > 0) { |
| g_source_remove(adapter->passive_scan_timeout); |
| adapter->passive_scan_timeout = 0; |
| } |
| |
| /* |
| * If any client is running a discovery right now, then do not |
| * even try to start passive scanning. |
| * |
| * The discovery procedure is using interleaved scanning and |
| * thus will discover Low Energy devices as well. |
| */ |
| if (adapter->discovery_list) |
| return; |
| |
| if (adapter->discovery_enable == 0x01) |
| return; |
| |
| /* |
| * In case the discovery is suspended (for example for an ongoing |
| * pairing attempt), then also do not start passive scanning. |
| */ |
| if (adapter->discovery_suspended) |
| return; |
| |
| /* |
| * If the list of connectable Low Energy devices is empty, |
| * then do not start passive scanning. |
| */ |
| if (!adapter->connect_list) |
| return; |
| |
| adapter->passive_scan_timeout = g_timeout_add_seconds(CONN_SCAN_TIMEOUT, |
| passive_scanning_timeout, adapter); |
| } |
| |
| static void stop_passive_scanning_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| struct btd_device *dev; |
| int err; |
| |
| DBG("status 0x%02x (%s)", status, mgmt_errstr(status)); |
| |
| dev = adapter->connect_le; |
| adapter->connect_le = NULL; |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| error("Stopping passive scanning failed: %s", |
| mgmt_errstr(status)); |
| return; |
| } |
| |
| adapter->discovery_type = 0x00; |
| adapter->discovery_enable = 0x00; |
| |
| if (!dev) { |
| DBG("Device removed while stopping passive scanning"); |
| trigger_passive_scanning(adapter); |
| return; |
| } |
| |
| err = device_connect_le(dev); |
| if (err < 0) { |
| error("LE auto connection failed: %s (%d)", |
| strerror(-err), -err); |
| trigger_passive_scanning(adapter); |
| } |
| } |
| |
| static void stop_passive_scanning(struct btd_adapter *adapter) |
| { |
| struct mgmt_cp_stop_discovery cp; |
| |
| DBG(""); |
| |
| /* If there are any normal discovery clients passive scanning |
| * wont be running */ |
| if (adapter->discovery_list) |
| return; |
| |
| if (adapter->discovery_enable == 0x00) |
| return; |
| |
| cp.type = adapter->discovery_type; |
| |
| mgmt_send(adapter->mgmt, MGMT_OP_STOP_DISCOVERY, |
| adapter->dev_id, sizeof(cp), &cp, |
| stop_passive_scanning_complete, adapter, NULL); |
| } |
| |
| static void cancel_passive_scanning(struct btd_adapter *adapter) |
| { |
| if (!(adapter->current_settings & MGMT_SETTING_LE)) |
| return; |
| |
| DBG(""); |
| |
| if (adapter->passive_scan_timeout > 0) { |
| g_source_remove(adapter->passive_scan_timeout); |
| adapter->passive_scan_timeout = 0; |
| } |
| } |
| |
| static void trigger_start_discovery(struct btd_adapter *adapter, guint delay); |
| |
| static void start_discovery_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| const struct mgmt_cp_start_discovery *rp = param; |
| |
| DBG("status 0x%02x", status); |
| |
| if (length < sizeof(*rp)) { |
| error("Wrong size of start discovery return parameters"); |
| return; |
| } |
| |
| if (status == MGMT_STATUS_SUCCESS) { |
| adapter->discovery_type = rp->type; |
| adapter->discovery_enable = 0x01; |
| |
| if (adapter->discovering) |
| return; |
| |
| adapter->discovering = true; |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "Discovering"); |
| return; |
| } |
| |
| /* |
| * In case the restart of the discovery failed, then just trigger |
| * it for the next idle timeout again. |
| */ |
| trigger_start_discovery(adapter, IDLE_DISCOV_TIMEOUT * 2); |
| } |
| |
| static gboolean start_discovery_timeout(gpointer user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| struct mgmt_cp_start_discovery cp; |
| uint8_t new_type; |
| |
| DBG(""); |
| |
| adapter->discovery_idle_timeout = 0; |
| |
| if (adapter->current_settings & MGMT_SETTING_BREDR) |
| new_type = (1 << BDADDR_BREDR); |
| else |
| new_type = 0; |
| |
| if (adapter->current_settings & MGMT_SETTING_LE) |
| new_type |= (1 << BDADDR_LE_PUBLIC) | (1 << BDADDR_LE_RANDOM); |
| |
| if (adapter->discovery_enable == 0x01) { |
| /* |
| * If there is an already running discovery and it has the |
| * same type, then just keep it. |
| */ |
| if (adapter->discovery_type == new_type) { |
| if (adapter->discovering) |
| return FALSE; |
| |
| adapter->discovering = true; |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "Discovering"); |
| return FALSE; |
| } |
| |
| /* |
| * Otherwise the current discovery must be stopped. So |
| * queue up a stop discovery command. |
| * |
| * This can happen if a passive scanning for Low Energy |
| * devices is ongoing. |
| */ |
| cp.type = adapter->discovery_type; |
| |
| mgmt_send(adapter->mgmt, MGMT_OP_STOP_DISCOVERY, |
| adapter->dev_id, sizeof(cp), &cp, |
| NULL, NULL, NULL); |
| } |
| |
| cp.type = new_type; |
| |
| mgmt_send(adapter->mgmt, MGMT_OP_START_DISCOVERY, |
| adapter->dev_id, sizeof(cp), &cp, |
| start_discovery_complete, adapter, NULL); |
| |
| return FALSE; |
| } |
| |
| static void trigger_start_discovery(struct btd_adapter *adapter, guint delay) |
| { |
| |
| DBG(""); |
| |
| cancel_passive_scanning(adapter); |
| |
| if (adapter->discovery_idle_timeout > 0) { |
| g_source_remove(adapter->discovery_idle_timeout); |
| adapter->discovery_idle_timeout = 0; |
| } |
| |
| /* |
| * If the controller got powered down in between, then ensure |
| * that we do not keep trying to restart discovery. |
| * |
| * This is safe-guard and should actually never trigger. |
| */ |
| if (!(adapter->current_settings & MGMT_SETTING_POWERED)) |
| return; |
| |
| adapter->discovery_idle_timeout = g_timeout_add_seconds(delay, |
| start_discovery_timeout, adapter); |
| } |
| |
| static void suspend_discovery_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| DBG("status 0x%02x", status); |
| |
| if (status == MGMT_STATUS_SUCCESS) { |
| adapter->discovery_type = 0x00; |
| adapter->discovery_enable = 0x00; |
| return; |
| } |
| } |
| |
| static void suspend_discovery(struct btd_adapter *adapter) |
| { |
| struct mgmt_cp_stop_discovery cp; |
| |
| DBG(""); |
| |
| adapter->discovery_suspended = true; |
| |
| /* |
| * If there are no clients discovering right now, then there is |
| * also nothing to suspend. |
| */ |
| if (!adapter->discovery_list) |
| return; |
| |
| /* |
| * In case of being inside the idle phase, make sure to remove |
| * the timeout to not trigger a restart. |
| * |
| * The restart will be triggered when the discovery is resumed. |
| */ |
| if (adapter->discovery_idle_timeout > 0) { |
| g_source_remove(adapter->discovery_idle_timeout); |
| adapter->discovery_idle_timeout = 0; |
| } |
| |
| if (adapter->discovery_enable == 0x00) |
| return; |
| |
| cp.type = adapter->discovery_type; |
| |
| mgmt_send(adapter->mgmt, MGMT_OP_STOP_DISCOVERY, |
| adapter->dev_id, sizeof(cp), &cp, |
| suspend_discovery_complete, adapter, NULL); |
| } |
| |
| static void resume_discovery(struct btd_adapter *adapter) |
| { |
| DBG(""); |
| |
| adapter->discovery_suspended = false; |
| |
| /* |
| * If there are no clients discovering right now, then there is |
| * also nothing to resume. |
| */ |
| if (!adapter->discovery_list) |
| return; |
| |
| /* |
| * Treat a suspended discovery session the same as extra long |
| * idle time for a normal discovery. So just trigger the default |
| * restart procedure. |
| */ |
| trigger_start_discovery(adapter, IDLE_DISCOV_TIMEOUT); |
| } |
| |
| static void discovering_callback(uint16_t index, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_ev_discovering *ev = param; |
| struct btd_adapter *adapter = user_data; |
| |
| if (length < sizeof(*ev)) { |
| error("Too small discovering event"); |
| return; |
| } |
| |
| DBG("hci%u type %u discovering %u", adapter->dev_id, |
| ev->type, ev->discovering); |
| |
| if (adapter->discovery_enable == ev->discovering) |
| return; |
| |
| adapter->discovery_type = ev->type; |
| adapter->discovery_enable = ev->discovering; |
| |
| /* |
| * Check for existing discoveries triggered by client applications |
| * and ignore all others. |
| * |
| * If there are no clients, then it is good idea to trigger a |
| * passive scanning attempt. |
| */ |
| if (!adapter->discovery_list) { |
| trigger_passive_scanning(adapter); |
| return; |
| } |
| |
| if (adapter->discovery_suspended) |
| return; |
| |
| switch (adapter->discovery_enable) { |
| case 0x00: |
| trigger_start_discovery(adapter, IDLE_DISCOV_TIMEOUT); |
| break; |
| |
| case 0x01: |
| if (adapter->discovery_idle_timeout > 0) { |
| g_source_remove(adapter->discovery_idle_timeout); |
| adapter->discovery_idle_timeout = 0; |
| } |
| break; |
| } |
| } |
| |
| static void stop_discovery_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| DBG("status 0x%02x", status); |
| |
| if (status == MGMT_STATUS_SUCCESS) { |
| adapter->discovery_type = 0x00; |
| adapter->discovery_enable = 0x00; |
| |
| adapter->discovering = false; |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "Discovering"); |
| |
| trigger_passive_scanning(adapter); |
| } |
| } |
| |
| static int compare_sender(gconstpointer a, gconstpointer b) |
| { |
| const struct watch_client *client = a; |
| const char *sender = b; |
| |
| return g_strcmp0(client->owner, sender); |
| } |
| |
| static void invalidate_rssi(gpointer a) |
| { |
| struct btd_device *dev = a; |
| |
| device_set_rssi(dev, 0); |
| } |
| |
| static void discovery_cleanup(struct btd_adapter *adapter) |
| { |
| g_slist_free_full(adapter->discovery_found, invalidate_rssi); |
| adapter->discovery_found = NULL; |
| } |
| |
| static gboolean remove_temp_devices(gpointer user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| GSList *l, *next; |
| |
| DBG("%s", adapter->path); |
| |
| adapter->temp_devices_timeout = 0; |
| |
| for (l = adapter->devices; l != NULL; l = next) { |
| struct btd_device *dev = l->data; |
| |
| next = g_slist_next(l); |
| |
| if (device_is_temporary(dev)) |
| btd_adapter_remove_device(adapter, dev); |
| } |
| |
| return FALSE; |
| } |
| |
| static void discovery_destroy(void *user_data) |
| { |
| struct watch_client *client = user_data; |
| struct btd_adapter *adapter = client->adapter; |
| |
| DBG("owner %s", client->owner); |
| |
| adapter->discovery_list = g_slist_remove(adapter->discovery_list, |
| client); |
| |
| g_free(client->owner); |
| g_free(client); |
| |
| /* |
| * If there are other client discoveries in progress, then leave |
| * it active. If not, then make sure to stop the restart timeout. |
| */ |
| if (adapter->discovery_list) |
| return; |
| |
| adapter->discovery_type = 0x00; |
| |
| if (adapter->discovery_idle_timeout > 0) { |
| g_source_remove(adapter->discovery_idle_timeout); |
| adapter->discovery_idle_timeout = 0; |
| } |
| |
| if (adapter->temp_devices_timeout > 0) { |
| g_source_remove(adapter->temp_devices_timeout); |
| adapter->temp_devices_timeout = 0; |
| } |
| |
| discovery_cleanup(adapter); |
| |
| adapter->temp_devices_timeout = g_timeout_add_seconds(TEMP_DEV_TIMEOUT, |
| remove_temp_devices, adapter); |
| } |
| |
| static void discovery_disconnect(DBusConnection *conn, void *user_data) |
| { |
| struct watch_client *client = user_data; |
| struct btd_adapter *adapter = client->adapter; |
| struct mgmt_cp_stop_discovery cp; |
| |
| DBG("owner %s", client->owner); |
| |
| adapter->discovery_list = g_slist_remove(adapter->discovery_list, |
| client); |
| |
| /* |
| * There is no need for extra cleanup of the client since that |
| * will be done by the destroy callback. |
| * |
| * However in case this is the last client, the discovery in |
| * the kernel needs to be disabled. |
| */ |
| if (adapter->discovery_list) |
| return; |
| |
| /* |
| * In the idle phase of a discovery, there is no need to stop it |
| * and so it is enough to send out the signal and just return. |
| */ |
| if (adapter->discovery_enable == 0x00) { |
| adapter->discovering = false; |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "Discovering"); |
| |
| trigger_passive_scanning(adapter); |
| return; |
| } |
| |
| cp.type = adapter->discovery_type; |
| |
| mgmt_send(adapter->mgmt, MGMT_OP_STOP_DISCOVERY, |
| adapter->dev_id, sizeof(cp), &cp, |
| stop_discovery_complete, adapter, NULL); |
| } |
| |
| static DBusMessage *start_discovery(DBusConnection *conn, |
| DBusMessage *msg, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| const char *sender = dbus_message_get_sender(msg); |
| struct watch_client *client; |
| GSList *list; |
| |
| DBG("sender %s", sender); |
| |
| if (!(adapter->current_settings & MGMT_SETTING_POWERED)) |
| return btd_error_not_ready(msg); |
| |
| /* |
| * Every client can only start one discovery, if the client |
| * already started a discovery then return an error. |
| */ |
| list = g_slist_find_custom(adapter->discovery_list, sender, |
| compare_sender); |
| if (list) |
| return btd_error_busy(msg); |
| |
| client = g_new0(struct watch_client, 1); |
| |
| client->adapter = adapter; |
| client->owner = g_strdup(sender); |
| client->watch = g_dbus_add_disconnect_watch(dbus_conn, sender, |
| discovery_disconnect, client, |
| discovery_destroy); |
| |
| adapter->discovery_list = g_slist_prepend(adapter->discovery_list, |
| client); |
| |
| /* |
| * Just trigger the discovery here. In case an already running |
| * discovery in idle phase exists, it will be restarted right |
| * away. |
| */ |
| trigger_start_discovery(adapter, 0); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static DBusMessage *stop_discovery(DBusConnection *conn, |
| DBusMessage *msg, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| const char *sender = dbus_message_get_sender(msg); |
| struct mgmt_cp_stop_discovery cp; |
| struct watch_client *client; |
| GSList *list; |
| |
| DBG("sender %s", sender); |
| |
| if (!(adapter->current_settings & MGMT_SETTING_POWERED)) |
| return btd_error_not_ready(msg); |
| |
| list = g_slist_find_custom(adapter->discovery_list, sender, |
| compare_sender); |
| if (!list) |
| return btd_error_failed(msg, "No discovery started"); |
| |
| client = list->data; |
| |
| cp.type = adapter->discovery_type; |
| |
| /* |
| * The destroy function will cleanup the client information and |
| * also remove it from the list of discovery clients. |
| */ |
| g_dbus_remove_watch(dbus_conn, client->watch); |
| |
| /* |
| * As long as other discovery clients are still active, just |
| * return success. |
| */ |
| if (adapter->discovery_list) |
| return dbus_message_new_method_return(msg); |
| |
| /* |
| * In the idle phase of a discovery, there is no need to stop it |
| * and so it is enough to send out the signal and just return. |
| */ |
| if (adapter->discovery_enable == 0x00) { |
| adapter->discovering = false; |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "Discovering"); |
| |
| trigger_passive_scanning(adapter); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| mgmt_send(adapter->mgmt, MGMT_OP_STOP_DISCOVERY, |
| adapter->dev_id, sizeof(cp), &cp, |
| stop_discovery_complete, adapter, NULL); |
| |
| return dbus_message_new_method_return(msg); |
| } |
| |
| static gboolean property_get_address(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| char addr[18]; |
| const char *str = addr; |
| |
| ba2str(&adapter->bdaddr, addr); |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str); |
| |
| return TRUE; |
| } |
| |
| static gboolean property_get_name(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| const char *str = adapter->system_name ? : ""; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str); |
| |
| return TRUE; |
| } |
| |
| static gboolean property_get_alias(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| const char *str; |
| |
| if (adapter->current_alias) |
| str = adapter->current_alias; |
| else if (adapter->stored_alias) |
| str = adapter->stored_alias; |
| else |
| str = adapter->system_name ? : ""; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str); |
| |
| return TRUE; |
| } |
| |
| static void property_set_alias(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, |
| GDBusPendingPropertySet id, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| const char *name; |
| int ret; |
| |
| dbus_message_iter_get_basic(iter, &name); |
| |
| if (g_str_equal(name, "") == TRUE) { |
| if (adapter->stored_alias == NULL) { |
| /* no alias set, nothing to restore */ |
| g_dbus_pending_property_success(id); |
| return; |
| } |
| |
| /* restore to system name */ |
| ret = set_name(adapter, adapter->system_name); |
| } else { |
| if (g_strcmp0(adapter->stored_alias, name) == 0) { |
| /* alias already set, nothing to do */ |
| g_dbus_pending_property_success(id); |
| return; |
| } |
| |
| /* set to alias */ |
| ret = set_name(adapter, name); |
| } |
| |
| if (ret >= 0) { |
| g_free(adapter->stored_alias); |
| |
| if (g_str_equal(name, "") == TRUE) |
| adapter->stored_alias = NULL; |
| else |
| adapter->stored_alias = g_strdup(name); |
| |
| store_adapter_info(adapter); |
| |
| g_dbus_pending_property_success(id); |
| return; |
| } |
| |
| if (ret == -EINVAL) |
| g_dbus_pending_property_error(id, |
| ERROR_INTERFACE ".InvalidArguments", |
| "Invalid arguments in method call"); |
| else |
| g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", |
| strerror(-ret)); |
| } |
| |
| static gboolean property_get_class(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| dbus_uint32_t val = adapter->dev_class; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &val); |
| |
| return TRUE; |
| } |
| |
| static gboolean property_get_mode(struct btd_adapter *adapter, |
| uint32_t setting, DBusMessageIter *iter) |
| { |
| dbus_bool_t enable; |
| |
| enable = (adapter->current_settings & setting) ? TRUE : FALSE; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &enable); |
| |
| return TRUE; |
| } |
| |
| struct property_set_data { |
| struct btd_adapter *adapter; |
| GDBusPendingPropertySet id; |
| }; |
| |
| static void property_set_mode_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct property_set_data *data = user_data; |
| struct btd_adapter *adapter = data->adapter; |
| |
| DBG("%s (0x%02x)", mgmt_errstr(status), status); |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| const char *dbus_err; |
| |
| error("Failed to set mode: %s (0x%02x)", |
| mgmt_errstr(status), status); |
| |
| if (status == MGMT_STATUS_RFKILLED) |
| dbus_err = ERROR_INTERFACE ".Blocked"; |
| else |
| dbus_err = ERROR_INTERFACE ".Failed"; |
| |
| g_dbus_pending_property_error(data->id, dbus_err, |
| mgmt_errstr(status)); |
| return; |
| } |
| |
| g_dbus_pending_property_success(data->id); |
| |
| /* |
| * The parameters are identical and also the task that is |
| * required in both cases. So it is safe to just call the |
| * event handling functions here. |
| */ |
| new_settings_callback(adapter->dev_id, length, param, adapter); |
| } |
| |
| static void property_set_mode(struct btd_adapter *adapter, uint32_t setting, |
| DBusMessageIter *value, |
| GDBusPendingPropertySet id) |
| { |
| struct property_set_data *data; |
| struct mgmt_cp_set_discoverable cp; |
| void *param; |
| dbus_bool_t enable, current_enable; |
| uint16_t opcode, len; |
| uint8_t mode; |
| |
| dbus_message_iter_get_basic(value, &enable); |
| |
| if (adapter->current_settings & setting) |
| current_enable = TRUE; |
| else |
| current_enable = FALSE; |
| |
| if (enable == current_enable) { |
| g_dbus_pending_property_success(id); |
| return; |
| } |
| |
| mode = (enable == TRUE) ? 0x01 : 0x00; |
| |
| switch (setting) { |
| case MGMT_SETTING_POWERED: |
| opcode = MGMT_OP_SET_POWERED; |
| param = &mode; |
| len = sizeof(mode); |
| break; |
| case MGMT_SETTING_DISCOVERABLE: |
| memset(&cp, 0, sizeof(cp)); |
| cp.val = mode; |
| if (cp.val) |
| cp.timeout = htobs(adapter->discoverable_timeout); |
| |
| opcode = MGMT_OP_SET_DISCOVERABLE; |
| param = &cp; |
| len = sizeof(cp); |
| break; |
| case MGMT_SETTING_PAIRABLE: |
| opcode = MGMT_OP_SET_PAIRABLE; |
| param = &mode; |
| len = sizeof(mode); |
| break; |
| default: |
| goto failed; |
| } |
| |
| DBG("sending %s command for index %u", mgmt_opstr(opcode), |
| adapter->dev_id); |
| |
| data = g_try_new0(struct property_set_data, 1); |
| if (!data) |
| goto failed; |
| |
| data->adapter = adapter; |
| data->id = id; |
| |
| if (mgmt_send(adapter->mgmt, opcode, adapter->dev_id, len, param, |
| property_set_mode_complete, data, g_free) > 0) |
| return; |
| |
| g_free(data); |
| |
| failed: |
| error("Failed to set mode for index %u", adapter->dev_id); |
| |
| g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", NULL); |
| } |
| |
| static gboolean property_get_powered(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| return property_get_mode(adapter, MGMT_SETTING_POWERED, iter); |
| } |
| |
| static void property_set_powered(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, |
| GDBusPendingPropertySet id, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| if (powering_down) { |
| g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", |
| "Powering down"); |
| return; |
| } |
| |
| property_set_mode(adapter, MGMT_SETTING_POWERED, iter, id); |
| } |
| |
| static gboolean property_get_discoverable(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| return property_get_mode(adapter, MGMT_SETTING_DISCOVERABLE, iter); |
| } |
| |
| static void property_set_discoverable(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, |
| GDBusPendingPropertySet id, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| property_set_mode(adapter, MGMT_SETTING_DISCOVERABLE, iter, id); |
| } |
| |
| static gboolean property_get_discoverable_timeout( |
| const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| dbus_uint32_t value = adapter->discoverable_timeout; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &value); |
| |
| return TRUE; |
| } |
| |
| static void property_set_discoverable_timeout( |
| const GDBusPropertyTable *property, |
| DBusMessageIter *iter, |
| GDBusPendingPropertySet id, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| dbus_uint32_t value; |
| |
| dbus_message_iter_get_basic(iter, &value); |
| |
| adapter->discoverable_timeout = value; |
| |
| g_dbus_pending_property_success(id); |
| |
| store_adapter_info(adapter); |
| |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "DiscoverableTimeout"); |
| |
| if (adapter->current_settings & MGMT_SETTING_DISCOVERABLE) |
| set_discoverable(adapter, 0x01, adapter->discoverable_timeout); |
| } |
| |
| static gboolean property_get_pairable(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| return property_get_mode(adapter, MGMT_SETTING_PAIRABLE, iter); |
| } |
| |
| static void property_set_pairable(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, |
| GDBusPendingPropertySet id, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| property_set_mode(adapter, MGMT_SETTING_PAIRABLE, iter, id); |
| } |
| |
| static gboolean property_get_pairable_timeout( |
| const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| dbus_uint32_t value = adapter->pairable_timeout; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &value); |
| |
| return TRUE; |
| } |
| |
| static void property_set_pairable_timeout(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, |
| GDBusPendingPropertySet id, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| dbus_uint32_t value; |
| |
| dbus_message_iter_get_basic(iter, &value); |
| |
| adapter->pairable_timeout = value; |
| |
| g_dbus_pending_property_success(id); |
| |
| store_adapter_info(adapter); |
| |
| trigger_pairable_timeout(adapter); |
| } |
| |
| static gboolean property_get_discovering(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| dbus_bool_t discovering = adapter->discovering; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &discovering); |
| |
| return TRUE; |
| } |
| |
| static gboolean property_get_uuids(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| DBusMessageIter entry; |
| sdp_list_t *l; |
| |
| dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_STRING_AS_STRING, &entry); |
| |
| for (l = adapter->services; l != NULL; l = l->next) { |
| sdp_record_t *rec = l->data; |
| char *uuid; |
| |
| uuid = bt_uuid2string(&rec->svclass); |
| if (uuid == NULL) |
| continue; |
| |
| dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, |
| &uuid); |
| free(uuid); |
| } |
| |
| dbus_message_iter_close_container(iter, &entry); |
| |
| return TRUE; |
| } |
| |
| static gboolean property_exists_modalias(const GDBusPropertyTable *property, |
| void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| return adapter->modalias ? TRUE : FALSE; |
| } |
| |
| static gboolean property_get_modalias(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| const char *str = adapter->modalias ? : ""; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str); |
| |
| return TRUE; |
| } |
| |
| static int device_path_cmp(gconstpointer a, gconstpointer b) |
| { |
| const struct btd_device *device = a; |
| const char *path = b; |
| const char *dev_path = device_get_path(device); |
| |
| return strcasecmp(dev_path, path); |
| } |
| |
| static DBusMessage *remove_device(DBusConnection *conn, |
| DBusMessage *msg, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| struct btd_device *device; |
| const char *path; |
| GSList *list; |
| |
| if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, |
| DBUS_TYPE_INVALID) == FALSE) |
| return btd_error_invalid_args(msg); |
| |
| list = g_slist_find_custom(adapter->devices, path, device_path_cmp); |
| if (!list) |
| return btd_error_does_not_exist(msg); |
| |
| if (!(adapter->current_settings & MGMT_SETTING_POWERED)) |
| return btd_error_not_ready(msg); |
| |
| device = list->data; |
| |
| btd_device_set_temporary(device, TRUE); |
| |
| if (!btd_device_is_connected(device)) { |
| btd_adapter_remove_device(adapter, device); |
| return dbus_message_new_method_return(msg); |
| } |
| |
| device_request_disconnect(device, msg); |
| |
| return NULL; |
| } |
| |
| static const GDBusMethodTable adapter_methods[] = { |
| { GDBUS_METHOD("StartDiscovery", NULL, NULL, start_discovery) }, |
| { GDBUS_METHOD("StopDiscovery", NULL, NULL, stop_discovery) }, |
| { GDBUS_ASYNC_METHOD("RemoveDevice", |
| GDBUS_ARGS({ "device", "o" }), NULL, remove_device) }, |
| { } |
| }; |
| |
| static const GDBusPropertyTable adapter_properties[] = { |
| { "Address", "s", property_get_address }, |
| { "Name", "s", property_get_name }, |
| { "Alias", "s", property_get_alias, property_set_alias }, |
| { "Class", "u", property_get_class }, |
| { "Powered", "b", property_get_powered, property_set_powered }, |
| { "Discoverable", "b", property_get_discoverable, |
| property_set_discoverable }, |
| { "DiscoverableTimeout", "u", property_get_discoverable_timeout, |
| property_set_discoverable_timeout }, |
| { "Pairable", "b", property_get_pairable, property_set_pairable }, |
| { "PairableTimeout", "u", property_get_pairable_timeout, |
| property_set_pairable_timeout }, |
| { "Discovering", "b", property_get_discovering }, |
| { "UUIDs", "as", property_get_uuids }, |
| { "Modalias", "s", property_get_modalias, NULL, |
| property_exists_modalias }, |
| { } |
| }; |
| |
| static int str2buf(const char *str, uint8_t *buf, size_t blen) |
| { |
| int i, dlen; |
| |
| if (str == NULL) |
| return -EINVAL; |
| |
| memset(buf, 0, blen); |
| |
| dlen = MIN((strlen(str) / 2), blen); |
| |
| for (i = 0; i < dlen; i++) |
| sscanf(str + (i * 2), "%02hhX", &buf[i]); |
| |
| return 0; |
| } |
| |
| static struct link_key_info *get_key_info(GKeyFile *key_file, const char *peer) |
| { |
| struct link_key_info *info = NULL; |
| char *str; |
| |
| str = g_key_file_get_string(key_file, "LinkKey", "Key", NULL); |
| if (!str || strlen(str) < 32) |
| goto failed; |
| |
| info = g_new0(struct link_key_info, 1); |
| |
| str2ba(peer, &info->bdaddr); |
| |
| if (!strncmp(str, "0x", 2)) |
| str2buf(&str[2], info->key, sizeof(info->key)); |
| else |
| str2buf(&str[0], info->key, sizeof(info->key)); |
| |
| info->type = g_key_file_get_integer(key_file, "LinkKey", "Type", NULL); |
| info->pin_len = g_key_file_get_integer(key_file, "LinkKey", "PINLength", |
| NULL); |
| |
| failed: |
| g_free(str); |
| |
| return info; |
| } |
| |
| static struct smp_ltk_info *get_ltk(GKeyFile *key_file, const char *peer, |
| uint8_t peer_type, const char *group) |
| { |
| struct smp_ltk_info *ltk = NULL; |
| GError *gerr = NULL; |
| bool master; |
| char *key; |
| char *rand = NULL; |
| |
| key = g_key_file_get_string(key_file, group, "Key", NULL); |
| if (!key || strlen(key) < 32) |
| goto failed; |
| |
| rand = g_key_file_get_string(key_file, group, "Rand", NULL); |
| if (!rand) |
| goto failed; |
| |
| ltk = g_new0(struct smp_ltk_info, 1); |
| |
| /* Default to assuming a master key */ |
| ltk->master = true; |
| |
| str2ba(peer, <k->bdaddr); |
| ltk->bdaddr_type = peer_type; |
| |
| /* |
| * Long term keys should respond to an identity address which can |
| * either be a public address or a random static address. Keys |
| * stored for resolvable random and unresolvable random addresses |
| * are ignored. |
| * |
| * This is an extra sanity check for older kernel versions or older |
| * daemons that might have been instructed to store long term keys |
| * for these temporary addresses. |
| */ |
| if (ltk->bdaddr_type == BDADDR_LE_RANDOM && |
| (ltk->bdaddr.b[5] & 0xc0) != 0xc0) { |
| g_free(ltk); |
| ltk = NULL; |
| goto failed; |
| } |
| |
| if (!strncmp(key, "0x", 2)) |
| str2buf(&key[2], ltk->val, sizeof(ltk->val)); |
| else |
| str2buf(&key[0], ltk->val, sizeof(ltk->val)); |
| |
| if (!strncmp(rand, "0x", 2)) { |
| uint64_t rand_le; |
| str2buf(&rand[2], (uint8_t *) &rand_le, sizeof(rand_le)); |
| ltk->rand = le64_to_cpu(rand_le); |
| } else { |
| sscanf(rand, "%" PRIu64, <k->rand); |
| } |
| |
| ltk->authenticated = g_key_file_get_integer(key_file, group, |
| "Authenticated", NULL); |
| ltk->enc_size = g_key_file_get_integer(key_file, group, "EncSize", |
| NULL); |
| ltk->ediv = g_key_file_get_integer(key_file, group, "EDiv", NULL); |
| |
| master = g_key_file_get_boolean(key_file, group, "Master", &gerr); |
| if (gerr) |
| g_error_free(gerr); |
| else |
| ltk->master = master; |
| |
| failed: |
| g_free(key); |
| g_free(rand); |
| |
| return ltk; |
| } |
| |
| static GSList *get_ltk_info(GKeyFile *key_file, const char *peer, |
| uint8_t bdaddr_type) |
| { |
| struct smp_ltk_info *ltk; |
| GSList *l = NULL; |
| |
| DBG("%s", peer); |
| |
| ltk = get_ltk(key_file, peer, bdaddr_type, "LongTermKey"); |
| if (ltk) |
| l = g_slist_append(l, ltk); |
| |
| ltk = get_ltk(key_file, peer, bdaddr_type, "SlaveLongTermKey"); |
| if (ltk) { |
| ltk->master = false; |
| l = g_slist_append(l, ltk); |
| } |
| |
| return l; |
| } |
| |
| static struct irk_info *get_irk_info(GKeyFile *key_file, const char *peer, |
| uint8_t bdaddr_type) |
| { |
| struct irk_info *irk; |
| char *str; |
| |
| str = g_key_file_get_string(key_file, "IdentityResolvingKey", "Key", NULL); |
| if (!str || strlen(str) < 32) |
| return NULL; |
| |
| irk = g_new0(struct irk_info, 1); |
| |
| str2ba(peer, &irk->bdaddr); |
| irk->bdaddr_type = bdaddr_type; |
| |
| if (!strncmp(str, "0x", 2)) |
| str2buf(&str[2], irk->val, sizeof(irk->val)); |
| else |
| str2buf(&str[0], irk->val, sizeof(irk->val)); |
| |
| g_free(str); |
| |
| return irk; |
| } |
| |
| static void load_link_keys_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| error("Failed to load link keys for hci%u: %s (0x%02x)", |
| adapter->dev_id, mgmt_errstr(status), status); |
| return; |
| } |
| |
| DBG("link keys loaded for hci%u", adapter->dev_id); |
| } |
| |
| static void load_link_keys(struct btd_adapter *adapter, GSList *keys, |
| bool debug_keys) |
| { |
| struct mgmt_cp_load_link_keys *cp; |
| struct mgmt_link_key_info *key; |
| size_t key_count, cp_size; |
| unsigned int id; |
| GSList *l; |
| |
| /* |
| * If the controller does not support BR/EDR operation, |
| * there is no point in trying to load the link keys into |
| * the kernel. |
| * |
| * This is an optimization for Low Energy only controllers. |
| */ |
| if (!(adapter->supported_settings & MGMT_SETTING_BREDR)) |
| return; |
| |
| key_count = g_slist_length(keys); |
| |
| DBG("hci%u keys %zu debug_keys %d", adapter->dev_id, key_count, |
| debug_keys); |
| |
| cp_size = sizeof(*cp) + (key_count * sizeof(*key)); |
| |
| cp = g_try_malloc0(cp_size); |
| if (cp == NULL) { |
| error("No memory for link keys for hci%u", adapter->dev_id); |
| return; |
| } |
| |
| /* |
| * Even if the list of stored keys is empty, it is important to |
| * load an empty list into the kernel. That way it is ensured |
| * that no old keys from a previous daemon are present. |
| * |
| * In addition it is also the only way to toggle the different |
| * behavior for debug keys. |
| */ |
| cp->debug_keys = debug_keys; |
| cp->key_count = htobs(key_count); |
| |
| for (l = keys, key = cp->keys; l != NULL; l = g_slist_next(l), key++) { |
| struct link_key_info *info = l->data; |
| |
| bacpy(&key->addr.bdaddr, &info->bdaddr); |
| key->addr.type = BDADDR_BREDR; |
| key->type = info->type; |
| memcpy(key->val, info->key, 16); |
| key->pin_len = info->pin_len; |
| } |
| |
| id = mgmt_send(adapter->mgmt, MGMT_OP_LOAD_LINK_KEYS, |
| adapter->dev_id, cp_size, cp, |
| load_link_keys_complete, adapter, NULL); |
| |
| g_free(cp); |
| |
| if (id == 0) |
| error("Failed to load link keys for hci%u", adapter->dev_id); |
| } |
| |
| static gboolean load_ltks_timeout(gpointer user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| error("Loading LTKs timed out for hci%u", adapter->dev_id); |
| |
| adapter->load_ltks_timeout = 0; |
| |
| mgmt_cancel(adapter->mgmt, adapter->load_ltks_id); |
| adapter->load_ltks_id = 0; |
| |
| return FALSE; |
| } |
| |
| static void load_ltks_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| error("Failed to load LTKs for hci%u: %s (0x%02x)", |
| adapter->dev_id, mgmt_errstr(status), status); |
| } |
| |
| adapter->load_ltks_id = 0; |
| |
| g_source_remove(adapter->load_ltks_timeout); |
| adapter->load_ltks_timeout = 0; |
| |
| DBG("LTKs loaded for hci%u", adapter->dev_id); |
| } |
| |
| static void load_ltks(struct btd_adapter *adapter, GSList *keys) |
| { |
| struct mgmt_cp_load_long_term_keys *cp; |
| struct mgmt_ltk_info *key; |
| size_t key_count, cp_size; |
| GSList *l; |
| |
| /* |
| * If the controller does not support Low Energy operation, |
| * there is no point in trying to load the long term keys |
| * into the kernel. |
| * |
| * While there is no harm in loading keys into the kernel, |
| * this is an optimization to avoid a confusing warning |
| * message when the loading of the keys timed out due to |
| * a kernel bug (see comment below). |
| */ |
| if (!(adapter->supported_settings & MGMT_SETTING_LE)) |
| return; |
| |
| key_count = g_slist_length(keys); |
| |
| DBG("hci%u keys %zu", adapter->dev_id, key_count); |
| |
| cp_size = sizeof(*cp) + (key_count * sizeof(*key)); |
| |
| cp = g_try_malloc0(cp_size); |
| if (cp == NULL) { |
| error("No memory for LTKs for hci%u", adapter->dev_id); |
| return; |
| } |
| |
| /* |
| * Even if the list of stored keys is empty, it is important to |
| * load an empty list into the kernel. That way it is ensured |
| * that no old keys from a previous daemon are present. |
| */ |
| cp->key_count = htobs(key_count); |
| |
| for (l = keys, key = cp->keys; l != NULL; l = g_slist_next(l), key++) { |
| struct smp_ltk_info *info = l->data; |
| |
| bacpy(&key->addr.bdaddr, &info->bdaddr); |
| key->addr.type = info->bdaddr_type; |
| memcpy(key->val, info->val, sizeof(info->val)); |
| key->rand = cpu_to_le64(info->rand); |
| key->ediv = cpu_to_le16(info->ediv); |
| key->type = info->authenticated; |
| key->master = info->master; |
| key->enc_size = info->enc_size; |
| } |
| |
| adapter->load_ltks_id = mgmt_send(adapter->mgmt, |
| MGMT_OP_LOAD_LONG_TERM_KEYS, |
| adapter->dev_id, cp_size, cp, |
| load_ltks_complete, adapter, NULL); |
| |
| g_free(cp); |
| |
| if (adapter->load_ltks_id == 0) { |
| error("Failed to load LTKs for hci%u", adapter->dev_id); |
| return; |
| } |
| |
| /* |
| * This timeout handling is needed since the kernel is stupid |
| * and forgets to send a command complete response. However in |
| * case of failures it does send a command status. |
| */ |
| adapter->load_ltks_timeout = g_timeout_add_seconds(2, |
| load_ltks_timeout, adapter); |
| } |
| |
| static void load_irks_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| if (status == MGMT_STATUS_UNKNOWN_COMMAND) { |
| info("Load IRKs failed: Kernel doesn't support LE Privacy"); |
| return; |
| } |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| error("Failed to load IRKs for hci%u: %s (0x%02x)", |
| adapter->dev_id, mgmt_errstr(status), status); |
| return; |
| } |
| |
| DBG("IRKs loaded for hci%u", adapter->dev_id); |
| } |
| |
| static void load_irks(struct btd_adapter *adapter, GSList *irks) |
| { |
| struct mgmt_cp_load_irks *cp; |
| struct mgmt_irk_info *irk; |
| size_t irk_count, cp_size; |
| unsigned int id; |
| GSList *l; |
| |
| /* |
| * If the controller does not support LE Privacy operation, |
| * there is no support for loading identity resolving keys |
| * into the kernel. |
| */ |
| if (!(adapter->supported_settings & MGMT_SETTING_PRIVACY)) |
| return; |
| |
| irk_count = g_slist_length(irks); |
| |
| DBG("hci%u irks %zu", adapter->dev_id, irk_count); |
| |
| cp_size = sizeof(*cp) + (irk_count * sizeof(*irk)); |
| |
| cp = g_try_malloc0(cp_size); |
| if (cp == NULL) { |
| error("No memory for IRKs for hci%u", adapter->dev_id); |
| return; |
| } |
| |
| /* |
| * Even if the list of stored keys is empty, it is important to |
| * load an empty list into the kernel. That way we tell the |
| * kernel that we are able to handle New IRK events. |
| */ |
| cp->irk_count = htobs(irk_count); |
| |
| for (l = irks, irk = cp->irks; l != NULL; l = g_slist_next(l), irk++) { |
| struct irk_info *info = l->data; |
| |
| bacpy(&irk->addr.bdaddr, &info->bdaddr); |
| irk->addr.type = info->bdaddr_type; |
| memcpy(irk->val, info->val, sizeof(irk->val)); |
| } |
| |
| id = mgmt_send(adapter->mgmt, MGMT_OP_LOAD_IRKS, adapter->dev_id, |
| cp_size, cp, load_irks_complete, adapter, NULL); |
| |
| g_free(cp); |
| |
| if (id == 0) |
| error("Failed to IRKs for hci%u", adapter->dev_id); |
| } |
| |
| static uint8_t get_le_addr_type(GKeyFile *keyfile) |
| { |
| uint8_t addr_type; |
| char *type; |
| |
| type = g_key_file_get_string(keyfile, "General", "AddressType", NULL); |
| if (!type) |
| return BDADDR_LE_PUBLIC; |
| |
| if (g_str_equal(type, "public")) |
| addr_type = BDADDR_LE_PUBLIC; |
| else if (g_str_equal(type, "static")) |
| addr_type = BDADDR_LE_RANDOM; |
| else |
| addr_type = BDADDR_LE_PUBLIC; |
| |
| g_free(type); |
| |
| return addr_type; |
| } |
| |
| static void load_devices(struct btd_adapter *adapter) |
| { |
| char filename[PATH_MAX + 1]; |
| char srcaddr[18]; |
| GSList *keys = NULL; |
| GSList *ltks = NULL; |
| GSList *irks = NULL; |
| DIR *dir; |
| struct dirent *entry; |
| |
| ba2str(&adapter->bdaddr, srcaddr); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s", srcaddr); |
| filename[PATH_MAX] = '\0'; |
| |
| dir = opendir(filename); |
| if (!dir) { |
| error("Unable to open adapter storage directory: %s", filename); |
| return; |
| } |
| |
| while ((entry = readdir(dir)) != NULL) { |
| struct btd_device *device; |
| char filename[PATH_MAX + 1]; |
| GKeyFile *key_file; |
| struct link_key_info *key_info; |
| GSList *list, *ltk_info; |
| struct irk_info *irk_info; |
| uint8_t bdaddr_type; |
| |
| if (entry->d_type != DT_DIR || bachk(entry->d_name) < 0) |
| continue; |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", srcaddr, |
| entry->d_name); |
| |
| key_file = g_key_file_new(); |
| g_key_file_load_from_file(key_file, filename, 0, NULL); |
| |
| key_info = get_key_info(key_file, entry->d_name); |
| if (key_info) |
| keys = g_slist_append(keys, key_info); |
| |
| bdaddr_type = get_le_addr_type(key_file); |
| |
| ltk_info = get_ltk_info(key_file, entry->d_name, bdaddr_type); |
| ltks = g_slist_concat(ltks, ltk_info); |
| |
| irk_info = get_irk_info(key_file, entry->d_name, bdaddr_type); |
| if (irk_info) |
| irks = g_slist_append(irks, irk_info); |
| |
| list = g_slist_find_custom(adapter->devices, entry->d_name, |
| device_address_cmp); |
| if (list) { |
| device = list->data; |
| goto device_exist; |
| } |
| |
| device = device_create_from_storage(adapter, entry->d_name, |
| key_file); |
| if (!device) |
| goto free; |
| |
| btd_device_set_temporary(device, FALSE); |
| adapter->devices = g_slist_append(adapter->devices, device); |
| |
| /* TODO: register services from pre-loaded list of primaries */ |
| |
| list = btd_device_get_uuids(device); |
| if (list) |
| device_probe_profiles(device, list); |
| |
| device_exist: |
| if (key_info) { |
| device_set_paired(device, BDADDR_BREDR); |
| device_set_bonded(device, BDADDR_BREDR); |
| } |
| |
| if (ltk_info) { |
| device_set_paired(device, bdaddr_type); |
| device_set_bonded(device, bdaddr_type); |
| } |
| |
| free: |
| g_key_file_free(key_file); |
| } |
| |
| closedir(dir); |
| |
| load_link_keys(adapter, keys, main_opts.debug_keys); |
| g_slist_free_full(keys, g_free); |
| |
| load_ltks(adapter, ltks); |
| g_slist_free_full(ltks, g_free); |
| load_irks(adapter, irks); |
| g_slist_free_full(irks, g_free); |
| } |
| |
| int btd_adapter_block_address(struct btd_adapter *adapter, |
| const bdaddr_t *bdaddr, uint8_t bdaddr_type) |
| { |
| struct mgmt_cp_block_device cp; |
| char addr[18]; |
| |
| ba2str(bdaddr, addr); |
| DBG("hci%u %s", adapter->dev_id, addr); |
| |
| memset(&cp, 0, sizeof(cp)); |
| bacpy(&cp.addr.bdaddr, bdaddr); |
| cp.addr.type = bdaddr_type; |
| |
| if (mgmt_send(adapter->mgmt, MGMT_OP_BLOCK_DEVICE, |
| adapter->dev_id, sizeof(cp), &cp, |
| NULL, NULL, NULL) > 0) |
| return 0; |
| |
| return -EIO; |
| } |
| |
| int btd_adapter_unblock_address(struct btd_adapter *adapter, |
| const bdaddr_t *bdaddr, uint8_t bdaddr_type) |
| { |
| struct mgmt_cp_unblock_device cp; |
| char addr[18]; |
| |
| ba2str(bdaddr, addr); |
| DBG("hci%u %s", adapter->dev_id, addr); |
| |
| memset(&cp, 0, sizeof(cp)); |
| bacpy(&cp.addr.bdaddr, bdaddr); |
| cp.addr.type = bdaddr_type; |
| |
| if (mgmt_send(adapter->mgmt, MGMT_OP_UNBLOCK_DEVICE, |
| adapter->dev_id, sizeof(cp), &cp, |
| NULL, NULL, NULL) > 0) |
| return 0; |
| |
| return -EIO; |
| } |
| |
| static int clear_blocked(struct btd_adapter *adapter) |
| { |
| return btd_adapter_unblock_address(adapter, BDADDR_ANY, 0); |
| } |
| |
| static void probe_driver(struct btd_adapter *adapter, gpointer user_data) |
| { |
| struct btd_adapter_driver *driver = user_data; |
| int err; |
| |
| if (driver->probe == NULL) |
| return; |
| |
| err = driver->probe(adapter); |
| if (err < 0) { |
| error("%s: %s (%d)", driver->name, strerror(-err), -err); |
| return; |
| } |
| |
| adapter->drivers = g_slist_prepend(adapter->drivers, driver); |
| } |
| |
| static void load_drivers(struct btd_adapter *adapter) |
| { |
| GSList *l; |
| |
| for (l = adapter_drivers; l; l = l->next) |
| probe_driver(adapter, l->data); |
| } |
| |
| static void probe_profile(struct btd_profile *profile, void *data) |
| { |
| struct btd_adapter *adapter = data; |
| int err; |
| |
| if (profile->adapter_probe == NULL) |
| return; |
| |
| err = profile->adapter_probe(profile, adapter); |
| if (err < 0) { |
| error("%s: %s (%d)", profile->name, strerror(-err), -err); |
| return; |
| } |
| |
| adapter->profiles = g_slist_prepend(adapter->profiles, profile); |
| } |
| |
| void adapter_add_profile(struct btd_adapter *adapter, gpointer p) |
| { |
| struct btd_profile *profile = p; |
| |
| if (!adapter->initialized) |
| return; |
| |
| probe_profile(profile, adapter); |
| |
| g_slist_foreach(adapter->devices, device_probe_profile, profile); |
| } |
| |
| void adapter_remove_profile(struct btd_adapter *adapter, gpointer p) |
| { |
| struct btd_profile *profile = p; |
| |
| if (!adapter->initialized) |
| return; |
| |
| if (profile->device_remove) |
| g_slist_foreach(adapter->devices, device_remove_profile, p); |
| |
| adapter->profiles = g_slist_remove(adapter->profiles, profile); |
| |
| if (profile->adapter_remove) |
| profile->adapter_remove(profile, adapter); |
| } |
| |
| static void adapter_add_connection(struct btd_adapter *adapter, |
| struct btd_device *device, |
| uint8_t bdaddr_type) |
| { |
| device_add_connection(device, bdaddr_type); |
| |
| if (g_slist_find(adapter->connections, device)) { |
| error("Device is already marked as connected"); |
| return; |
| } |
| |
| adapter->connections = g_slist_append(adapter->connections, device); |
| } |
| |
| static void get_connections_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| const struct mgmt_rp_get_connections *rp = param; |
| uint16_t i, conn_count; |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| error("Failed to get connections: %s (0x%02x)", |
| mgmt_errstr(status), status); |
| return; |
| } |
| |
| if (length < sizeof(*rp)) { |
| error("Wrong size of get connections response"); |
| return; |
| } |
| |
| conn_count = btohs(rp->conn_count); |
| |
| DBG("Connection count: %d", conn_count); |
| |
| if (conn_count * sizeof(struct mgmt_addr_info) + |
| sizeof(*rp) != length) { |
| error("Incorrect packet size for get connections response"); |
| return; |
| } |
| |
| for (i = 0; i < conn_count; i++) { |
| const struct mgmt_addr_info *addr = &rp->addr[i]; |
| struct btd_device *device; |
| char address[18]; |
| |
| ba2str(&addr->bdaddr, address); |
| DBG("Adding existing connection to %s", address); |
| |
| device = btd_adapter_get_device(adapter, &addr->bdaddr, |
| addr->type); |
| if (device) |
| adapter_add_connection(adapter, device, addr->type); |
| } |
| } |
| |
| static void load_connections(struct btd_adapter *adapter) |
| { |
| DBG("sending get connections command for index %u", adapter->dev_id); |
| |
| if (mgmt_send(adapter->mgmt, MGMT_OP_GET_CONNECTIONS, |
| adapter->dev_id, 0, NULL, |
| get_connections_complete, adapter, NULL) > 0) |
| return; |
| |
| error("Failed to get connections for index %u", adapter->dev_id); |
| } |
| |
| bool btd_adapter_get_pairable(struct btd_adapter *adapter) |
| { |
| if (adapter->current_settings & MGMT_SETTING_PAIRABLE) |
| return true; |
| |
| return false; |
| } |
| |
| bool btd_adapter_get_powered(struct btd_adapter *adapter) |
| { |
| if (adapter->current_settings & MGMT_SETTING_POWERED) |
| return true; |
| |
| return false; |
| } |
| |
| bool btd_adapter_get_connectable(struct btd_adapter *adapter) |
| { |
| if (adapter->current_settings & MGMT_SETTING_CONNECTABLE) |
| return true; |
| |
| return false; |
| } |
| |
| uint32_t btd_adapter_get_class(struct btd_adapter *adapter) |
| { |
| return adapter->dev_class; |
| } |
| |
| const char *btd_adapter_get_name(struct btd_adapter *adapter) |
| { |
| if (adapter->stored_alias) |
| return adapter->stored_alias; |
| |
| if (adapter->system_name) |
| return adapter->system_name; |
| |
| return NULL; |
| } |
| |
| int adapter_connect_list_add(struct btd_adapter *adapter, |
| struct btd_device *device) |
| { |
| /* |
| * If the adapter->connect_le device is getting added back to |
| * the connect list it probably means that the connect attempt |
| * failed and hence we should clear this pointer |
| */ |
| if (device == adapter->connect_le) |
| adapter->connect_le = NULL; |
| |
| if (g_slist_find(adapter->connect_list, device)) { |
| DBG("ignoring already added device %s", |
| device_get_path(device)); |
| return 0; |
| } |
| |
| if (!(adapter->supported_settings & MGMT_SETTING_LE)) { |
| error("Can't add %s to non-LE capable adapter connect list", |
| device_get_path(device)); |
| return -ENOTSUP; |
| } |
| |
| adapter->connect_list = g_slist_append(adapter->connect_list, device); |
| DBG("%s added to %s's connect_list", device_get_path(device), |
| adapter->system_name); |
| |
| if (!(adapter->current_settings & MGMT_SETTING_POWERED)) |
| return 0; |
| |
| trigger_passive_scanning(adapter); |
| |
| return 0; |
| } |
| |
| void adapter_connect_list_remove(struct btd_adapter *adapter, |
| struct btd_device *device) |
| { |
| /* |
| * If the adapter->connect_le device is being removed from the |
| * connect list it means the connection was successful and hence |
| * the pointer should be cleared |
| */ |
| if (device == adapter->connect_le) |
| adapter->connect_le = NULL; |
| |
| if (!g_slist_find(adapter->connect_list, device)) { |
| DBG("device %s is not on the list, ignoring", |
| device_get_path(device)); |
| return; |
| } |
| |
| adapter->connect_list = g_slist_remove(adapter->connect_list, device); |
| DBG("%s removed from %s's connect_list", device_get_path(device), |
| adapter->system_name); |
| |
| if (!adapter->connect_list) { |
| stop_passive_scanning(adapter); |
| return; |
| } |
| |
| if (!(adapter->current_settings & MGMT_SETTING_POWERED)) |
| return; |
| |
| trigger_passive_scanning(adapter); |
| } |
| |
| static void adapter_start(struct btd_adapter *adapter) |
| { |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "Powered"); |
| |
| DBG("adapter %s has been enabled", adapter->path); |
| |
| trigger_passive_scanning(adapter); |
| } |
| |
| static void reply_pending_requests(struct btd_adapter *adapter) |
| { |
| GSList *l; |
| |
| if (!adapter) |
| return; |
| |
| /* pending bonding */ |
| for (l = adapter->devices; l; l = l->next) { |
| struct btd_device *device = l->data; |
| |
| if (device_is_bonding(device, NULL)) |
| device_bonding_failed(device, |
| HCI_OE_USER_ENDED_CONNECTION); |
| } |
| } |
| |
| static void remove_driver(gpointer data, gpointer user_data) |
| { |
| struct btd_adapter_driver *driver = data; |
| struct btd_adapter *adapter = user_data; |
| |
| if (driver->remove) |
| driver->remove(adapter); |
| } |
| |
| static void remove_profile(gpointer data, gpointer user_data) |
| { |
| struct btd_profile *profile = data; |
| struct btd_adapter *adapter = user_data; |
| |
| if (profile->adapter_remove) |
| profile->adapter_remove(profile, adapter); |
| } |
| |
| static void unload_drivers(struct btd_adapter *adapter) |
| { |
| g_slist_foreach(adapter->drivers, remove_driver, adapter); |
| g_slist_free(adapter->drivers); |
| adapter->drivers = NULL; |
| |
| g_slist_foreach(adapter->profiles, remove_profile, adapter); |
| g_slist_free(adapter->profiles); |
| adapter->profiles = NULL; |
| } |
| |
| static void free_service_auth(gpointer data, gpointer user_data) |
| { |
| struct service_auth *auth = data; |
| |
| g_free(auth); |
| } |
| |
| static void adapter_free(gpointer user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| DBG("%p", adapter); |
| |
| if (adapter->load_ltks_timeout > 0) |
| g_source_remove(adapter->load_ltks_timeout); |
| |
| if (adapter->confirm_name_timeout > 0) |
| g_source_remove(adapter->confirm_name_timeout); |
| |
| if (adapter->pair_device_timeout > 0) |
| g_source_remove(adapter->pair_device_timeout); |
| |
| if (adapter->auth_idle_id) |
| g_source_remove(adapter->auth_idle_id); |
| |
| g_queue_foreach(adapter->auths, free_service_auth, NULL); |
| g_queue_free(adapter->auths); |
| |
| /* |
| * Unregister all handlers for this specific index since |
| * the adapter bound to them is no longer valid. |
| * |
| * This also avoids having multiple instances of the same |
| * handler in case indexes got removed and re-added. |
| */ |
| mgmt_unregister_index(adapter->mgmt, adapter->dev_id); |
| |
| /* |
| * Cancel all pending commands for this specific index |
| * since the adapter bound to them is no longer valid. |
| */ |
| mgmt_cancel_index(adapter->mgmt, adapter->dev_id); |
| |
| mgmt_unref(adapter->mgmt); |
| |
| sdp_list_free(adapter->services, NULL); |
| |
| g_slist_free(adapter->connections); |
| |
| g_free(adapter->path); |
| g_free(adapter->name); |
| g_free(adapter->short_name); |
| g_free(adapter->system_name); |
| g_free(adapter->stored_alias); |
| g_free(adapter->current_alias); |
| free(adapter |