| /* |
| * |
| * 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 <glib.h> |
| #include <dbus/dbus.h> |
| |
| #include "bluetooth/bluetooth.h" |
| #include "bluetooth/hci.h" |
| #include "bluetooth/hci_lib.h" |
| #include "bluetooth/sdp.h" |
| #include "bluetooth/sdp_lib.h" |
| #include "lib/uuid.h" |
| #include "lib/mgmt.h" |
| |
| #include "gdbus/gdbus.h" |
| |
| #include "log.h" |
| #include "textfile.h" |
| |
| #include "src/shared/mgmt.h" |
| #include "src/shared/util.h" |
| #include "src/shared/queue.h" |
| #include "src/shared/att.h" |
| #include "src/shared/gatt-db.h" |
| |
| #include "btio/btio.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 "gatt-database.h" |
| #include "advertising.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) |
| |
| #define SCAN_TYPE_BREDR (1 << BDADDR_BREDR) |
| #define SCAN_TYPE_LE ((1 << BDADDR_LE_PUBLIC) | (1 << BDADDR_LE_RANDOM)) |
| #define SCAN_TYPE_DUAL (SCAN_TYPE_BREDR | SCAN_TYPE_LE) |
| |
| #define HCI_RSSI_INVALID 127 |
| #define DISTANCE_VAL_INVALID 0x7FFF |
| #define PATHLOSS_MAX 137 |
| |
| static DBusConnection *dbus_conn = NULL; |
| |
| static bool kernel_conn_control = false; |
| |
| 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; |
| |
| static GSList *disconnect_list = NULL; |
| static GSList *conn_fail_list = 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 conn_param { |
| bdaddr_t bdaddr; |
| uint8_t bdaddr_type; |
| uint16_t min_interval; |
| uint16_t max_interval; |
| uint16_t latency; |
| uint16_t timeout; |
| }; |
| |
| struct discovery_filter { |
| uint8_t type; |
| uint16_t pathloss; |
| int16_t rssi; |
| GSList *uuids; |
| bool duplicate; |
| }; |
| |
| struct watch_client { |
| struct btd_adapter *adapter; |
| DBusMessage *msg; |
| char *owner; |
| guint watch; |
| struct discovery_filter *discovery_filter; |
| }; |
| |
| 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 */ |
| uint8_t bdaddr_type; /* address type */ |
| 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 */ |
| uint16_t manufacturer; /* adapter manufacturer */ |
| 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 */ |
| bool filtered_discovery; /* we are doing filtered discovery */ |
| bool no_scan_restart_delay; /* when this flag is set, restart scan |
| * without delay */ |
| 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 *set_filter_list; /* list of clients that specified |
| * filter, but don't scan yet |
| */ |
| /* current discovery filter, if any */ |
| struct mgmt_cp_start_service_discovery *current_discovery_filter; |
| |
| 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 */ |
| |
| struct btd_gatt_database *database; |
| struct btd_adv_manager *adv_manager; |
| |
| gboolean initialized; |
| |
| GSList *pin_callbacks; |
| GSList *msd_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; |
| |
| unsigned int db_id; /* Service event handler for GATT db */ |
| |
| bool is_default; /* true if adapter is default one */ |
| }; |
| |
| typedef enum { |
| ADAPTER_AUTHORIZE_DISCONNECTED = 0, |
| ADAPTER_AUTHORIZE_CHECK_CONNECTED |
| } adapter_authorize_type; |
| |
| 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; |
| uint32_t dev_class; |
| |
| if (length < sizeof(*rp)) { |
| btd_error(adapter->dev_id, |
| "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"); |
| } |
| |
| 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) { |
| btd_error(adapter->dev_id, |
| "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; |
| |
| btd_error(adapter->dev_id, |
| "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; |
| } |
| |
| const char *btd_adapter_get_storage_dir(struct btd_adapter *adapter) |
| { |
| static char dir[25]; |
| |
| if (adapter->bdaddr_type == BDADDR_LE_RANDOM) { |
| strcpy(dir, "static-"); |
| ba2str(&adapter->bdaddr, dir + 7); |
| } else { |
| ba2str(&adapter->bdaddr, dir); |
| } |
| |
| return dir; |
| } |
| |
| uint8_t btd_adapter_get_address_type(struct btd_adapter *adapter) |
| { |
| return adapter->bdaddr_type; |
| } |
| |
| static void store_adapter_info(struct btd_adapter *adapter) |
| { |
| GKeyFile *key_file; |
| char filename[PATH_MAX]; |
| 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); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/settings", |
| btd_adapter_get_storage_dir(adapter)); |
| |
| 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 bool set_mode(struct btd_adapter *adapter, uint16_t opcode, |
| uint8_t mode); |
| |
| 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_DISCOVERABLE) { |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "Discoverable"); |
| store_adapter_info(adapter); |
| btd_adv_manager_refresh(adapter->adv_manager); |
| } |
| |
| if (changed_mask & MGMT_SETTING_BONDABLE) { |
| 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)) { |
| btd_error(adapter->dev_id, |
| "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) { |
| btd_error(adapter->dev_id, "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; |
| |
| btd_error(adapter->dev_id, "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 (kernel_conn_control) { |
| if (mode) |
| set_mode(adapter, MGMT_OP_SET_CONNECTABLE, mode); |
| else |
| /* This also disables discoverable so we're done */ |
| return set_mode(adapter, MGMT_OP_SET_CONNECTABLE, |
| mode); |
| } |
| |
| if (mgmt_send(adapter->mgmt, MGMT_OP_SET_DISCOVERABLE, |
| adapter->dev_id, sizeof(cp), &cp, |
| set_mode_complete, adapter, NULL) > 0) |
| return true; |
| |
| btd_error(adapter->dev_id, "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_BONDABLE, 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; |
| } |
| |
| if (!(adapter->current_settings & MGMT_SETTING_BONDABLE)) |
| return; |
| |
| if (adapter->pairable_timeout > 0) |
| adapter->pairable_timeout_id = |
| 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)) { |
| btd_error(adapter->dev_id, |
| "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) { |
| btd_error(adapter->dev_id, |
| "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)) { |
| btd_error(adapter->dev_id, |
| "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; |
| |
| btd_error(adapter->dev_id, "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; |
| |
| /* |
| * If we're looking up based on public address and the address |
| * was not previously used over this bearer we may need to |
| * update LE or BR/EDR support information. |
| */ |
| if (bdaddr_type == BDADDR_BREDR) |
| device_set_bredr_support(device); |
| else |
| device_set_le_support(device, bdaddr_type); |
| |
| 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) { |
| btd_error(adapter->dev_id, "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)) { |
| btd_warn(adapter->dev_id, |
| "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; |
| |
| btd_error(adapter->dev_id, "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) { |
| btd_error(adapter->dev_id, "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)) { |
| btd_warn(adapter->dev_id, |
| "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; |
| |
| btd_error(adapter->dev_id, "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) { |
| btd_error(adapter->dev_id, "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; |
| |
| btd_error(adapter->dev_id, "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; |
| |
| 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_cancel(auth->agent); |
| 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)) { |
| btd_error(adapter->dev_id, |
| "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 = SCAN_TYPE_LE; |
| |
| 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; |
| } |
| |
| /* |
| * When the kernel background scanning is available, there is |
| * no need to start any discovery. The kernel will keep scanning |
| * as long as devices are in its auto-connection list. |
| */ |
| if (kernel_conn_control) |
| return; |
| |
| /* |
| * 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; |
| |
| /* |
| * When the kernel background scanning is available, there is |
| * no need to stop any discovery. The kernel will handle the |
| * auto-connection by itself. |
| */ |
| if (kernel_conn_control) |
| return; |
| |
| /* |
| * MGMT_STATUS_REJECTED may be returned from kernel because the passive |
| * scan timer had expired in kernel and passive scan was disabled just |
| * around the time we called stop_passive_scanning(). |
| */ |
| if (status != MGMT_STATUS_SUCCESS && status != MGMT_STATUS_REJECTED) { |
| btd_error(adapter->dev_id, "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) { |
| btd_error(adapter->dev_id, "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 uint8_t get_scan_type(struct btd_adapter *adapter) |
| { |
| uint8_t type; |
| |
| if (adapter->current_settings & MGMT_SETTING_BREDR) |
| type = SCAN_TYPE_BREDR; |
| else |
| type = 0; |
| |
| if (adapter->current_settings & MGMT_SETTING_LE) |
| type |= SCAN_TYPE_LE; |
| |
| return type; |
| } |
| |
| static void free_discovery_filter(struct discovery_filter *discovery_filter) |
| { |
| if (!discovery_filter) |
| return; |
| |
| g_slist_free_full(discovery_filter->uuids, g_free); |
| g_free(discovery_filter); |
| } |
| |
| 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; |
| struct watch_client *client; |
| const struct mgmt_cp_start_discovery *rp = param; |
| DBusMessage *reply; |
| |
| DBG("status 0x%02x", status); |
| |
| /* Is there are no clients the discovery must have been stopped while |
| * discovery command was pending. |
| */ |
| if (!adapter->discovery_list) { |
| struct mgmt_cp_stop_discovery cp; |
| |
| if (status != MGMT_STATUS_SUCCESS) |
| return; |
| |
| /* Stop discovering as there are no clients left */ |
| cp.type = rp->type; |
| mgmt_send(adapter->mgmt, MGMT_OP_STOP_DISCOVERY, |
| adapter->dev_id, sizeof(cp), &cp, |
| NULL, NULL, NULL); |
| return; |
| } |
| |
| client = adapter->discovery_list->data; |
| |
| if (length < sizeof(*rp)) { |
| btd_error(adapter->dev_id, |
| "Wrong size of start discovery return parameters"); |
| if (client->msg) |
| goto fail; |
| return; |
| } |
| |
| if (status == MGMT_STATUS_SUCCESS) { |
| adapter->discovery_type = rp->type; |
| adapter->discovery_enable = 0x01; |
| |
| if (adapter->current_discovery_filter) |
| adapter->filtered_discovery = true; |
| else |
| adapter->filtered_discovery = false; |
| |
| if (client->msg) { |
| g_dbus_send_reply(dbus_conn, client->msg, |
| DBUS_TYPE_INVALID); |
| dbus_message_unref(client->msg); |
| client->msg = NULL; |
| } |
| |
| if (adapter->discovering) |
| return; |
| |
| adapter->discovering = true; |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "Discovering"); |
| return; |
| } |
| |
| fail: |
| /* Reply with an error if the first discovery has failed */ |
| if (client->msg) { |
| reply = btd_error_busy(client->msg); |
| g_dbus_send_message(dbus_conn, reply); |
| g_dbus_remove_watch(dbus_conn, client->watch); |
| 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_service_discovery *sd_cp; |
| uint8_t new_type; |
| |
| DBG(""); |
| |
| adapter->discovery_idle_timeout = 0; |
| |
| /* If we're doing filtered discovery, it must be quickly restarted */ |
| adapter->no_scan_restart_delay = !!adapter->current_discovery_filter; |
| |
| DBG("adapter->current_discovery_filter == %d", |
| !!adapter->current_discovery_filter); |
| |
| new_type = get_scan_type(adapter); |
| |
| if (adapter->discovery_enable == 0x01) { |
| struct mgmt_cp_stop_discovery cp; |
| |
| /* |
| * If we're asked to start regular discovery, and there is an |
| * already running regular discovery and it has the same type, |
| * then just keep it. |
| */ |
| if (!adapter->current_discovery_filter && |
| !adapter->filtered_discovery && |
| 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, or scan type is changed between |
| * regular and filtered, or filter was updated. |
| */ |
| cp.type = adapter->discovery_type; |
| mgmt_send(adapter->mgmt, MGMT_OP_STOP_DISCOVERY, |
| adapter->dev_id, sizeof(cp), &cp, |
| NULL, NULL, NULL); |
| |
| /* Don't even bother to try to quickly start discovery |
| * just after stopping it, it would fail with status |
| * MGMT_BUSY. Instead discovering_callback will take |
| * care of that. |
| */ |
| return FALSE; |
| |
| } |
| |
| /* Regular discovery is required */ |
| if (!adapter->current_discovery_filter) { |
| struct mgmt_cp_start_discovery cp; |
| |
| 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; |
| } |
| |
| /* Filtered discovery is required */ |
| sd_cp = adapter->current_discovery_filter; |
| |
| DBG("sending MGMT_OP_START_SERVICE_DISCOVERY %d, %d, %d", |
| sd_cp->rssi, sd_cp->type, sd_cp->uuid_count); |
| |
| mgmt_send(adapter->mgmt, MGMT_OP_START_SERVICE_DISCOVERY, |
| adapter->dev_id, sizeof(*sd_cp) + sd_cp->uuid_count * 16, |
| sd_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)) { |
| btd_error(adapter->dev_id, "Too small discovering event"); |
| return; |
| } |
| |
| DBG("hci%u type %u discovering %u method %d", adapter->dev_id, ev->type, |
| ev->discovering, adapter->filtered_discovery); |
| |
| 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) { |
| if (!adapter->connect_le) |
| trigger_passive_scanning(adapter); |
| return; |
| } |
| |
| if (adapter->discovery_suspended) |
| return; |
| |
| switch (adapter->discovery_enable) { |
| case 0x00: |
| if (adapter->no_scan_restart_delay) |
| trigger_start_discovery(adapter, 0); |
| else |
| 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 invalidate_rssi_and_tx_power(gpointer a) |
| { |
| struct btd_device *dev = a; |
| |
| device_set_rssi(dev, 0); |
| device_set_tx_power(dev, 127); |
| } |
| |
| 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_device_is_connected(dev)) |
| btd_adapter_remove_device(adapter, dev); |
| } |
| |
| return FALSE; |
| } |
| |
| static void discovery_cleanup(struct btd_adapter *adapter) |
| { |
| GSList *l, *next; |
| |
| 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; |
| } |
| |
| g_slist_free_full(adapter->discovery_found, |
| invalidate_rssi_and_tx_power); |
| adapter->discovery_found = NULL; |
| |
| if (!adapter->devices) |
| return; |
| |
| for (l = adapter->devices; l != NULL; l = next) { |
| struct btd_device *dev = l->data; |
| |
| next = g_slist_next(l); |
| |
| if (device_is_temporary(dev) && !device_is_connectable(dev)) |
| btd_adapter_remove_device(adapter, dev); |
| } |
| |
| adapter->temp_devices_timeout = g_timeout_add_seconds(TEMP_DEV_TIMEOUT, |
| remove_temp_devices, adapter); |
| } |
| |
| static void discovery_free(void *user_data) |
| { |
| struct watch_client *client = user_data; |
| |
| if (client->watch) |
| g_dbus_remove_watch(dbus_conn, client->watch); |
| |
| if (client->discovery_filter) { |
| free_discovery_filter(client->discovery_filter); |
| client->discovery_filter = NULL; |
| } |
| |
| if (client->msg) |
| dbus_message_unref(client->msg); |
| |
| g_free(client->owner); |
| g_free(client); |
| } |
| |
| static void discovery_remove(struct watch_client *client) |
| { |
| struct btd_adapter *adapter = client->adapter; |
| |
| DBG("owner %s", client->owner); |
| |
| adapter->set_filter_list = g_slist_remove(adapter->set_filter_list, |
| client); |
| |
| adapter->discovery_list = g_slist_remove(adapter->discovery_list, |
| client); |
| |
| discovery_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; |
| |
| discovery_cleanup(adapter); |
| } |
| |
| static void stop_discovery_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct watch_client *client = user_data; |
| struct btd_adapter *adapter = client->adapter; |
| DBusMessage *reply; |
| |
| DBG("status 0x%02x", status); |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| if (client->msg) { |
| reply = btd_error_busy(client->msg); |
| g_dbus_send_message(dbus_conn, reply); |
| } |
| goto done; |
| } |
| |
| if (client->msg) |
| g_dbus_send_reply(dbus_conn, client->msg, DBUS_TYPE_INVALID); |
| |
| adapter->discovery_type = 0x00; |
| adapter->discovery_enable = 0x00; |
| adapter->filtered_discovery = false; |
| adapter->no_scan_restart_delay = false; |
| adapter->discovering = false; |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "Discovering"); |
| |
| trigger_passive_scanning(adapter); |
| |
| done: |
| discovery_remove(client); |
| } |
| |
| 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 gint g_strcmp(gconstpointer a, gconstpointer b) |
| { |
| return strcmp(a, b); |
| } |
| |
| static void extract_unique_uuids(gpointer data, gpointer user_data) |
| { |
| char *uuid_str = data; |
| GSList **uuids = user_data; |
| |
| if (!g_slist_find_custom(*uuids, uuid_str, g_strcmp)) |
| *uuids = g_slist_insert_sorted(*uuids, uuid_str, g_strcmp); |
| } |
| |
| /* |
| * This method merges all adapter filters into rssi, transport and uuids. |
| * Returns 1 if there was no filtered scan, 0 otherwise. |
| */ |
| static int merge_discovery_filters(struct btd_adapter *adapter, int *rssi, |
| uint8_t *transport, GSList **uuids) |
| { |
| GSList *l; |
| bool empty_uuid = false; |
| bool has_regular_discovery = false; |
| bool has_filtered_discovery = false; |
| |
| for (l = adapter->discovery_list; l != NULL; l = g_slist_next(l)) { |
| struct watch_client *client = l->data; |
| struct discovery_filter *item = client->discovery_filter; |
| |
| if (!item) { |
| has_regular_discovery = true; |
| continue; |
| } |
| |
| has_filtered_discovery = true; |
| |
| *transport |= item->type; |
| |
| /* |
| * Rule for merging rssi and pathloss into rssi field of kernel |
| * filter is as follow: |
| * - if there's any client without proximity filter, then do no |
| * proximity filtering, |
| * - if all clients specified RSSI, then use lowest value, |
| * - if any client specified pathloss, then kernel filter should |
| * do no proximity, as kernel can't compute pathloss. We'll do |
| * filtering on our own. |
| */ |
| if (item->rssi == DISTANCE_VAL_INVALID) |
| *rssi = HCI_RSSI_INVALID; |
| else if (*rssi != HCI_RSSI_INVALID && *rssi >= item->rssi) |
| *rssi = item->rssi; |
| else if (item->pathloss != DISTANCE_VAL_INVALID) |
| *rssi = HCI_RSSI_INVALID; |
| |
| if (!g_slist_length(item->uuids)) |
| empty_uuid = true; |
| |
| g_slist_foreach(item->uuids, extract_unique_uuids, uuids); |
| } |
| |
| /* If no proximity filtering is set, disable it */ |
| if (*rssi == DISTANCE_VAL_INVALID) |
| *rssi = HCI_RSSI_INVALID; |
| |
| /* |
| * Empty_uuid variable determines wether there was any filter with no |
| * uuids. In this case someone might be looking for all devices in |
| * certain proximity, and we need to have empty uuids in kernel filter. |
| */ |
| if (empty_uuid) { |
| g_slist_free(*uuids); |
| *uuids = NULL; |
| } |
| |
| if (has_regular_discovery) { |
| if (!has_filtered_discovery) |
| return 1; |
| |
| /* |
| * It there is both regular and filtered scan running, then |
| * clear whole fitler to report all devices. |
| */ |
| *transport = get_scan_type(adapter); |
| *rssi = HCI_RSSI_INVALID; |
| g_slist_free(*uuids); |
| *uuids = NULL; |
| } |
| |
| return 0; |
| } |
| |
| static void populate_mgmt_filter_uuids(uint8_t (*mgmt_uuids)[16], GSList *uuids) |
| { |
| GSList *l; |
| |
| for (l = uuids; l != NULL; l = g_slist_next(l)) { |
| bt_uuid_t uuid, u128; |
| uint128_t uint128; |
| |
| bt_string_to_uuid(&uuid, l->data); |
| bt_uuid_to_uuid128(&uuid, &u128); |
| |
| ntoh128((uint128_t *) u128.value.u128.data, &uint128); |
| htob128(&uint128, (uint128_t *) mgmt_uuids); |
| |
| mgmt_uuids++; |
| } |
| } |
| |
| /* |
| * This method merges all adapter filters into one that will be send to kernel. |
| * cp_ptr is set to null when regular non-filtered discovery is needed, |
| * otherwise it's pointing to filter. Returns 0 on succes, -1 on error |
| */ |
| static int discovery_filter_to_mgmt_cp(struct btd_adapter *adapter, |
| struct mgmt_cp_start_service_discovery **cp_ptr) |
| { |
| GSList *uuids = NULL; |
| struct mgmt_cp_start_service_discovery *cp; |
| int rssi = DISTANCE_VAL_INVALID; |
| int uuid_count; |
| uint8_t discovery_type = 0; |
| |
| DBG(""); |
| |
| if (merge_discovery_filters(adapter, &rssi, &discovery_type, &uuids)) { |
| /* There are only regular scans, run just regular scan. */ |
| *cp_ptr = NULL; |
| return 0; |
| } |
| |
| uuid_count = g_slist_length(uuids); |
| |
| cp = g_try_malloc(sizeof(*cp) + 16*uuid_count); |
| *cp_ptr = cp; |
| if (!cp) { |
| g_slist_free(uuids); |
| return -1; |
| } |
| |
| cp->type = discovery_type; |
| cp->rssi = rssi; |
| cp->uuid_count = uuid_count; |
| populate_mgmt_filter_uuids(cp->uuids, uuids); |
| |
| g_slist_free(uuids); |
| return 0; |
| } |
| |
| static bool filters_equal(struct mgmt_cp_start_service_discovery *a, |
| struct mgmt_cp_start_service_discovery *b) { |
| if (!a && !b) |
| return true; |
| |
| if ((!a && b) || (a && !b)) |
| return false; |
| |
| if (a->type != b->type) |
| return false; |
| |
| if (a->rssi != b->rssi) |
| return false; |
| |
| /* |
| * When we create mgmt_cp_start_service_discovery structure inside |
| * discovery_filter_to_mgmt_cp, we always keep uuids sorted, and |
| * unique, so we're safe to compare uuid_count, and uuids like that. |
| */ |
| if (a->uuid_count != b->uuid_count) |
| return false; |
| |
| if (memcmp(a->uuids, b->uuids, 16 * a->uuid_count) != 0) |
| return false; |
| |
| return true; |
| } |
| |
| static int update_discovery_filter(struct btd_adapter *adapter) |
| { |
| struct mgmt_cp_start_service_discovery *sd_cp; |
| |
| DBG(""); |
| |
| if (discovery_filter_to_mgmt_cp(adapter, &sd_cp)) { |
| btd_error(adapter->dev_id, |
| "discovery_filter_to_mgmt_cp returned error"); |
| return -ENOMEM; |
| } |
| |
| /* |
| * If filters are equal, then don't update scan, except for when |
| * starting discovery. |
| */ |
| if (filters_equal(adapter->current_discovery_filter, sd_cp) && |
| adapter->discovering != 0) { |
| DBG("filters were equal, deciding to not restart the scan."); |
| g_free(sd_cp); |
| return 0; |
| } |
| |
| g_free(adapter->current_discovery_filter); |
| adapter->current_discovery_filter = sd_cp; |
| |
| trigger_start_discovery(adapter, 0); |
| |
| return -EINPROGRESS; |
| } |
| |
| static int discovery_stop(struct watch_client *client) |
| { |
| struct btd_adapter *adapter = client->adapter; |
| struct mgmt_cp_stop_discovery cp; |
| |
| /* Check if there are more client discovering */ |
| if (g_slist_next(adapter->discovery_list)) { |
| discovery_remove(client); |
| update_discovery_filter(adapter); |
| return 0; |
| } |
| |
| /* |
| * 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) { |
| discovery_remove(client); |
| adapter->discovering = false; |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "Discovering"); |
| |
| trigger_passive_scanning(adapter); |
| |
| return 0; |
| } |
| |
| cp.type = adapter->discovery_type; |
| |
| mgmt_send(adapter->mgmt, MGMT_OP_STOP_DISCOVERY, |
| adapter->dev_id, sizeof(cp), &cp, |
| stop_discovery_complete, client, NULL); |
| |
| return -EINPROGRESS; |
| } |
| |
| static void discovery_disconnect(DBusConnection *conn, void *user_data) |
| { |
| struct watch_client *client = user_data; |
| |
| DBG("owner %s", client->owner); |
| |
| discovery_stop(client); |
| } |
| |
| /* |
| * Returns true if client was already discovering, false otherwise. *client |
| * will point to discovering client, or client that have pre-set his filter. |
| */ |
| static bool get_discovery_client(struct btd_adapter *adapter, |
| const char *owner, |
| struct watch_client **client) |
| { |
| GSList *list = g_slist_find_custom(adapter->discovery_list, owner, |
| compare_sender); |
| if (list) { |
| *client = list->data; |
| return true; |
| } |
| |
| list = g_slist_find_custom(adapter->set_filter_list, owner, |
| compare_sender); |
| if (list) { |
| *client = list->data; |
| return false; |
| } |
| |
| *client = NULL; |
| return false; |
| } |
| |
| 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; |
| bool is_discovering; |
| int err; |
| |
| DBG("sender %s", sender); |
| |
| if (!(adapter->current_settings & MGMT_SETTING_POWERED)) |
| return btd_error_not_ready(msg); |
| |
| is_discovering = get_discovery_client(adapter, sender, &client); |
| |
| /* |
| * Every client can only start one discovery, if the client |
| * already started a discovery then return an error. |
| */ |
| if (is_discovering) |
| return btd_error_busy(msg); |
| |
| /* |
| * If there was pre-set filter, just reconnect it to discovery_list, |
| * and trigger scan. |
| */ |
| if (client) { |
| if (client->msg) |
| return btd_error_busy(msg); |
| |
| adapter->set_filter_list = g_slist_remove( |
| adapter->set_filter_list, client); |
| adapter->discovery_list = g_slist_prepend( |
| adapter->discovery_list, client); |
| goto done; |
| } |
| |
| client = g_new0(struct watch_client, 1); |
| |
| client->adapter = adapter; |
| client->owner = g_strdup(sender); |
| client->discovery_filter = NULL; |
| client->watch = g_dbus_add_disconnect_watch(dbus_conn, sender, |
| discovery_disconnect, client, |
| NULL); |
| adapter->discovery_list = g_slist_prepend(adapter->discovery_list, |
| client); |
| |
| done: |
| /* |
| * Just trigger the discovery here. In case an already running |
| * discovery in idle phase exists, it will be restarted right |
| * away. |
| */ |
| err = update_discovery_filter(adapter); |
| if (!err) |
| return dbus_message_new_method_return(msg); |
| |
| /* If the discovery has to be started wait it complete to reply */ |
| if (err == -EINPROGRESS) { |
| client->msg = dbus_message_ref(msg); |
| return NULL; |
| } |
| |
| return btd_error_failed(msg, strerror(-err)); |
| } |
| |
| static bool parse_uuids(DBusMessageIter *value, struct discovery_filter *filter) |
| { |
| DBusMessageIter arriter; |
| |
| if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_ARRAY) |
| return false; |
| |
| dbus_message_iter_recurse(value, &arriter); |
| while (dbus_message_iter_get_arg_type(&arriter) != DBUS_TYPE_INVALID) { |
| bt_uuid_t uuid, u128; |
| char uuidstr[MAX_LEN_UUID_STR + 1]; |
| char *uuid_param; |
| |
| if (dbus_message_iter_get_arg_type(&arriter) != |
| DBUS_TYPE_STRING) |
| return false; |
| |
| dbus_message_iter_get_basic(&arriter, &uuid_param); |
| |
| if (bt_string_to_uuid(&uuid, uuid_param)) |
| return false; |
| |
| bt_uuid_to_uuid128(&uuid, &u128); |
| bt_uuid_to_string(&u128, uuidstr, sizeof(uuidstr)); |
| |
| filter->uuids = g_slist_prepend(filter->uuids, strdup(uuidstr)); |
| |
| dbus_message_iter_next(&arriter); |
| } |
| |
| return true; |
| } |
| |
| static bool parse_rssi(DBusMessageIter *value, struct discovery_filter *filter) |
| { |
| if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_INT16) |
| return false; |
| |
| dbus_message_iter_get_basic(value, &filter->rssi); |
| /* -127 <= RSSI <= +20 (spec V4.2 [Vol 2, Part E] 7.7.65.2) */ |
| if (filter->rssi > 20 || filter->rssi < -127) |
| return false; |
| |
| return true; |
| } |
| |
| static bool parse_pathloss(DBusMessageIter *value, |
| struct discovery_filter *filter) |
| { |
| if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_UINT16) |
| return false; |
| |
| dbus_message_iter_get_basic(value, &filter->pathloss); |
| /* pathloss filter must be smaller that PATHLOSS_MAX */ |
| if (filter->pathloss > PATHLOSS_MAX) |
| return false; |
| |
| return true; |
| } |
| |
| static bool parse_transport(DBusMessageIter *value, |
| struct discovery_filter *filter) |
| { |
| char *transport_str; |
| |
| if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_STRING) |
| return false; |
| |
| dbus_message_iter_get_basic(value, &transport_str); |
| |
| if (!strcmp(transport_str, "bredr")) |
| filter->type = SCAN_TYPE_BREDR; |
| else if (!strcmp(transport_str, "le")) |
| filter->type = SCAN_TYPE_LE; |
| else if (strcmp(transport_str, "auto")) |
| return false; |
| |
| return true; |
| } |
| |
| static bool parse_duplicate_data(DBusMessageIter *value, |
| struct discovery_filter *filter) |
| { |
| if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN) |
| return false; |
| |
| dbus_message_iter_get_basic(value, &filter->duplicate); |
| |
| return true; |
| } |
| |
| struct filter_parser { |
| const char *name; |
| bool (*func)(DBusMessageIter *iter, struct discovery_filter *filter); |
| } parsers[] = { |
| { "UUIDs", parse_uuids }, |
| { "RSSI", parse_rssi }, |
| { "Pathloss", parse_pathloss }, |
| { "Transport", parse_transport }, |
| { "DuplicateData", parse_duplicate_data }, |
| { } |
| }; |
| |
| static bool parse_discovery_filter_entry(char *key, DBusMessageIter *value, |
| struct discovery_filter *filter) |
| { |
| struct filter_parser *parser; |
| |
| for (parser = parsers; parser && parser->name; parser++) { |
| if (!strcmp(parser->name, key)) |
| return parser->func(value, filter); |
| } |
| |
| DBG("Unknown key parameter: %s!\n", key); |
| return false; |
| } |
| |
| /* |
| * This method is responsible for parsing parameters to SetDiscoveryFilter. If |
| * filter in msg was empty, sets *filter to NULL. If whole parsing was |
| * successful, sets *filter to proper value. |
| * Returns false on any error, and true on success. |
| */ |
| static bool parse_discovery_filter_dict(struct btd_adapter *adapter, |
| struct discovery_filter **filter, |
| DBusMessage *msg) |
| { |
| DBusMessageIter iter, subiter, dictiter, variantiter; |
| bool is_empty = true; |
| |
| *filter = g_try_malloc(sizeof(**filter)); |
| if (!*filter) |
| return false; |
| |
| (*filter)->uuids = NULL; |
| (*filter)->pathloss = DISTANCE_VAL_INVALID; |
| (*filter)->rssi = DISTANCE_VAL_INVALID; |
| (*filter)->type = get_scan_type(adapter); |
| (*filter)->duplicate = false; |
| |
| dbus_message_iter_init(msg, &iter); |
| if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || |
| dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) |
| goto invalid_args; |
| |
| dbus_message_iter_recurse(&iter, &subiter); |
| do { |
| int type = dbus_message_iter_get_arg_type(&subiter); |
| char *key; |
| |
| if (type == DBUS_TYPE_INVALID) |
| break; |
| |
| is_empty = false; |
| dbus_message_iter_recurse(&subiter, &dictiter); |
| |
| dbus_message_iter_get_basic(&dictiter, &key); |
| if (!dbus_message_iter_next(&dictiter)) |
| goto invalid_args; |
| |
| if (dbus_message_iter_get_arg_type(&dictiter) != |
| DBUS_TYPE_VARIANT) |
| goto invalid_args; |
| |
| dbus_message_iter_recurse(&dictiter, &variantiter); |
| |
| if (!parse_discovery_filter_entry(key, &variantiter, *filter)) |
| goto invalid_args; |
| |
| dbus_message_iter_next(&subiter); |
| } while (true); |
| |
| if (is_empty) { |
| g_free(*filter); |
| *filter = NULL; |
| return true; |
| } |
| |
| /* only pathlos or rssi can be set, never both */ |
| if ((*filter)->pathloss != DISTANCE_VAL_INVALID && |
| (*filter)->rssi != DISTANCE_VAL_INVALID) |
| goto invalid_args; |
| |
| DBG("filtered discovery params: transport: %d rssi: %d pathloss: %d " |
| " duplicate data: %s ", (*filter)->type, (*filter)->rssi, |
| (*filter)->pathloss, (*filter)->duplicate ? "true" : "false"); |
| |
| return true; |
| |
| invalid_args: |
| g_slist_free_full((*filter)->uuids, g_free); |
| g_free(*filter); |
| *filter = NULL; |
| return false; |
| } |
| |
| static DBusMessage *set_discovery_filter(DBusConnection *conn, |
| DBusMessage *msg, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| struct watch_client *client; |
| struct discovery_filter *discovery_filter; |
| const char *sender = dbus_message_get_sender(msg); |
| bool is_discovering; |
| |
| DBG("sender %s", sender); |
| |
| if (!(adapter->current_settings & MGMT_SETTING_POWERED)) |
| return btd_error_not_ready(msg); |
| |
| if (MGMT_VERSION(mgmt_version, mgmt_revision) < MGMT_VERSION(1, 8)) |
| return btd_error_not_supported(msg); |
| |
| /* parse parameters */ |
| if (!parse_discovery_filter_dict(adapter, &discovery_filter, msg)) |
| return btd_error_invalid_args(msg); |
| |
| is_discovering = get_discovery_client(adapter, sender, &client); |
| |
| if (client) { |
| free_discovery_filter(client->discovery_filter); |
| client->discovery_filter = discovery_filter; |
| |
| if (is_discovering) |
| update_discovery_filter(adapter); |
| |
| if (discovery_filter || is_discovering) |
| return dbus_message_new_method_return(msg); |
| |
| /* Removing pre-set filter */ |
| adapter->set_filter_list = g_slist_remove( |
| adapter->set_filter_list, |
| client); |
| discovery_free(client); |
| DBG("successfully cleared pre-set filter"); |
| } else if (discovery_filter) { |
| /* Client pre-setting his filter for first time */ |
| client = g_new0(struct watch_client, 1); |
| client->adapter = adapter; |
| client->owner = g_strdup(sender); |
| client->discovery_filter = discovery_filter; |
| client->watch = g_dbus_add_disconnect_watch(dbus_conn, sender, |
| discovery_disconnect, client, |
| NULL); |
| adapter->set_filter_list = g_slist_prepend( |
| adapter->set_filter_list, client); |
| |
| DBG("successfully pre-set filter"); |
| } |
| |
| 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 watch_client *client; |
| GSList *list; |
| int err; |
| |
| 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; |
| |
| if (client->msg) |
| return btd_error_busy(msg); |
| |
| err = discovery_stop(client); |
| switch (err) { |
| case 0: |
| return dbus_message_new_method_return(msg); |
| case -EINPROGRESS: |
| client->msg = dbus_message_ref(msg); |
| return NULL; |
| default: |
| return btd_error_failed(msg, strerror(-err)); |
| } |
| } |
| |
| 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_address_type(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| const char *str; |
| |
| if ((adapter->current_settings & MGMT_SETTING_LE) && |
| (adapter->bdaddr_type == BDADDR_LE_RANDOM)) |
| str = "random"; |
| else |
| str = "public"; |
| |
| 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; |
| |
| btd_error(adapter->dev_id, "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 clear_discoverable(struct btd_adapter *adapter) |
| { |
| if (!kernel_conn_control) |
| return; |
| |
| if (!(adapter->current_settings & MGMT_SETTING_DISCOVERABLE)) |
| return; |
| |
| /* If no timeout is set do nothing as both connectable and discoverable |
| * flags are persistent on power toggle. |
| */ |
| if (!adapter->discoverable_timeout) |
| return; |
| |
| /* If timeout was set kernel clears discoverable on its own when |
| * powering off controller. This would leave connectable flag set |
| * after power on. |
| * |
| * With kernel control clearing connectable clear also discoverable |
| * flag so we need to clear connectable. |
| */ |
| set_mode(adapter, MGMT_OP_SET_CONNECTABLE, 0x00); |
| } |
| |
| 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); |
| |
| if (!mode) |
| clear_discoverable(adapter); |
| |
| break; |
| case MGMT_SETTING_DISCOVERABLE: |
| if (kernel_conn_control) { |
| if (mode) { |
| set_mode(adapter, MGMT_OP_SET_CONNECTABLE, |
| mode); |
| } else { |
| opcode = MGMT_OP_SET_CONNECTABLE; |
| param = &mode; |
| len = sizeof(mode); |
| break; |
| } |
| } |
| |
| 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_BONDABLE: |
| opcode = MGMT_OP_SET_BONDABLE; |
| 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: |
| btd_error(adapter->dev_id, "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; |
| |
| if (adapter->discoverable_timeout > 0 && |
| !(adapter->current_settings & MGMT_SETTING_POWERED)) { |
| g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", |
| "Not Powered"); |
| return; |
| } |
| |
| 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_BONDABLE, 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_BONDABLE, 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); |
| |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "PairableTimeout"); |
| |
| 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 void add_gatt_uuid(struct gatt_db_attribute *attrib, void *user_data) |
| { |
| GHashTable *uuids = user_data; |
| bt_uuid_t uuid, u128; |
| char uuidstr[MAX_LEN_UUID_STR + 1]; |
| |
| if (!gatt_db_service_get_active(attrib)) |
| return; |
| |
| if (!gatt_db_attribute_get_service_uuid(attrib, &uuid)) |
| return; |
| |
| bt_uuid_to_uuid128(&uuid, &u128); |
| bt_uuid_to_string(&u128, uuidstr, sizeof(uuidstr)); |
| |
| g_hash_table_add(uuids, strdup(uuidstr)); |
| } |
| |
| static void iter_append_uuid(gpointer key, gpointer value, gpointer user_data) |
| { |
| DBusMessageIter *iter = user_data; |
| const char *uuid = key; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); |
| } |
| |
| 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; |
| struct gatt_db *db; |
| GHashTable *uuids; |
| |
| uuids = g_hash_table_new_full(g_str_hash, g_str_equal, free, NULL); |
| if (!uuids) |
| return FALSE; |
| |
| /* SDP records */ |
| 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; |
| |
| g_hash_table_add(uuids, uuid); |
| } |
| |
| /* GATT services */ |
| db = btd_gatt_database_get_db(adapter->database); |
| if (db) |
| gatt_db_foreach_service(db, NULL, add_gatt_uuid, uuids); |
| |
| dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_STRING_AS_STRING, &entry); |
| g_hash_table_foreach(uuids, iter_append_uuid, &entry); |
| dbus_message_iter_close_container(iter, &entry); |
| |
| g_hash_table_destroy(uuids); |
| |
| 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 DBusMessage *get_discovery_filters(DBusConnection *conn, |
| DBusMessage *msg, void *user_data) |
| { |
| DBusMessage *reply; |
| DBusMessageIter iter, array; |
| struct filter_parser *parser; |
| |
| reply = dbus_message_new_method_return(msg); |
| |
| dbus_message_iter_init_append(reply, &iter); |
| |
| dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_STRING_AS_STRING, &array); |
| |
| for (parser = parsers; parser && parser->name; parser++) { |
| dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, |
| &parser->name); |
| } |
| |
| dbus_message_iter_close_container(&iter, &array); |
| |
| return reply; |
| } |
| |
| struct device_connect_data { |
| struct btd_adapter *adapter; |
| bdaddr_t dst; |
| uint8_t dst_type; |
| DBusMessage *msg; |
| }; |
| |
| static void device_browse_cb(struct btd_device *dev, int err, void *user_data) |
| { |
| DBG("err %d (%s)", err, strerror(-err)); |
| |
| if (!err) |
| btd_device_connect_services(dev, NULL); |
| } |
| |
| static void device_connect_cb(GIOChannel *io, GError *gerr, gpointer user_data) |
| { |
| struct device_connect_data *data = user_data; |
| struct btd_adapter *adapter = data->adapter; |
| struct btd_device *device; |
| const char *path; |
| |
| DBG("%s", gerr ? gerr->message : ""); |
| |
| if (gerr) |
| goto failed; |
| |
| /* object might already exist due to mgmt socket event */ |
| device = btd_adapter_get_device(adapter, &data->dst, data->dst_type); |
| if (!device) |
| goto failed; |
| |
| path = device_get_path(device); |
| |
| g_dbus_send_reply(dbus_conn, data->msg, DBUS_TYPE_OBJECT_PATH, &path, |
| DBUS_TYPE_INVALID); |
| |
| /* continue with service discovery and connection */ |
| btd_device_set_temporary(device, false); |
| device_update_last_seen(device, data->dst_type); |
| |
| if (data->dst_type != BDADDR_BREDR){ |
| g_io_channel_set_close_on_unref(io, FALSE); |
| device_attach_att(device, io); |
| } |
| |
| device_discover_services(device); |
| device_wait_for_svc_complete(device, device_browse_cb, NULL); |
| |
| g_io_channel_unref(io); |
| dbus_message_unref(data->msg); |
| free(data); |
| return; |
| |
| failed: |
| g_dbus_send_error(dbus_conn, data->msg, "org.bluez.Failed", NULL); |
| g_io_channel_unref(io); |
| dbus_message_unref(data->msg); |
| free(data); |
| } |
| |
| static void device_connect(struct btd_adapter *adapter, const bdaddr_t *dst, |
| uint8_t dst_type, DBusMessage *msg) |
| { |
| struct device_connect_data *data; |
| GIOChannel *io; |
| |
| data = new0(struct device_connect_data, 1); |
| data->adapter = adapter; |
| bacpy(&data->dst, dst); |
| data->dst_type = dst_type; |
| data->msg = dbus_message_ref(msg); |
| |
| if (dst_type == BDADDR_BREDR) |
| io = bt_io_connect(device_connect_cb, data, NULL, NULL, |
| BT_IO_OPT_SOURCE_BDADDR, &adapter->bdaddr, |
| BT_IO_OPT_SOURCE_TYPE, BDADDR_BREDR, |
| BT_IO_OPT_DEST_BDADDR, dst, |
| BT_IO_OPT_DEST_TYPE, BDADDR_BREDR, |
| BT_IO_OPT_PSM, SDP_PSM, |
| BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, |
| BT_IO_OPT_INVALID); |
| else |
| io = bt_io_connect(device_connect_cb, data, NULL, NULL, |
| BT_IO_OPT_SOURCE_BDADDR, &adapter->bdaddr, |
| BT_IO_OPT_SOURCE_TYPE, adapter->bdaddr_type, |
| BT_IO_OPT_DEST_BDADDR, dst, |
| BT_IO_OPT_DEST_TYPE, dst_type, |
| BT_IO_OPT_CID, ATT_CID, |
| BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, |
| BT_IO_OPT_INVALID); |
| |
| if (!io) { |
| g_dbus_send_message(dbus_conn, |
| btd_error_failed(msg, "Connect failed")); |
| dbus_message_unref(data->msg); |
| free(data); |
| } |
| } |
| |
| static DBusMessage *connect_device(DBusConnection *conn, |
| DBusMessage *msg, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| DBusMessageIter iter, subiter, dictiter, value; |
| uint8_t addr_type = BDADDR_BREDR; |
| bdaddr_t addr = *BDADDR_ANY; |
| |
| DBG("sender %s", dbus_message_get_sender(msg)); |
| |
| if (!(adapter->current_settings & MGMT_SETTING_POWERED)) |
| return btd_error_not_ready(msg); |
| |
| dbus_message_iter_init(msg, &iter); |
| if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || |
| dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) |
| return btd_error_invalid_args(msg); |
| |
| dbus_message_iter_recurse(&iter, &subiter); |
| while (true) { |
| int type = dbus_message_iter_get_arg_type(&subiter); |
| char *key; |
| char *str; |
| |
| if (type == DBUS_TYPE_INVALID) |
| break; |
| |
| dbus_message_iter_recurse(&subiter, &dictiter); |
| |
| dbus_message_iter_get_basic(&dictiter, &key); |
| if (!dbus_message_iter_next(&dictiter)) |
| return btd_error_invalid_args(msg); |
| |
| if (dbus_message_iter_get_arg_type(&dictiter) != |
| DBUS_TYPE_VARIANT) |
| return btd_error_invalid_args(msg); |
| |
| dbus_message_iter_recurse(&dictiter, &value); |
| |
| if (!strcmp(key, "Address")) { |
| if (dbus_message_iter_get_arg_type(&value) != |
| DBUS_TYPE_STRING) |
| return btd_error_invalid_args(msg); |
| |
| dbus_message_iter_get_basic(&value, &str); |
| |
| if (str2ba(str, &addr) < 0 ) |
| return btd_error_invalid_args(msg); |
| } else if (!strcmp(key, "AddressType")) { |
| if (dbus_message_iter_get_arg_type(&value) != |
| DBUS_TYPE_STRING) |
| return btd_error_invalid_args(msg); |
| |
| dbus_message_iter_get_basic(&value, &str); |
| |
| |
| if (!strcmp(str, "public")) |
| addr_type = BDADDR_LE_PUBLIC; |
| else if (!strcmp(str, "random")) |
| addr_type = BDADDR_LE_RANDOM; |
| else |
| return btd_error_invalid_args(msg); |
| } else { |
| return btd_error_invalid_args(msg); |
| } |
| |
| dbus_message_iter_next(&subiter); |
| } |
| |
| if (!bacmp(&addr, BDADDR_ANY)) |
| return btd_error_invalid_args(msg); |
| |
| if (btd_adapter_find_device(adapter, &addr, addr_type)) |
| return btd_error_already_exists(msg); |
| |
| device_connect(adapter, &addr, addr_type, msg); |
| return NULL; |
| } |
| |
| static const GDBusMethodTable adapter_methods[] = { |
| { GDBUS_ASYNC_METHOD("StartDiscovery", NULL, NULL, start_discovery) }, |
| { GDBUS_METHOD("SetDiscoveryFilter", |
| GDBUS_ARGS({ "properties", "a{sv}" }), NULL, |
| set_discovery_filter) }, |
| { GDBUS_ASYNC_METHOD("StopDiscovery", NULL, NULL, stop_discovery) }, |
| { GDBUS_ASYNC_METHOD("RemoveDevice", |
| GDBUS_ARGS({ "device", "o" }), NULL, remove_device) }, |
| { GDBUS_METHOD("GetDiscoveryFilters", NULL, |
| GDBUS_ARGS({ "filters", "as" }), |
| get_discovery_filters) }, |
| { GDBUS_EXPERIMENTAL_ASYNC_METHOD("ConnectDevice", |
| GDBUS_ARGS({ "properties", "a{sv}" }), NULL, |
| connect_device) }, |
| { } |
| }; |
| |
| static const GDBusPropertyTable adapter_properties[] = { |
| { "Address", "s", property_get_address }, |
| { "AddressType", "s", property_get_address_type }, |
| { "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 = NULL; |
| char *str; |
| |
| str = g_key_file_get_string(key_file, "IdentityResolvingKey", "Key", NULL); |
| if (!str || strlen(str) < 32) |
| goto failed; |
| |
| 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)); |
| |
| failed: |
| g_free(str); |
| |
| return irk; |
| } |
| |
| static struct conn_param *get_conn_param(GKeyFile *key_file, const char *peer, |
| uint8_t bdaddr_type) |
| { |
| struct conn_param *param; |
| |
| if (!g_key_file_has_group(key_file, "ConnectionParameters")) |
| return NULL; |
| |
| param = g_new0(struct conn_param, 1); |
| |
| param->min_interval = g_key_file_get_integer(key_file, |
| "ConnectionParameters", |
| "MinInterval", NULL); |
| param->max_interval = g_key_file_get_integer(key_file, |
| "ConnectionParameters", |
| "MaxInterval", NULL); |
| param->latency = g_key_file_get_integer(key_file, |
| "ConnectionParameters", |
| "Latency", NULL); |
| param->timeout = g_key_file_get_integer(key_file, |
| "ConnectionParameters", |
| "Timeout", NULL); |
| str2ba(peer, ¶m->bdaddr); |
| param->bdaddr_type = bdaddr_type; |
| |
| return param; |
| } |
| |
| static int generate_and_write_irk(uint8_t *irk, GKeyFile *key_file, |
| const char *filename) |
| { |
| struct bt_crypto *crypto; |
| char str_irk_out[33]; |
| gsize length = 0; |
| char *str; |
| int i; |
| |
| crypto = bt_crypto_new(); |
| if (!crypto) { |
| error("Failed to open crypto"); |
| return -1; |
| } |
| |
| if (!bt_crypto_random_bytes(crypto, irk, 16)) { |
| error("Failed to generate IRK"); |
| bt_crypto_unref(crypto); |
| return -1; |
| } |
| |
| bt_crypto_unref(crypto); |
| |
| for (i = 0; i < 16; i++) |
| sprintf(str_irk_out + (i * 2), "%02x", irk[i]); |
| |
| str_irk_out[32] = '\0'; |
| info("Generated IRK successfully"); |
| |
| g_key_file_set_string(key_file, "General", "IdentityResolvingKey", |
| str_irk_out); |
| str = g_key_file_to_data(key_file, &length, NULL); |
| g_file_set_contents(filename, str, length, NULL); |
| g_free(str); |
| DBG("Generated IRK written to file"); |
| return 0; |
| } |
| |
| static int load_irk(struct btd_adapter *adapter, uint8_t *irk) |
| { |
| char filename[PATH_MAX]; |
| GKeyFile *key_file; |
| char *str_irk; |
| int ret; |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/identity", |
| btd_adapter_get_storage_dir(adapter)); |
| |
| key_file = g_key_file_new(); |
| g_key_file_load_from_file(key_file, filename, 0, NULL); |
| |
| str_irk = g_key_file_get_string(key_file, "General", |
| "IdentityResolvingKey", NULL); |
| if (!str_irk) { |
| info("No IRK stored"); |
| ret = generate_and_write_irk(irk, key_file, filename); |
| g_key_file_free(key_file); |
| return ret; |
| } |
| |
| g_key_file_free(key_file); |
| |
| if (strlen(str_irk) != 32 || str2buf(str_irk, irk, 16)) { |
| /* TODO re-create new IRK here? */ |
| error("Invalid IRK format, disabling privacy"); |
| g_free(str_irk); |
| return -1; |
| } |
| |
| g_free(str_irk); |
| DBG("Successfully read IRK from file"); |
| return 0; |
| } |
| |
| static void set_privacy_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| btd_error(adapter->dev_id, "Failed to set privacy: %s (0x%02x)", |
| mgmt_errstr(status), status); |
| return; |
| } |
| |
| DBG("Successfuly set privacy for index %u", adapter->dev_id); |
| } |
| |
| static int set_privacy(struct btd_adapter *adapter, uint8_t privacy) |
| { |
| struct mgmt_cp_set_privacy cp; |
| |
| memset(&cp, 0, sizeof(cp)); |
| |
| if (privacy) { |
| uint8_t irk[16]; |
| |
| if (load_irk(adapter, irk) == 0) { |
| cp.privacy = privacy; |
| memcpy(cp.irk, irk, 16); |
| } |
| } |
| |
| DBG("sending set privacy command for index %u", adapter->dev_id); |
| DBG("setting privacy mode 0x%02x for index %u", cp.privacy, |
| adapter->dev_id); |
| |
| if (mgmt_send(adapter->mgmt, MGMT_OP_SET_PRIVACY, |
| adapter->dev_id, sizeof(cp), &cp, |
| set_privacy_complete, adapter, NULL) > 0) |
| return 0; |
| |
| btd_error(adapter->dev_id, "Failed to set privacy for index %u", |
| adapter->dev_id); |
| |
| return -1; |
| } |
| |
| 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) { |
| btd_error(adapter->dev_id, |
| "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) { |
| btd_error(adapter->dev_id, "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) |
| btd_error(adapter->dev_id, "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; |
| |
| btd_error(adapter->dev_id, "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) { |
| btd_error(adapter->dev_id, |
| "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) { |
| btd_error(adapter->dev_id, "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) { |
| btd_error(adapter->dev_id, "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) { |
| btd_info(adapter->dev_id, |
| "Load IRKs failed: Kernel doesn't support LE Privacy"); |
| return; |
| } |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| btd_error(adapter->dev_id, |
| "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) { |
| btd_error(adapter->dev_id, "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) |
| btd_error(adapter->dev_id, "Failed to IRKs for hci%u", |
| adapter->dev_id); |
| } |
| |
| static void load_conn_params_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| btd_error(adapter->dev_id, |
| "hci%u Load Connection Parameters failed: %s (0x%02x)", |
| adapter->dev_id, mgmt_errstr(status), status); |
| return; |
| } |
| |
| DBG("Connection Parameters loaded for hci%u", adapter->dev_id); |
| } |
| |
| static void load_conn_params(struct btd_adapter *adapter, GSList *params) |
| { |
| struct mgmt_cp_load_conn_param *cp; |
| struct mgmt_conn_param *param; |
| size_t param_count, cp_size; |
| unsigned int id; |
| GSList *l; |
| |
| /* |
| * If the controller does not support Low Energy operation, |
| * there is no point in trying to load the connection |
| * parameters into the kernel. |
| */ |
| if (!(adapter->supported_settings & MGMT_SETTING_LE)) |
| return; |
| |
| param_count = g_slist_length(params); |
| |
| DBG("hci%u conn params %zu", adapter->dev_id, param_count); |
| |
| cp_size = sizeof(*cp) + (param_count * sizeof(*param)); |
| |
| cp = g_try_malloc0(cp_size); |
| if (cp == NULL) { |
| btd_error(adapter->dev_id, |
| "Failed to allocate memory for connection parameters"); |
| return; |
| } |
| |
| cp->param_count = htobs(param_count); |
| |
| for (l = params, param = cp->params; l; l = g_slist_next(l), param++) { |
| struct conn_param *info = l->data; |
| |
| bacpy(¶m->addr.bdaddr, &info->bdaddr); |
| param->addr.type = info->bdaddr_type; |
| param->min_interval = htobs(info->min_interval); |
| param->max_interval = htobs(info->max_interval); |
| param->latency = htobs(info->latency); |
| param->timeout = htobs(info->timeout); |
| } |
| |
| id = mgmt_send(adapter->mgmt, MGMT_OP_LOAD_CONN_PARAM, adapter->dev_id, |
| cp_size, cp, load_conn_params_complete, adapter, NULL); |
| |
| g_free(cp); |
| |
| if (id == 0) |
| btd_error(adapter->dev_id, "Load connection parameters failed"); |
| } |
| |
| 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 probe_devices(void *user_data) |
| { |
| struct btd_device *device = user_data; |
| |
| device_probe_profiles(device, btd_device_get_uuids(device)); |
| } |
| |
| static void load_devices(struct btd_adapter *adapter) |
| { |
| char dirname[PATH_MAX]; |
| GSList *keys = NULL; |
| GSList *ltks = NULL; |
| GSList *irks = NULL; |
| GSList *params = NULL; |
| GSList *added_devices = NULL; |
| DIR *dir; |
| struct dirent *entry; |
| |
| snprintf(dirname, PATH_MAX, STORAGEDIR "/%s", |
| btd_adapter_get_storage_dir(adapter)); |
| |
| dir = opendir(dirname); |
| if (!dir) { |
| btd_error(adapter->dev_id, |
| "Unable to open adapter storage directory: %s", |
| dirname); |
| return; |
| } |
| |
| while ((entry = readdir(dir)) != NULL) { |
| struct btd_device *device; |
| char filename[PATH_MAX]; |
| GKeyFile *key_file; |
| struct link_key_info *key_info; |
| GSList *list, *ltk_info; |
| struct irk_info *irk_info; |
| struct conn_param *param; |
| uint8_t bdaddr_type; |
| |
| if (entry->d_type == DT_UNKNOWN) |
| entry->d_type = util_get_dt(dirname, entry->d_name); |
| |
| if (entry->d_type != DT_DIR || bachk(entry->d_name) < 0) |
| continue; |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", |
| btd_adapter_get_storage_dir(adapter), |
| 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); |
| |
| param = get_conn_param(key_file, entry->d_name, bdaddr_type); |
| if (param) |
| params = g_slist_append(params, param); |
| |
| 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 */ |
| |
| added_devices = g_slist_append(added_devices, device); |
| |
| 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); |
| load_conn_params(adapter, params); |
| g_slist_free_full(params, g_free); |
| |
| g_slist_free_full(added_devices, probe_devices); |
| } |
| |
| 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) { |
| btd_error(adapter->dev_id, "%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) { |
| btd_error(adapter->dev_id, "%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)) { |
| btd_error(adapter->dev_id, |
| "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) { |
| btd_error(adapter->dev_id, |
| "Failed to get connections: %s (0x%02x)", |
| mgmt_errstr(status), status); |
| return; |
| } |
| |
| if (length < sizeof(*rp)) { |
| btd_error(adapter->dev_id, |
| "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) { |
| btd_error(adapter->dev_id, |
| "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; |
| |
| btd_error(adapter->dev_id, "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_BONDABLE) |
| 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; |
| } |
| |
| bool btd_adapter_get_discoverable(struct btd_adapter *adapter) |
| { |
| if (adapter->current_settings & MGMT_SETTING_DISCOVERABLE) |
| return true; |
| |
| return false; |
| } |
| |
| bool btd_adapter_get_bredr(struct btd_adapter *adapter) |
| { |
| if (adapter->current_settings & MGMT_SETTING_BREDR) |
| return true; |
| |
| return false; |
| } |
| |
| struct btd_gatt_database *btd_adapter_get_database(struct btd_adapter *adapter) |
| { |
| if (!adapter) |
| return NULL; |
| |
| return adapter->database; |
| } |
| |
| 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 kernel background scanning is supported then the |
| * adapter_auto_connect_add() function is used to maintain what to |
| * connect. |
| */ |
| if (kernel_conn_control) |
| return 0; |
| |
| if (g_slist_find(adapter->connect_list, device)) { |
| DBG("ignoring already added device %s", |
| device_get_path(device)); |
| goto done; |
| } |
| |
| if (!(adapter->supported_settings & MGMT_SETTING_LE)) { |
| btd_error(adapter->dev_id, |
| "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); |
| |
| done: |
| 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 (kernel_conn_control) |
| return; |
| |
| 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 add_whitelist_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_rp_add_device *rp = param; |
| struct btd_adapter *adapter = user_data; |
| struct btd_device *dev; |
| char addr[18]; |
| |
| if (length < sizeof(*rp)) { |
| btd_error(adapter->dev_id, |
| "Too small Add Device complete event"); |
| return; |
| } |
| |
| ba2str(&rp->addr.bdaddr, addr); |
| |
| dev = btd_adapter_find_device(adapter, &rp->addr.bdaddr, |
| rp->addr.type); |
| if (!dev) { |
| btd_error(adapter->dev_id, |
| "Add Device complete for unknown device %s", addr); |
| return; |
| } |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| btd_error(adapter->dev_id, |
| "Failed to add device %s: %s (0x%02x)", |
| addr, mgmt_errstr(status), status); |
| return; |
| } |
| |
| DBG("%s added to kernel whitelist", addr); |
| } |
| |
| void adapter_whitelist_add(struct btd_adapter *adapter, struct btd_device *dev) |
| { |
| struct mgmt_cp_add_device cp; |
| |
| if (!kernel_conn_control) |
| return; |
| |
| memset(&cp, 0, sizeof(cp)); |
| bacpy(&cp.addr.bdaddr, device_get_address(dev)); |
| cp.addr.type = BDADDR_BREDR; |
| cp.action = 0x01; |
| |
| mgmt_send(adapter->mgmt, MGMT_OP_ADD_DEVICE, |
| adapter->dev_id, sizeof(cp), &cp, |
| add_whitelist_complete, adapter, NULL); |
| } |
| |
| static void remove_whitelist_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_rp_remove_device *rp = param; |
| char addr[18]; |
| |
| if (length < sizeof(*rp)) { |
| error("Too small Remove Device complete event"); |
| return; |
| } |
| |
| ba2str(&rp->addr.bdaddr, addr); |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| error("Failed to remove device %s: %s (0x%02x)", |
| addr, mgmt_errstr(status), status); |
| return; |
| } |
| |
| DBG("%s removed from kernel whitelist", addr); |
| } |
| |
| void adapter_whitelist_remove(struct btd_adapter *adapter, struct btd_device *dev) |
| { |
| struct mgmt_cp_remove_device cp; |
| |
| if (!kernel_conn_control) |
| return; |
| |
| memset(&cp, 0, sizeof(cp)); |
| bacpy(&cp.addr.bdaddr, device_get_address(dev)); |
| cp.addr.type = BDADDR_BREDR; |
| |
| mgmt_send(adapter->mgmt, MGMT_OP_REMOVE_DEVICE, |
| adapter->dev_id, sizeof(cp), &cp, |
| remove_whitelist_complete, adapter, NULL); |
| } |
| |
| static void add_device_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_rp_add_device *rp = param; |
| struct btd_adapter *adapter = user_data; |
| struct btd_device *dev; |
| char addr[18]; |
| |
| if (length < sizeof(*rp)) { |
| btd_error(adapter->dev_id, |
| "Too small Add Device complete event"); |
| return; |
| } |
| |
| ba2str(&rp->addr.bdaddr, addr); |
| |
| dev = btd_adapter_find_device(adapter, &rp->addr.bdaddr, |
| rp->addr.type); |
| if (!dev) { |
| btd_error(adapter->dev_id, |
| "Add Device complete for unknown device %s", addr); |
| return; |
| } |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| btd_error(adapter->dev_id, |
| "Failed to add device %s (%u): %s (0x%02x)", |
| addr, rp->addr.type, mgmt_errstr(status), status); |
| adapter->connect_list = g_slist_remove(adapter->connect_list, |
| dev); |
| return; |
| } |
| |
| DBG("%s (%u) added to kernel connect list", addr, rp->addr.type); |
| } |
| |
| void adapter_auto_connect_add(struct btd_adapter *adapter, |
| struct btd_device *device) |
| { |
| struct mgmt_cp_add_device cp; |
| const bdaddr_t *bdaddr; |
| uint8_t bdaddr_type; |
| unsigned int id; |
| |
| if (!kernel_conn_control) |
| return; |
| |
| if (g_slist_find(adapter->connect_list, device)) { |
| DBG("ignoring already added device %s", |
| device_get_path(device)); |
| return; |
| } |
| |
| bdaddr = device_get_address(device); |
| bdaddr_type = btd_device_get_bdaddr_type(device); |
| |
| if (bdaddr_type == BDADDR_BREDR) { |
| DBG("auto-connection feature is not avaiable for BR/EDR"); |
| return; |
| } |
| |
| memset(&cp, 0, sizeof(cp)); |
| bacpy(&cp.addr.bdaddr, bdaddr); |
| cp.addr.type = bdaddr_type; |
| cp.action = 0x02; |
| |
| id = mgmt_send(adapter->mgmt, MGMT_OP_ADD_DEVICE, |
| adapter->dev_id, sizeof(cp), &cp, add_device_complete, |
| adapter, NULL); |
| if (id == 0) |
| return; |
| |
| adapter->connect_list = g_slist_append(adapter->connect_list, device); |
| } |
| |
| static void remove_device_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_rp_remove_device *rp = param; |
| char addr[18]; |
| |
| if (length < sizeof(*rp)) { |
| error("Too small Remove Device complete event"); |
| return; |
| } |
| |
| ba2str(&rp->addr.bdaddr, addr); |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| error("Failed to remove device %s (%u): %s (0x%02x)", |
| addr, rp->addr.type, mgmt_errstr(status), status); |
| return; |
| } |
| |
| DBG("%s (%u) removed from kernel connect list", addr, rp->addr.type); |
| } |
| |
| void adapter_auto_connect_remove(struct btd_adapter *adapter, |
| struct btd_device *device) |
| { |
| struct mgmt_cp_remove_device cp; |
| const bdaddr_t *bdaddr; |
| uint8_t bdaddr_type; |
| unsigned int id; |
| |
| if (!kernel_conn_control) |
| return; |
| |
| if (!g_slist_find(adapter->connect_list, device)) { |
| DBG("ignoring not added device %s", device_get_path(device)); |
| return; |
| } |
| |
| bdaddr = device_get_address(device); |
| bdaddr_type = btd_device_get_bdaddr_type(device); |
| |
| if (bdaddr_type == BDADDR_BREDR) { |
| DBG("auto-connection feature is not avaiable for BR/EDR"); |
| return; |
| } |
| |
| memset(&cp, 0, sizeof(cp)); |
| bacpy(&cp.addr.bdaddr, bdaddr); |
| cp.addr.type = bdaddr_type; |
| |
| id = mgmt_send(adapter->mgmt, MGMT_OP_REMOVE_DEVICE, |
| adapter->dev_id, sizeof(cp), &cp, |
| remove_device_complete, adapter, NULL); |
| if (id == 0) |
| return; |
| |
| adapter->connect_list = g_slist_remove(adapter->connect_list, device); |
| } |
| |
| 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->pairable_timeout_id > 0) { |
| g_source_remove(adapter->pairable_timeout_id); |
| adapter->pairable_timeout_id = 0; |
| } |
| |
| if (adapter->passive_scan_timeout > 0) { |
| g_source_remove(adapter->passive_scan_timeout); |
| adapter->passive_scan_timeout = 0; |
| } |
| |
| 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->modalias); |
| g_free(adapter); |
| } |
| |
| struct btd_adapter *btd_adapter_ref(struct btd_adapter *adapter) |
| { |
| __sync_fetch_and_add(&adapter->ref_count, 1); |
| |
| return adapter; |
| } |
| |
| void btd_adapter_unref(struct btd_adapter *adapter) |
| { |
| if (__sync_sub_and_fetch(&adapter->ref_count, 1)) |
| return; |
| |
| if (!adapter->path) { |
| DBG("Freeing adapter %u", adapter->dev_id); |
| |
| adapter_free(adapter); |
| return; |
| } |
| |
| DBG("Freeing adapter %s", adapter->path); |
| |
| g_dbus_unregister_interface(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE); |
| } |
| |
| static void convert_names_entry(char *key, char *value, void *user_data) |
| { |
| char *address = user_data; |
| char *str = key; |
| char filename[PATH_MAX]; |
| GKeyFile *key_file; |
| char *data; |
| gsize length = 0; |
| |
| if (strchr(key, '#')) |
| str[17] = '\0'; |
| |
| if (bachk(str) != 0) |
| return; |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", address, str); |
| create_file(filename, S_IRUSR | S_IWUSR); |
| |
| key_file = g_key_file_new(); |
| g_key_file_load_from_file(key_file, filename, 0, NULL); |
| g_key_file_set_string(key_file, "General", "Name", value); |
| |
| data = g_key_file_to_data(key_file, &length, NULL); |
| g_file_set_contents(filename, data, length, NULL); |
| g_free(data); |
| |
| g_key_file_free(key_file); |
| } |
| |
| struct device_converter { |
| char *address; |
| void (*cb)(GKeyFile *key_file, void *value); |
| gboolean force; |
| }; |
| |
| static void set_device_type(GKeyFile *key_file, char type) |
| { |
| char *techno; |
| char *addr_type = NULL; |
| char *str; |
| |
| switch (type) { |
| case BDADDR_BREDR: |
| techno = "BR/EDR"; |
| break; |
| case BDADDR_LE_PUBLIC: |
| techno = "LE"; |
| addr_type = "public"; |
| break; |
| case BDADDR_LE_RANDOM: |
| techno = "LE"; |
| addr_type = "static"; |
| break; |
| default: |
| return; |
| } |
| |
| str = g_key_file_get_string(key_file, "General", |
| "SupportedTechnologies", NULL); |
| if (!str) |
| g_key_file_set_string(key_file, "General", |
| "SupportedTechnologies", techno); |
| else if (!strstr(str, techno)) |
| g_key_file_set_string(key_file, "General", |
| "SupportedTechnologies", "BR/EDR;LE"); |
| |
| g_free(str); |
| |
| if (addr_type) |
| g_key_file_set_string(key_file, "General", "AddressType", |
| addr_type); |
| } |
| |
| static void convert_aliases_entry(GKeyFile *key_file, void *value) |
| { |
| g_key_file_set_string(key_file, "General", "Alias", value); |
| } |
| |
| static void convert_trusts_entry(GKeyFile *key_file, void *value) |
| { |
| g_key_file_set_boolean(key_file, "General", "Trusted", TRUE); |
| } |
| |
| static void convert_classes_entry(GKeyFile *key_file, void *value) |
| { |
| g_key_file_set_string(key_file, "General", "Class", value); |
| } |
| |
| static void convert_blocked_entry(GKeyFile *key_file, void *value) |
| { |
| g_key_file_set_boolean(key_file, "General", "Blocked", TRUE); |
| } |
| |
| static void convert_did_entry(GKeyFile *key_file, void *value) |
| { |
| char *vendor_str, *product_str, *version_str; |
| uint16_t val; |
| |
| vendor_str = strchr(value, ' '); |
| if (!vendor_str) |
| return; |
| |
| *(vendor_str++) = 0; |
| |
| if (g_str_equal(value, "FFFF")) |
| return; |
| |
| product_str = strchr(vendor_str, ' '); |
| if (!product_str) |
| return; |
| |
| *(product_str++) = 0; |
| |
| version_str = strchr(product_str, ' '); |
| if (!version_str) |
| return; |
| |
| *(version_str++) = 0; |
| |
| val = (uint16_t) strtol(value, NULL, 16); |
| g_key_file_set_integer(key_file, "DeviceID", "Source", val); |
| |
| val = (uint16_t) strtol(vendor_str, NULL, 16); |
| g_key_file_set_integer(key_file, "DeviceID", "Vendor", val); |
| |
| val = (uint16_t) strtol(product_str, NULL, 16); |
| g_key_file_set_integer(key_file, "DeviceID", "Product", val); |
| |
| val = (uint16_t) strtol(version_str, NULL, 16); |
| g_key_file_set_integer(key_file, "DeviceID", "Version", val); |
| } |
| |
| static void convert_linkkey_entry(GKeyFile *key_file, void *value) |
| { |
| char *type_str, *length_str, *str; |
| int val; |
| |
| type_str = strchr(value, ' '); |
| if (!type_str) |
| return; |
| |
| *(type_str++) = 0; |
| |
| length_str = strchr(type_str, ' '); |
| if (!length_str) |
| return; |
| |
| *(length_str++) = 0; |
| |
| str = g_strconcat("0x", value, NULL); |
| g_key_file_set_string(key_file, "LinkKey", "Key", str); |
| g_free(str); |
| |
| val = strtol(type_str, NULL, 16); |
| g_key_file_set_integer(key_file, "LinkKey", "Type", val); |
| |
| val = strtol(length_str, NULL, 16); |
| g_key_file_set_integer(key_file, "LinkKey", "PINLength", val); |
| } |
| |
| static void convert_ltk_entry(GKeyFile *key_file, void *value) |
| { |
| char *auth_str, *rand_str, *str; |
| int i, ret; |
| unsigned char auth, master, enc_size; |
| unsigned short ediv; |
| |
| auth_str = strchr(value, ' '); |
| if (!auth_str) |
| return; |
| |
| *(auth_str++) = 0; |
| |
| for (i = 0, rand_str = auth_str; i < 4; i++) { |
| rand_str = strchr(rand_str, ' '); |
| if (!rand_str || rand_str[1] == '\0') |
| return; |
| |
| rand_str++; |
| } |
| |
| ret = sscanf(auth_str, " %hhd %hhd %hhd %hd", &auth, &master, |
| &enc_size, &ediv); |
| if (ret < 4) |
| return; |
| |
| str = g_strconcat("0x", value, NULL); |
| g_key_file_set_string(key_file, "LongTermKey", "Key", str); |
| g_free(str); |
| |
| g_key_file_set_integer(key_file, "LongTermKey", "Authenticated", auth); |
| g_key_file_set_integer(key_file, "LongTermKey", "Master", master); |
| g_key_file_set_integer(key_file, "LongTermKey", "EncSize", enc_size); |
| g_key_file_set_integer(key_file, "LongTermKey", "EDiv", ediv); |
| |
| str = g_strconcat("0x", rand_str, NULL); |
| g_key_file_set_string(key_file, "LongTermKey", "Rand", str); |
| g_free(str); |
| } |
| |
| static void convert_profiles_entry(GKeyFile *key_file, void *value) |
| { |
| g_strdelimit(value, " ", ';'); |
| g_key_file_set_string(key_file, "General", "Services", value); |
| } |
| |
| static void convert_appearances_entry(GKeyFile *key_file, void *value) |
| { |
| g_key_file_set_string(key_file, "General", "Appearance", value); |
| } |
| |
| static void convert_entry(char *key, char *value, void *user_data) |
| { |
| struct device_converter *converter = user_data; |
| char type = BDADDR_BREDR; |
| char filename[PATH_MAX]; |
| GKeyFile *key_file; |
| char *data; |
| gsize length = 0; |
| |
| if (strchr(key, '#')) { |
| key[17] = '\0'; |
| type = key[18] - '0'; |
| } |
| |
| if (bachk(key) != 0) |
| return; |
| |
| if (converter->force == FALSE) { |
| struct stat st; |
| int err; |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s", |
| converter->address, key); |
| |
| err = stat(filename, &st); |
| if (err || !S_ISDIR(st.st_mode)) |
| return; |
| } |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", |
| converter->address, key); |
| |
| key_file = g_key_file_new(); |
| g_key_file_load_from_file(key_file, filename, 0, NULL); |
| |
| set_device_type(key_file, type); |
| |
| converter->cb(key_file, value); |
| |
| data = g_key_file_to_data(key_file, &length, NULL); |
| if (length > 0) { |
| create_file(filename, S_IRUSR | S_IWUSR); |
| g_file_set_contents(filename, data, length, NULL); |
| } |
| |
| g_free(data); |
| |
| g_key_file_free(key_file); |
| } |
| |
| static void convert_file(char *file, char *address, |
| void (*cb)(GKeyFile *key_file, void *value), |
| gboolean force) |
| { |
| char filename[PATH_MAX]; |
| struct device_converter converter; |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s", address, file); |
| |
| converter.address = address; |
| converter.cb = cb; |
| converter.force = force; |
| |
| textfile_foreach(filename, convert_entry, &converter); |
| } |
| |
| 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); |
| |
| free(uuid); |
| |
| if (ret == 0) |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| static void store_attribute_uuid(GKeyFile *key_file, uint16_t start, |
| uint16_t end, char *att_uuid, |
| uuid_t uuid) |
| { |
| char handle[6], uuid_str[33]; |
| int i; |
| |
| switch (uuid.type) { |
| case SDP_UUID16: |
| sprintf(uuid_str, "%4.4X", uuid.value.uuid16); |
| break; |
| case SDP_UUID32: |
| sprintf(uuid_str, "%8.8X", uuid.value.uuid32); |
| break; |
| case SDP_UUID128: |
| for (i = 0; i < 16; i++) |
| sprintf(uuid_str + (i * 2), "%2.2X", |
| uuid.value.uuid128.data[i]); |
| break; |
| default: |
| uuid_str[0] = '\0'; |
| } |
| |
| sprintf(handle, "%hu", start); |
| g_key_file_set_string(key_file, handle, "UUID", att_uuid); |
| g_key_file_set_string(key_file, handle, "Value", uuid_str); |
| g_key_file_set_integer(key_file, handle, "EndGroupHandle", end); |
| } |
| |
| static void store_sdp_record(char *local, char *peer, int handle, char *value) |
| { |
| char filename[PATH_MAX]; |
| GKeyFile *key_file; |
| char handle_str[11]; |
| char *data; |
| gsize length = 0; |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", local, peer); |
| |
| key_file = g_key_file_new(); |
| g_key_file_load_from_file(key_file, filename, 0, NULL); |
| |
| sprintf(handle_str, "0x%8.8X", handle); |
| g_key_file_set_string(key_file, "ServiceRecords", handle_str, value); |
| |
| data = g_key_file_to_data(key_file, &length, NULL); |
| if (length > 0) { |
| create_file(filename, S_IRUSR | S_IWUSR); |
| g_file_set_contents(filename, data, length, NULL); |
| } |
| |
| g_free(data); |
| |
| g_key_file_free(key_file); |
| } |
| |
| static void convert_sdp_entry(char *key, char *value, void *user_data) |
| { |
| char *src_addr = user_data; |
| char dst_addr[18]; |
| char type = BDADDR_BREDR; |
| int handle, ret; |
| char filename[PATH_MAX]; |
| GKeyFile *key_file; |
| struct stat st; |
| sdp_record_t *rec; |
| uuid_t uuid; |
| char *att_uuid, *prim_uuid; |
| uint16_t start = 0, end = 0, psm = 0; |
| int err; |
| char *data; |
| gsize length = 0; |
| |
| ret = sscanf(key, "%17s#%hhu#%08X", dst_addr, &type, &handle); |
| if (ret < 3) { |
| ret = sscanf(key, "%17s#%08X", dst_addr, &handle); |
| if (ret < 2) |
| return; |
| } |
| |
| if (bachk(dst_addr) != 0) |
| return; |
| |
| /* Check if the device directory has been created as records should |
| * only be converted for known devices */ |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s", src_addr, dst_addr); |
| |
| err = stat(filename, &st); |
| if (err || !S_ISDIR(st.st_mode)) |
| return; |
| |
| /* store device records in cache */ |
| store_sdp_record(src_addr, dst_addr, handle, value); |
| |
| /* Retrieve device record and check if there is an |
| * attribute entry in it */ |
| sdp_uuid16_create(&uuid, ATT_UUID); |
| att_uuid = bt_uuid2string(&uuid); |
| |
| sdp_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); |
| prim_uuid = bt_uuid2string(&uuid); |
| |
| rec = record_from_string(value); |
| |
| if (record_has_uuid(rec, att_uuid)) |
| goto failed; |
| |
| /* TODO: Do this through btd_gatt_database */ |
| if (!gatt_parse_record(rec, &uuid, &psm, &start, &end)) |
| goto failed; |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/attributes", src_addr, |
| dst_addr); |
| |
| key_file = g_key_file_new(); |
| g_key_file_load_from_file(key_file, filename, 0, NULL); |
| |
| store_attribute_uuid(key_file, start, end, prim_uuid, uuid); |
| |
| data = g_key_file_to_data(key_file, &length, NULL); |
| if (length > 0) { |
| create_file(filename, S_IRUSR | S_IWUSR); |
| g_file_set_contents(filename, data, length, NULL); |
| } |
| |
| g_free(data); |
| g_key_file_free(key_file); |
| |
| failed: |
| sdp_record_free(rec); |
| free(prim_uuid); |
| free(att_uuid); |
| } |
| |
| static void convert_primaries_entry(char *key, char *value, void *user_data) |
| { |
| char *address = user_data; |
| int device_type = -1; |
| uuid_t uuid; |
| char **services, **service, *prim_uuid; |
| char filename[PATH_MAX]; |
| GKeyFile *key_file; |
| int ret; |
| uint16_t start, end; |
| char uuid_str[MAX_LEN_UUID_STR + 1]; |
| char *data; |
| gsize length = 0; |
| |
| if (strchr(key, '#')) { |
| key[17] = '\0'; |
| device_type = key[18] - '0'; |
| } |
| |
| if (bachk(key) != 0) |
| return; |
| |
| services = g_strsplit(value, " ", 0); |
| if (services == NULL) |
| return; |
| |
| sdp_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); |
| prim_uuid = bt_uuid2string(&uuid); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/attributes", address, |
| key); |
| key_file = g_key_file_new(); |
| g_key_file_load_from_file(key_file, filename, 0, NULL); |
| |
| for (service = services; *service; service++) { |
| ret = sscanf(*service, "%04hX#%04hX#%s", &start, &end, |
| uuid_str); |
| if (ret < 3) |
| continue; |
| |
| bt_string2uuid(&uuid, uuid_str); |
| sdp_uuid128_to_uuid(&uuid); |
| |
| store_attribute_uuid(key_file, start, end, prim_uuid, uuid); |
| } |
| |
| g_strfreev(services); |
| |
| data = g_key_file_to_data(key_file, &length, NULL); |
| if (length == 0) |
| goto end; |
| |
| create_file(filename, S_IRUSR | S_IWUSR); |
| g_file_set_contents(filename, data, length, NULL); |
| |
| if (device_type < 0) |
| goto end; |
| |
| g_free(data); |
| g_key_file_free(key_file); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", address, key); |
| |
| key_file = g_key_file_new(); |
| g_key_file_load_from_file(key_file, filename, 0, NULL); |
| set_device_type(key_file, device_type); |
| |
| data = g_key_file_to_data(key_file, &length, NULL); |
| if (length > 0) { |
| create_file(filename, S_IRUSR | S_IWUSR); |
| g_file_set_contents(filename, data, length, NULL); |
| } |
| |
| end: |
| g_free(data); |
| free(prim_uuid); |
| g_key_file_free(key_file); |
| } |
| |
| static void convert_ccc_entry(char *key, char *value, void *user_data) |
| { |
| char *src_addr = user_data; |
| char dst_addr[18]; |
| char type = BDADDR_BREDR; |
| uint16_t handle; |
| int ret, err; |
| char filename[PATH_MAX]; |
| GKeyFile *key_file; |
| struct stat st; |
| char group[6]; |
| char *data; |
| gsize length = 0; |
| |
| ret = sscanf(key, "%17s#%hhu#%04hX", dst_addr, &type, &handle); |
| if (ret < 3) |
| return; |
| |
| if (bachk(dst_addr) != 0) |
| return; |
| |
| /* Check if the device directory has been created as records should |
| * only be converted for known devices */ |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s", src_addr, dst_addr); |
| |
| err = stat(filename, &st); |
| if (err || !S_ISDIR(st.st_mode)) |
| return; |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/ccc", src_addr, |
| dst_addr); |
| key_file = g_key_file_new(); |
| g_key_file_load_from_file(key_file, filename, 0, NULL); |
| |
| sprintf(group, "%hu", handle); |
| g_key_file_set_string(key_file, group, "Value", value); |
| |
| data = g_key_file_to_data(key_file, &length, NULL); |
| if (length > 0) { |
| create_file(filename, S_IRUSR | S_IWUSR); |
| g_file_set_contents(filename, data, length, NULL); |
| } |
| |
| g_free(data); |
| g_key_file_free(key_file); |
| } |
| |
| static void convert_gatt_entry(char *key, char *value, void *user_data) |
| { |
| char *src_addr = user_data; |
| char dst_addr[18]; |
| char type = BDADDR_BREDR; |
| uint16_t handle; |
| int ret, err; |
| char filename[PATH_MAX]; |
| GKeyFile *key_file; |
| struct stat st; |
| char group[6]; |
| char *data; |
| gsize length = 0; |
| |
| ret = sscanf(key, "%17s#%hhu#%04hX", dst_addr, &type, &handle); |
| if (ret < 3) |
| return; |
| |
| if (bachk(dst_addr) != 0) |
| return; |
| |
| /* Check if the device directory has been created as records should |
| * only be converted for known devices */ |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s", src_addr, dst_addr); |
| |
| err = stat(filename, &st); |
| if (err || !S_ISDIR(st.st_mode)) |
| return; |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/gatt", src_addr, |
| dst_addr); |
| key_file = g_key_file_new(); |
| g_key_file_load_from_file(key_file, filename, 0, NULL); |
| |
| sprintf(group, "%hu", handle); |
| g_key_file_set_string(key_file, group, "Value", value); |
| |
| data = g_key_file_to_data(key_file, &length, NULL); |
| if (length > 0) { |
| create_file(filename, S_IRUSR | S_IWUSR); |
| g_file_set_contents(filename, data, length, NULL); |
| } |
| |
| g_free(data); |
| g_key_file_free(key_file); |
| } |
| |
| static void convert_proximity_entry(char *key, char *value, void *user_data) |
| { |
| char *src_addr = user_data; |
| char *alert; |
| char filename[PATH_MAX]; |
| GKeyFile *key_file; |
| struct stat st; |
| int err; |
| char *data; |
| gsize length = 0; |
| |
| if (!strchr(key, '#')) |
| return; |
| |
| key[17] = '\0'; |
| alert = &key[18]; |
| |
| if (bachk(key) != 0) |
| return; |
| |
| /* Check if the device directory has been created as records should |
| * only be converted for known devices */ |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s", src_addr, key); |
| |
| err = stat(filename, &st); |
| if (err || !S_ISDIR(st.st_mode)) |
| return; |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/proximity", src_addr, |
| key); |
| key_file = g_key_file_new(); |
| g_key_file_load_from_file(key_file, filename, 0, NULL); |
| |
| g_key_file_set_string(key_file, alert, "Level", value); |
| |
| data = g_key_file_to_data(key_file, &length, NULL); |
| if (length > 0) { |
| create_file(filename, S_IRUSR | S_IWUSR); |
| g_file_set_contents(filename, data, length, NULL); |
| } |
| |
| g_free(data); |
| g_key_file_free(key_file); |
| } |
| |
| static void convert_device_storage(struct btd_adapter *adapter) |
| { |
| char filename[PATH_MAX]; |
| char address[18]; |
| |
| ba2str(&adapter->bdaddr, address); |
| |
| /* Convert device's name cache */ |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/names", address); |
| textfile_foreach(filename, convert_names_entry, address); |
| |
| /* Convert aliases */ |
| convert_file("aliases", address, convert_aliases_entry, TRUE); |
| |
| /* Convert trusts */ |
| convert_file("trusts", address, convert_trusts_entry, TRUE); |
| |
| /* Convert blocked */ |
| convert_file("blocked", address, convert_blocked_entry, TRUE); |
| |
| /* Convert profiles */ |
| convert_file("profiles", address, convert_profiles_entry, TRUE); |
| |
| /* Convert primaries */ |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/primaries", address); |
| textfile_foreach(filename, convert_primaries_entry, address); |
| |
| /* Convert linkkeys */ |
| convert_file("linkkeys", address, convert_linkkey_entry, TRUE); |
| |
| /* Convert longtermkeys */ |
| convert_file("longtermkeys", address, convert_ltk_entry, TRUE); |
| |
| /* Convert classes */ |
| convert_file("classes", address, convert_classes_entry, FALSE); |
| |
| /* Convert device ids */ |
| convert_file("did", address, convert_did_entry, FALSE); |
| |
| /* Convert sdp */ |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/sdp", address); |
| textfile_foreach(filename, convert_sdp_entry, address); |
| |
| /* Convert ccc */ |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/ccc", address); |
| textfile_foreach(filename, convert_ccc_entry, address); |
| |
| /* Convert appearances */ |
| convert_file("appearances", address, convert_appearances_entry, FALSE); |
| |
| /* Convert gatt */ |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/gatt", address); |
| textfile_foreach(filename, convert_gatt_entry, address); |
| |
| /* Convert proximity */ |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/proximity", address); |
| textfile_foreach(filename, convert_proximity_entry, address); |
| } |
| |
| static void convert_config(struct btd_adapter *adapter, const char *filename, |
| GKeyFile *key_file) |
| { |
| char address[18]; |
| char str[MAX_NAME_LENGTH + 1]; |
| char config_path[PATH_MAX]; |
| int timeout; |
| uint8_t mode; |
| char *data; |
| gsize length = 0; |
| |
| ba2str(&adapter->bdaddr, address); |
| snprintf(config_path, PATH_MAX, STORAGEDIR "/%s/config", address); |
| |
| if (read_pairable_timeout(address, &timeout) == 0) |
| g_key_file_set_integer(key_file, "General", |
| "PairableTimeout", timeout); |
| |
| if (read_discoverable_timeout(address, &timeout) == 0) |
| g_key_file_set_integer(key_file, "General", |
| "DiscoverableTimeout", timeout); |
| |
| if (read_on_mode(address, str, sizeof(str)) == 0) { |
| mode = get_mode(str); |
| g_key_file_set_boolean(key_file, "General", "Discoverable", |
| mode == MODE_DISCOVERABLE); |
| } |
| |
| if (read_local_name(&adapter->bdaddr, str) == 0) |
| g_key_file_set_string(key_file, "General", "Alias", str); |
| |
| create_file(filename, S_IRUSR | S_IWUSR); |
| |
| data = g_key_file_to_data(key_file, &length, NULL); |
| g_file_set_contents(filename, data, length, NULL); |
| g_free(data); |
| } |
| |
| static void fix_storage(struct btd_adapter *adapter) |
| { |
| char filename[PATH_MAX]; |
| char address[18]; |
| char *converted; |
| |
| ba2str(&adapter->bdaddr, address); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/config", address); |
| converted = textfile_get(filename, "converted"); |
| if (!converted) |
| return; |
| |
| free(converted); |
| |
| textfile_del(filename, "converted"); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/names", address); |
| textfile_del(filename, "converted"); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/aliases", address); |
| textfile_del(filename, "converted"); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/trusts", address); |
| textfile_del(filename, "converted"); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/blocked", address); |
| textfile_del(filename, "converted"); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/profiles", address); |
| textfile_del(filename, "converted"); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/primaries", address); |
| textfile_del(filename, "converted"); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/linkkeys", address); |
| textfile_del(filename, "converted"); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/longtermkeys", address); |
| textfile_del(filename, "converted"); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/classes", address); |
| textfile_del(filename, "converted"); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/did", address); |
| textfile_del(filename, "converted"); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/sdp", address); |
| textfile_del(filename, "converted"); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/ccc", address); |
| textfile_del(filename, "converted"); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/appearances", address); |
| textfile_del(filename, "converted"); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/gatt", address); |
| textfile_del(filename, "converted"); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/proximity", address); |
| textfile_del(filename, "converted"); |
| } |
| |
| static void load_config(struct btd_adapter *adapter) |
| { |
| GKeyFile *key_file; |
| char filename[PATH_MAX]; |
| struct stat st; |
| GError *gerr = NULL; |
| |
| key_file = g_key_file_new(); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/settings", |
| btd_adapter_get_storage_dir(adapter)); |
| |
| if (stat(filename, &st) < 0) { |
| convert_config(adapter, filename, key_file); |
| convert_device_storage(adapter); |
| } |
| |
| g_key_file_load_from_file(key_file, filename, 0, NULL); |
| |
| /* Get alias */ |
| adapter->stored_alias = g_key_file_get_string(key_file, "General", |
| "Alias", NULL); |
| if (!adapter->stored_alias) { |
| /* fallback */ |
| adapter->stored_alias = g_key_file_get_string(key_file, |
| "General", "Name", NULL); |
| } |
| |
| /* Get pairable timeout */ |
| adapter->pairable_timeout = g_key_file_get_integer(key_file, "General", |
| "PairableTimeout", &gerr); |
| if (gerr) { |
| adapter->pairable_timeout = main_opts.pairto; |
| g_error_free(gerr); |
| gerr = NULL; |
| } |
| |
| /* Get discoverable mode */ |
| adapter->stored_discoverable = g_key_file_get_boolean(key_file, |
| "General", "Discoverable", &gerr); |
| if (gerr) { |
| adapter->stored_discoverable = false; |
| g_error_free(gerr); |
| gerr = NULL; |
| } |
| |
| /* Get discoverable timeout */ |
| adapter->discoverable_timeout = g_key_file_get_integer(key_file, |
| "General", "DiscoverableTimeout", &gerr); |
| if (gerr) { |
| adapter->discoverable_timeout = main_opts.discovto; |
| g_error_free(gerr); |
| gerr = NULL; |
| } |
| |
| g_key_file_free(key_file); |
| } |
| |
| static struct btd_adapter *btd_adapter_new(uint16_t index) |
| { |
| struct btd_adapter *adapter; |
| |
| adapter = g_try_new0(struct btd_adapter, 1); |
| if (!adapter) |
| return NULL; |
| |
| adapter->dev_id = index; |
| adapter->mgmt = mgmt_ref(mgmt_master); |
| adapter->pincode_requested = false; |
| |
| /* |
| * Setup default configuration values. These are either adapter |
| * defaults or from a system wide configuration file. |
| * |
| * Some value might be overwritten later on by adapter specific |
| * configuration. This is to make sure that sane defaults are |
| * always present. |
| */ |
| adapter->system_name = g_strdup(main_opts.name); |
| adapter->major_class = (main_opts.class & 0x001f00) >> 8; |
| adapter->minor_class = (main_opts.class & 0x0000fc) >> 2; |
| adapter->modalias = bt_modalias(main_opts.did_source, |
| main_opts.did_vendor, |
| main_opts.did_product, |
| main_opts.did_version); |
| adapter->discoverable_timeout = main_opts.discovto; |
| adapter->pairable_timeout = main_opts.pairto; |
| |
| DBG("System name: %s", adapter->system_name); |
| DBG("Major class: %u", adapter->major_class); |
| DBG("Minor class: %u", adapter->minor_class); |
| DBG("Modalias: %s", adapter->modalias); |
| DBG("Discoverable timeout: %u seconds", adapter->discoverable_timeout); |
| DBG("Pairable timeout: %u seconds", adapter->pairable_timeout); |
| |
| adapter->auths = g_queue_new(); |
| |
| return btd_adapter_ref(adapter); |
| } |
| |
| static void adapter_remove(struct btd_adapter *adapter) |
| { |
| GSList *l; |
| struct gatt_db *db; |
| |
| DBG("Removing adapter %s", adapter->path); |
| |
| g_slist_free(adapter->connect_list); |
| adapter->connect_list = NULL; |
| |
| for (l = adapter->devices; l; l = l->next) |
| device_remove(l->data, FALSE); |
| |
| g_slist_free(adapter->devices); |
| adapter->devices = NULL; |
| |
| discovery_cleanup(adapter); |
| |
| unload_drivers(adapter); |
| |
| db = btd_gatt_database_get_db(adapter->database); |
| gatt_db_unregister(db, adapter->db_id); |
| adapter->db_id = 0; |
| |
| btd_gatt_database_destroy(adapter->database); |
| adapter->database = NULL; |
| |
| btd_adv_manager_destroy(adapter->adv_manager); |
| adapter->adv_manager = NULL; |
| |
| g_slist_free(adapter->pin_callbacks); |
| adapter->pin_callbacks = NULL; |
| |
| g_slist_free(adapter->msd_callbacks); |
| adapter->msd_callbacks = NULL; |
| } |
| |
| const char *adapter_get_path(struct btd_adapter *adapter) |
| { |
| if (!adapter) |
| return NULL; |
| |
| return adapter->path; |
| } |
| |
| const bdaddr_t *btd_adapter_get_address(struct btd_adapter *adapter) |
| { |
| return &adapter->bdaddr; |
| } |
| |
| static gboolean confirm_name_timeout(gpointer user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| btd_error(adapter->dev_id, "Confirm name timed out for hci%u", |
| adapter->dev_id); |
| |
| adapter->confirm_name_timeout = 0; |
| |
| mgmt_cancel(adapter->mgmt, adapter->confirm_name_id); |
| adapter->confirm_name_id = 0; |
| |
| return FALSE; |
| } |
| |
| static void confirm_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) { |
| btd_error(adapter->dev_id, |
| "Failed to confirm name for hci%u: %s (0x%02x)", |
| adapter->dev_id, mgmt_errstr(status), status); |
| } |
| |
| adapter->confirm_name_id = 0; |
| |
| g_source_remove(adapter->confirm_name_timeout); |
| adapter->confirm_name_timeout = 0; |
| |
| DBG("Confirm name complete for hci%u", adapter->dev_id); |
| } |
| |
| static void confirm_name(struct btd_adapter *adapter, const bdaddr_t *bdaddr, |
| uint8_t bdaddr_type, bool name_known) |
| { |
| struct mgmt_cp_confirm_name cp; |
| char addr[18]; |
| |
| ba2str(bdaddr, addr); |
| DBG("hci%d bdaddr %s name_known %u", adapter->dev_id, addr, |
| name_known); |
| |
| /* |
| * If the kernel does not answer the confirm name command with |
| * a command complete or command status in time, this might |
| * race against another device found event that also requires |
| * to confirm the name. If there is a pending command, just |
| * cancel it to be safe here. |
| */ |
| if (adapter->confirm_name_id > 0) { |
| btd_warn(adapter->dev_id, |
| "Found pending confirm name for hci%u", |
| adapter->dev_id); |
| mgmt_cancel(adapter->mgmt, adapter->confirm_name_id); |
| } |
| |
| if (adapter->confirm_name_timeout > 0) { |
| g_source_remove(adapter->confirm_name_timeout); |
| adapter->confirm_name_timeout = 0; |
| } |
| |
| memset(&cp, 0, sizeof(cp)); |
| bacpy(&cp.addr.bdaddr, bdaddr); |
| cp.addr.type = bdaddr_type; |
| cp.name_known = name_known; |
| |
| adapter->confirm_name_id = mgmt_reply(adapter->mgmt, |
| MGMT_OP_CONFIRM_NAME, |
| adapter->dev_id, sizeof(cp), &cp, |
| confirm_name_complete, adapter, NULL); |
| |
| if (adapter->confirm_name_id == 0) { |
| btd_error(adapter->dev_id, "Failed to confirm name 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->confirm_name_timeout = g_timeout_add_seconds(2, |
| confirm_name_timeout, adapter); |
| } |
| |
| static void adapter_msd_notify(struct btd_adapter *adapter, |
| struct btd_device *dev, |
| GSList *msd_list) |
| { |
| GSList *cb_l, *cb_next; |
| GSList *msd_l, *msd_next; |
| |
| for (cb_l = adapter->msd_callbacks; cb_l != NULL; cb_l = cb_next) { |
| btd_msd_cb_t cb = cb_l->data; |
| |
| cb_next = g_slist_next(cb_l); |
| |
| for (msd_l = msd_list; msd_l != NULL; msd_l = msd_next) { |
| const struct eir_msd *msd = msd_l->data; |
| |
| msd_next = g_slist_next(msd_l); |
| |
| cb(adapter, dev, msd->company, msd->data, |
| msd->data_len); |
| } |
| } |
| } |
| |
| static bool is_filter_match(GSList *discovery_filter, struct eir_data *eir_data, |
| int8_t rssi) |
| { |
| GSList *l, *m; |
| bool got_match = false; |
| |
| for (l = discovery_filter; l != NULL && got_match != true; |
| l = g_slist_next(l)) { |
| struct watch_client *client = l->data; |
| struct discovery_filter *item = client->discovery_filter; |
| |
| /* |
| * If one of currently running scans is regular scan, then |
| * return all devices as matches |
| */ |
| if (!item) { |
| got_match = true; |
| continue; |
| } |
| |
| /* if someone started discovery with empty uuids, he wants all |
| * devices in given proximity. |
| */ |
| if (!item->uuids) |
| got_match = true; |
| else { |
| for (m = item->uuids; m != NULL && got_match != true; |
| m = g_slist_next(m)) { |
| /* m->data contains string representation of |
| * uuid. |
| */ |
| if (g_slist_find_custom(eir_data->services, |
| m->data, |
| g_strcmp) != NULL) |
| got_match = true; |
| } |
| } |
| |
| if (got_match) { |
| /* we have service match, check proximity */ |
| if (item->rssi == DISTANCE_VAL_INVALID || |
| item->rssi <= rssi || |
| item->pathloss == DISTANCE_VAL_INVALID || |
| (eir_data->tx_power != 127 && |
| eir_data->tx_power - rssi <= item->pathloss)) |
| return true; |
| |
| got_match = false; |
| } |
| } |
| |
| return got_match; |
| } |
| |
| static void filter_duplicate_data(void *data, void *user_data) |
| { |
| struct watch_client *client = data; |
| bool *duplicate = user_data; |
| |
| if (*duplicate || !client->discovery_filter) |
| return; |
| |
| *duplicate = client->discovery_filter->duplicate; |
| } |
| |
| static void update_found_devices(struct btd_adapter *adapter, |
| const bdaddr_t *bdaddr, |
| uint8_t bdaddr_type, int8_t rssi, |
| bool confirm, bool legacy, |
| bool not_connectable, |
| const uint8_t *data, uint8_t data_len) |
| { |
| struct btd_device *dev; |
| struct eir_data eir_data; |
| bool name_known, discoverable; |
| char addr[18]; |
| bool duplicate = false; |
| |
| memset(&eir_data, 0, sizeof(eir_data)); |
| eir_parse(&eir_data, data, data_len); |
| |
| if (bdaddr_type == BDADDR_BREDR || adapter->filtered_discovery) |
| discoverable = true; |
| else |
| discoverable = eir_data.flags & (EIR_LIM_DISC | EIR_GEN_DISC); |
| |
| ba2str(bdaddr, addr); |
| |
| dev = btd_adapter_find_device(adapter, bdaddr, bdaddr_type); |
| if (!dev) { |
| /* |
| * If no client has requested discovery or the device is |
| * not marked as discoverable, then do not create new |
| * device objects. |
| */ |
| if (!adapter->discovery_list || !discoverable) { |
| eir_data_free(&eir_data); |
| return; |
| } |
| |
| dev = adapter_create_device(adapter, bdaddr, bdaddr_type); |
| } |
| |
| if (!dev) { |
| btd_error(adapter->dev_id, |
| "Unable to create object for found device %s", addr); |
| eir_data_free(&eir_data); |
| return; |
| } |
| |
| device_update_last_seen(dev, bdaddr_type); |
| |
| /* |
| * FIXME: We need to check for non-zero flags first because |
| * older kernels send separate adv_ind and scan_rsp. Newer |
| * kernels send them merged, so once we know which mgmt version |
| * supports this we can make the non-zero check conditional. |
| */ |
| if (bdaddr_type != BDADDR_BREDR && eir_data.flags && |
| !(eir_data.flags & EIR_BREDR_UNSUP)) { |
| device_set_bredr_support(dev); |
| /* Update last seen for BR/EDR in case its flag is set */ |
| device_update_last_seen(dev, BDADDR_BREDR); |
| } |
| |
| if (eir_data.name != NULL && eir_data.name_complete) |
| device_store_cached_name(dev, eir_data.name); |
| |
| /* |
| * Only skip devices that are not connected, are temporary and there |
| * is no active discovery session ongoing. |
| */ |
| if (!btd_device_is_connected(dev) && (device_is_temporary(dev) && |
| !adapter->discovery_list)) { |
| eir_data_free(&eir_data); |
| return; |
| } |
| |
| if (adapter->filtered_discovery && |
| !is_filter_match(adapter->discovery_list, &eir_data, rssi)) { |
| eir_data_free(&eir_data); |
| return; |
| } |
| |
| device_set_legacy(dev, legacy); |
| |
| if (adapter->filtered_discovery) |
| device_set_rssi_with_delta(dev, rssi, 0); |
| else |
| device_set_rssi(dev, rssi); |
| |
| if (eir_data.tx_power != 127) |
| device_set_tx_power(dev, eir_data.tx_power); |
| |
| if (eir_data.appearance != 0) |
| device_set_appearance(dev, eir_data.appearance); |
| |
| /* Report an unknown name to the kernel even if there is a short name |
| * known, but still update the name with the known short name. */ |
| name_known = device_name_known(dev); |
| |
| if (eir_data.name && (eir_data.name_complete || !name_known)) |
| btd_device_device_set_name(dev, eir_data.name); |
| |
| if (eir_data.class != 0) |
| device_set_class(dev, eir_data.class); |
| |
| if (eir_data.did_source || eir_data.did_vendor || |
| eir_data.did_product || eir_data.did_version) |
| btd_device_set_pnpid(dev, eir_data.did_source, |
| eir_data.did_vendor, |
| eir_data.did_product, |
| eir_data.did_version); |
| |
| device_add_eir_uuids(dev, eir_data.services); |
| |
| if (adapter->discovery_list) |
| g_slist_foreach(adapter->discovery_list, filter_duplicate_data, |
| &duplicate); |
| |
| if (eir_data.msd_list) { |
| device_set_manufacturer_data(dev, eir_data.msd_list, duplicate); |
| adapter_msd_notify(adapter, dev, eir_data.msd_list); |
| } |
| |
| if (eir_data.sd_list) |
| device_set_service_data(dev, eir_data.sd_list, duplicate); |
| |
| if (bdaddr_type != BDADDR_BREDR) |
| device_set_flags(dev, eir_data.flags); |
| |
| eir_data_free(&eir_data); |
| |
| /* |
| * Only if at least one client has requested discovery, maintain |
| * list of found devices and name confirming for legacy devices. |
| * Otherwise, this is an event from passive discovery and we |
| * should check if the device needs connecting to. |
| */ |
| if (!adapter->discovery_list) |
| goto connect_le; |
| |
| if (g_slist_find(adapter->discovery_found, dev)) |
| return; |
| |
| if (confirm) |
| confirm_name(adapter, bdaddr, bdaddr_type, name_known); |
| |
| adapter->discovery_found = g_slist_prepend(adapter->discovery_found, |
| dev); |
| |
| return; |
| |
| connect_le: |
| /* Ignore non-connectable events */ |
| if (not_connectable) |
| return; |
| |
| /* |
| * If we're in the process of stopping passive scanning and |
| * connecting another (or maybe even the same) LE device just |
| * ignore this one. |
| */ |
| if (adapter->connect_le) |
| return; |
| |
| /* |
| * If kernel background scan is used then the kernel is |
| * responsible for connecting. |
| */ |
| if (kernel_conn_control) |
| return; |
| |
| /* |
| * If this is an LE device that's not connected and part of the |
| * connect_list stop passive scanning so that a connection |
| * attempt to it can be made |
| */ |
| if (bdaddr_type != BDADDR_BREDR && !btd_device_is_connected(dev) && |
| g_slist_find(adapter->connect_list, dev)) { |
| adapter->connect_le = dev; |
| stop_passive_scanning(adapter); |
| } |
| } |
| |
| static void device_found_callback(uint16_t index, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_ev_device_found *ev = param; |
| struct btd_adapter *adapter = user_data; |
| const uint8_t *eir; |
| uint16_t eir_len; |
| uint32_t flags; |
| bool confirm_name; |
| bool legacy; |
| char addr[18]; |
| |
| if (length < sizeof(*ev)) { |
| btd_error(adapter->dev_id, |
| "Too short device found event (%u bytes)", length); |
| return; |
| } |
| |
| eir_len = btohs(ev->eir_len); |
| if (length != sizeof(*ev) + eir_len) { |
| btd_error(adapter->dev_id, |
| "Device found event size mismatch (%u != %zu)", |
| length, sizeof(*ev) + eir_len); |
| return; |
| } |
| |
| if (eir_len == 0) |
| eir = NULL; |
| else |
| eir = ev->eir; |
| |
| flags = btohl(ev->flags); |
| |
| ba2str(&ev->addr.bdaddr, addr); |
| DBG("hci%u addr %s, rssi %d flags 0x%04x eir_len %u", |
| index, addr, ev->rssi, flags, eir_len); |
| |
| confirm_name = (flags & MGMT_DEV_FOUND_CONFIRM_NAME); |
| legacy = (flags & MGMT_DEV_FOUND_LEGACY_PAIRING); |
| |
| update_found_devices(adapter, &ev->addr.bdaddr, ev->addr.type, |
| ev->rssi, confirm_name, legacy, |
| flags & MGMT_DEV_FOUND_NOT_CONNECTABLE, |
| eir, eir_len); |
| } |
| |
| struct agent *adapter_get_agent(struct btd_adapter *adapter) |
| { |
| return agent_get(NULL); |
| } |
| |
| static void adapter_remove_connection(struct btd_adapter *adapter, |
| struct btd_device *device, |
| uint8_t bdaddr_type) |
| { |
| DBG(""); |
| |
| if (!g_slist_find(adapter->connections, device)) { |
| btd_error(adapter->dev_id, "No matching connection for device"); |
| return; |
| } |
| |
| device_remove_connection(device, bdaddr_type); |
| |
| if (device_is_authenticating(device)) |
| device_cancel_authentication(device, TRUE); |
| |
| /* If another bearer is still connected */ |
| if (btd_device_is_connected(device)) |
| return; |
| |
| adapter->connections = g_slist_remove(adapter->connections, device); |
| |
| if (device_is_temporary(device) && !device_is_retrying(device)) { |
| const char *path = device_get_path(device); |
| |
| DBG("Removing temporary device %s", path); |
| btd_adapter_remove_device(adapter, device); |
| } |
| } |
| |
| static void adapter_stop(struct btd_adapter *adapter) |
| { |
| /* check pending requests */ |
| reply_pending_requests(adapter); |
| |
| cancel_passive_scanning(adapter); |
| |
| g_slist_free_full(adapter->set_filter_list, discovery_free); |
| adapter->set_filter_list = NULL; |
| |
| g_slist_free_full(adapter->discovery_list, discovery_free); |
| adapter->discovery_list = NULL; |
| |
| discovery_cleanup(adapter); |
| |
| adapter->filtered_discovery = false; |
| adapter->no_scan_restart_delay = false; |
| g_free(adapter->current_discovery_filter); |
| adapter->current_discovery_filter = NULL; |
| |
| adapter->discovering = false; |
| |
| while (adapter->connections) { |
| struct btd_device *device = adapter->connections->data; |
| uint8_t addr_type = btd_device_get_bdaddr_type(device); |
| |
| adapter_remove_connection(adapter, device, BDADDR_BREDR); |
| if (addr_type != BDADDR_BREDR) |
| adapter_remove_connection(adapter, device, addr_type); |
| } |
| |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "Discovering"); |
| |
| if (adapter->dev_class) { |
| /* the kernel should reset the class of device when powering |
| * down, but it does not. So force it here ... */ |
| adapter->dev_class = 0; |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "Class"); |
| } |
| |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "Powered"); |
| |
| DBG("adapter %s has been disabled", adapter->path); |
| } |
| |
| int btd_register_adapter_driver(struct btd_adapter_driver *driver) |
| { |
| adapter_drivers = g_slist_append(adapter_drivers, driver); |
| |
| if (driver->probe == NULL) |
| return 0; |
| |
| adapter_foreach(probe_driver, driver); |
| |
| return 0; |
| } |
| |
| static void unload_driver(struct btd_adapter *adapter, gpointer data) |
| { |
| struct btd_adapter_driver *driver = data; |
| |
| if (driver->remove) |
| driver->remove(adapter); |
| |
| adapter->drivers = g_slist_remove(adapter->drivers, data); |
| } |
| |
| void btd_unregister_adapter_driver(struct btd_adapter_driver *driver) |
| { |
| adapter_drivers = g_slist_remove(adapter_drivers, driver); |
| |
| adapter_foreach(unload_driver, driver); |
| } |
| |
| static void agent_auth_cb(struct agent *agent, DBusError *derr, |
| void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| struct service_auth *auth = g_queue_pop_head(adapter->auths); |
| |
| if (!auth) { |
| DBG("No pending authorization"); |
| return; |
| } |
| |
| auth->cb(derr, auth->user_data); |
| |
| if (auth->agent) |
| agent_unref(auth->agent); |
| |
| g_free(auth); |
| |
| /* Stop processing if queue is empty */ |
| if (g_queue_is_empty(adapter->auths)) { |
| if (adapter->auth_idle_id > 0) |
| g_source_remove(adapter->auth_idle_id); |
| return; |
| } |
| |
| if (adapter->auth_idle_id > 0) |
| return; |
| |
| adapter->auth_idle_id = g_idle_add(process_auth_queue, adapter); |
| } |
| |
| static gboolean process_auth_queue(gpointer user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| DBusError err; |
| |
| adapter->auth_idle_id = 0; |
| |
| dbus_error_init(&err); |
| dbus_set_error_const(&err, ERROR_INTERFACE ".Rejected", NULL); |
| |
| while (!g_queue_is_empty(adapter->auths)) { |
| struct service_auth *auth = adapter->auths->head->data; |
| struct btd_device *device = auth->device; |
| const char *dev_path; |
| |
| /* Wait services to be resolved before asking authorization */ |
| if (auth->svc_id > 0) |
| return FALSE; |
| |
| if (device_is_trusted(device) == TRUE) { |
| auth->cb(NULL, auth->user_data); |
| goto next; |
| } |
| |
| /* If agent is set authorization is already ongoing */ |
| if (auth->agent) |
| return FALSE; |
| |
| auth->agent = agent_get(NULL); |
| if (auth->agent == NULL) { |
| btd_warn(adapter->dev_id, |
| "Authentication attempt without agent"); |
| auth->cb(&err, auth->user_data); |
| goto next; |
| } |
| |
| dev_path = device_get_path(device); |
| |
| if (agent_authorize_service(auth->agent, dev_path, auth->uuid, |
| agent_auth_cb, adapter, NULL) < 0) { |
| auth->cb(&err, auth->user_data); |
| goto next; |
| } |
| |
| break; |
| |
| next: |
| if (auth->agent) |
| agent_unref(auth->agent); |
| |
| g_free(auth); |
| |
| g_queue_pop_head(adapter->auths); |
| } |
| |
| dbus_error_free(&err); |
| |
| return FALSE; |
| } |
| |
| static void svc_complete(struct btd_device *dev, int err, void *user_data) |
| { |
| struct service_auth *auth = user_data; |
| struct btd_adapter *adapter = auth->adapter; |
| |
| auth->svc_id = 0; |
| |
| if (adapter->auth_idle_id != 0) |
| return; |
| |
| adapter->auth_idle_id = g_idle_add(process_auth_queue, adapter); |
| } |
| |
| static int adapter_authorize(struct btd_adapter *adapter, const bdaddr_t *dst, |
| const char *uuid, |
| adapter_authorize_type check_for_connection, |
| service_auth_cb cb, void *user_data) |
| { |
| struct service_auth *auth; |
| struct btd_device *device; |
| static guint id = 0; |
| |
| device = btd_adapter_find_device(adapter, dst, BDADDR_BREDR); |
| if (!device) |
| return 0; |
| |
| if (device_is_disconnecting(device)) { |
| DBG("Authorization request while disconnecting"); |
| return 0; |
| } |
| |
| /* Device connected? */ |
| if (check_for_connection && !g_slist_find(adapter->connections, device)) |
| btd_error(adapter->dev_id, |
| "Authorization request for non-connected device!?"); |
| |
| auth = g_try_new0(struct service_auth, 1); |
| if (!auth) |
| return 0; |
| |
| auth->cb = cb; |
| auth->user_data = user_data; |
| auth->uuid = uuid; |
| auth->device = device; |
| auth->adapter = adapter; |
| auth->id = ++id; |
| if (check_for_connection) |
| auth->svc_id = device_wait_for_svc_complete(device, svc_complete, auth); |
| else { |
| if (adapter->auth_idle_id == 0) |
| adapter->auth_idle_id = g_idle_add(process_auth_queue, adapter); |
| } |
| |
| g_queue_push_tail(adapter->auths, auth); |
| |
| return auth->id; |
| } |
| |
| guint btd_request_authorization(const bdaddr_t *src, const bdaddr_t *dst, |
| const char *uuid, service_auth_cb cb, |
| void *user_data) |
| { |
| struct btd_adapter *adapter; |
| GSList *l; |
| |
| if (bacmp(src, BDADDR_ANY) != 0) { |
| adapter = adapter_find(src); |
| if (!adapter) |
| return 0; |
| |
| return adapter_authorize(adapter, dst, uuid, |
| ADAPTER_AUTHORIZE_CHECK_CONNECTED, cb, user_data); |
| } |
| |
| for (l = adapters; l != NULL; l = g_slist_next(l)) { |
| guint id; |
| |
| adapter = l->data; |
| |
| id = adapter_authorize(adapter, dst, uuid, |
| ADAPTER_AUTHORIZE_CHECK_CONNECTED, cb, user_data); |
| if (id != 0) |
| return id; |
| } |
| |
| return 0; |
| } |
| |
| guint btd_request_authorization_cable_configured(const bdaddr_t *src, const bdaddr_t *dst, |
| const char *uuid, service_auth_cb cb, |
| void *user_data) |
| { |
| struct btd_adapter *adapter; |
| |
| if (bacmp(src, BDADDR_ANY) == 0) |
| return 0; |
| |
| adapter = adapter_find(src); |
| if (!adapter) |
| return 0; |
| |
| return adapter_authorize(adapter, dst, uuid, |
| ADAPTER_AUTHORIZE_DISCONNECTED, cb, user_data); |
| } |
| |
| static struct service_auth *find_authorization(guint id) |
| { |
| GSList *l; |
| GList *l2; |
| |
| for (l = adapters; l != NULL; l = g_slist_next(l)) { |
| struct btd_adapter *adapter = l->data; |
| |
| for (l2 = adapter->auths->head; l2 != NULL; l2 = l2->next) { |
| struct service_auth *auth = l2->data; |
| |
| if (auth->id == id) |
| return auth; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| int btd_cancel_authorization(guint id) |
| { |
| struct service_auth *auth; |
| |
| auth = find_authorization(id); |
| if (auth == NULL) |
| return -EPERM; |
| |
| if (auth->svc_id > 0) |
| device_remove_svc_complete_callback(auth->device, |
| auth->svc_id); |
| |
| g_queue_remove(auth->adapter->auths, auth); |
| |
| if (auth->agent) { |
| agent_cancel(auth->agent); |
| agent_unref(auth->agent); |
| } |
| |
| g_free(auth); |
| |
| return 0; |
| } |
| |
| int btd_adapter_restore_powered(struct btd_adapter *adapter) |
| { |
| if (adapter->current_settings & MGMT_SETTING_POWERED) |
| return 0; |
| |
| set_mode(adapter, MGMT_OP_SET_POWERED, 0x01); |
| |
| return 0; |
| } |
| |
| void btd_adapter_register_pin_cb(struct btd_adapter *adapter, |
| btd_adapter_pin_cb_t cb) |
| { |
| adapter->pin_callbacks = g_slist_prepend(adapter->pin_callbacks, cb); |
| } |
| |
| void btd_adapter_unregister_pin_cb(struct btd_adapter *adapter, |
| btd_adapter_pin_cb_t cb) |
| { |
| adapter->pin_callbacks = g_slist_remove(adapter->pin_callbacks, cb); |
| } |
| |
| void btd_adapter_unregister_msd_cb(struct btd_adapter *adapter, |
| btd_msd_cb_t cb) |
| { |
| adapter->msd_callbacks = g_slist_remove(adapter->msd_callbacks, cb); |
| } |
| |
| void btd_adapter_register_msd_cb(struct btd_adapter *adapter, |
| btd_msd_cb_t cb) |
| { |
| adapter->msd_callbacks = g_slist_prepend(adapter->msd_callbacks, cb); |
| } |
| |
| int btd_adapter_set_fast_connectable(struct btd_adapter *adapter, |
| gboolean enable) |
| { |
| if (!(adapter->current_settings & MGMT_SETTING_POWERED)) |
| return -EINVAL; |
| |
| set_mode(adapter, MGMT_OP_SET_FAST_CONNECTABLE, enable ? 0x01 : 0x00); |
| |
| return 0; |
| } |
| |
| int btd_adapter_read_clock(struct btd_adapter *adapter, const bdaddr_t *bdaddr, |
| int which, int timeout, uint32_t *clock, |
| uint16_t *accuracy) |
| { |
| if (!(adapter->current_settings & MGMT_SETTING_POWERED)) |
| return -EINVAL; |
| |
| return -ENOSYS; |
| } |
| |
| int btd_adapter_remove_bonding(struct btd_adapter *adapter, |
| const bdaddr_t *bdaddr, uint8_t bdaddr_type) |
| { |
| struct mgmt_cp_unpair_device cp; |
| |
| memset(&cp, 0, sizeof(cp)); |
| bacpy(&cp.addr.bdaddr, bdaddr); |
| cp.addr.type = bdaddr_type; |
| cp.disconnect = 1; |
| |
| if (mgmt_send(adapter->mgmt, MGMT_OP_UNPAIR_DEVICE, |
| adapter->dev_id, sizeof(cp), &cp, |
| NULL, NULL, NULL) > 0) |
| return 0; |
| |
| return -EIO; |
| } |
| |
| static void pincode_reply_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct btd_device *device = user_data; |
| |
| /* If the MGMT_OP_PIN_CODE_REPLY command is acknowledged, move the |
| * starting time to that point. This give a better sense of time |
| * evaluating the pincode. */ |
| device_bonding_restart_timer(device); |
| } |
| |
| int btd_adapter_pincode_reply(struct btd_adapter *adapter, |
| const bdaddr_t *bdaddr, |
| const char *pin, size_t pin_len) |
| { |
| struct btd_device *device; |
| unsigned int id; |
| char addr[18]; |
| |
| ba2str(bdaddr, addr); |
| DBG("hci%u addr %s pinlen %zu", adapter->dev_id, addr, pin_len); |
| |
| if (pin == NULL) { |
| struct mgmt_cp_pin_code_neg_reply cp; |
| |
| memset(&cp, 0, sizeof(cp)); |
| bacpy(&cp.addr.bdaddr, bdaddr); |
| cp.addr.type = BDADDR_BREDR; |
| |
| id = mgmt_reply(adapter->mgmt, MGMT_OP_PIN_CODE_NEG_REPLY, |
| adapter->dev_id, sizeof(cp), &cp, |
| NULL, NULL, NULL); |
| } else { |
| struct mgmt_cp_pin_code_reply cp; |
| |
| if (pin_len > 16) |
| return -EINVAL; |
| |
| memset(&cp, 0, sizeof(cp)); |
| bacpy(&cp.addr.bdaddr, bdaddr); |
| cp.addr.type = BDADDR_BREDR; |
| cp.pin_len = pin_len; |
| memcpy(cp.pin_code, pin, pin_len); |
| |
| /* Since a pincode was requested, update the starting time to |
| * the point where the pincode is provided. */ |
| device = btd_adapter_find_device(adapter, bdaddr, BDADDR_BREDR); |
| device_bonding_restart_timer(device); |
| |
| id = mgmt_reply(adapter->mgmt, MGMT_OP_PIN_CODE_REPLY, |
| adapter->dev_id, sizeof(cp), &cp, |
| pincode_reply_complete, device, NULL); |
| } |
| |
| if (id == 0) |
| return -EIO; |
| |
| return 0; |
| } |
| |
| int btd_adapter_confirm_reply(struct btd_adapter *adapter, |
| const bdaddr_t *bdaddr, uint8_t bdaddr_type, |
| gboolean success) |
| { |
| struct mgmt_cp_user_confirm_reply cp; |
| uint16_t opcode; |
| char addr[18]; |
| |
| ba2str(bdaddr, addr); |
| DBG("hci%u addr %s success %d", adapter->dev_id, addr, success); |
| |
| if (success) |
| opcode = MGMT_OP_USER_CONFIRM_REPLY; |
| else |
| opcode = MGMT_OP_USER_CONFIRM_NEG_REPLY; |
| |
| memset(&cp, 0, sizeof(cp)); |
| bacpy(&cp.addr.bdaddr, bdaddr); |
| cp.addr.type = bdaddr_type; |
| |
| if (mgmt_reply(adapter->mgmt, opcode, adapter->dev_id, sizeof(cp), &cp, |
| NULL, NULL, NULL) > 0) |
| return 0; |
| |
| return -EIO; |
| } |
| |
| static void user_confirm_request_callback(uint16_t index, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_ev_user_confirm_request *ev = param; |
| struct btd_adapter *adapter = user_data; |
| struct btd_device *device; |
| char addr[18]; |
| int err; |
| |
| if (length < sizeof(*ev)) { |
| btd_error(adapter->dev_id, |
| "Too small user confirm request event"); |
| return; |
| } |
| |
| ba2str(&ev->addr.bdaddr, addr); |
| DBG("hci%u %s confirm_hint %u", adapter->dev_id, addr, |
| ev->confirm_hint); |
| device = btd_adapter_get_device(adapter, &ev->addr.bdaddr, |
| ev->addr.type); |
| if (!device) { |
| btd_error(adapter->dev_id, |
| "Unable to get device object for %s", addr); |
| return; |
| } |
| |
| err = device_confirm_passkey(device, ev->addr.type, btohl(ev->value), |
| ev->confirm_hint); |
| if (err < 0) { |
| btd_error(adapter->dev_id, |
| "device_confirm_passkey: %s", strerror(-err)); |
| btd_adapter_confirm_reply(adapter, &ev->addr.bdaddr, |
| ev->addr.type, FALSE); |
| } |
| } |
| |
| int btd_adapter_passkey_reply(struct btd_adapter *adapter, |
| const bdaddr_t *bdaddr, uint8_t bdaddr_type, |
| uint32_t passkey) |
| { |
| unsigned int id; |
| char addr[18]; |
| |
| ba2str(bdaddr, addr); |
| DBG("hci%u addr %s passkey %06u", adapter->dev_id, addr, passkey); |
| |
| if (passkey == INVALID_PASSKEY) { |
| struct mgmt_cp_user_passkey_neg_reply cp; |
| |
| memset(&cp, 0, sizeof(cp)); |
| bacpy(&cp.addr.bdaddr, bdaddr); |
| cp.addr.type = bdaddr_type; |
| |
| id = mgmt_reply(adapter->mgmt, MGMT_OP_USER_PASSKEY_NEG_REPLY, |
| adapter->dev_id, sizeof(cp), &cp, |
| NULL, NULL, NULL); |
| } else { |
| struct mgmt_cp_user_passkey_reply cp; |
| |
| memset(&cp, 0, sizeof(cp)); |
| bacpy(&cp.addr.bdaddr, bdaddr); |
| cp.addr.type = bdaddr_type; |
| cp.passkey = htobl(passkey); |
| |
| id = mgmt_reply(adapter->mgmt, MGMT_OP_USER_PASSKEY_REPLY, |
| adapter->dev_id, sizeof(cp), &cp, |
| NULL, NULL, NULL); |
| } |
| |
| if (id == 0) |
| return -EIO; |
| |
| return 0; |
| } |
| |
| static void user_passkey_request_callback(uint16_t index, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_ev_user_passkey_request *ev = param; |
| struct btd_adapter *adapter = user_data; |
| struct btd_device *device; |
| char addr[18]; |
| int err; |
| |
| if (length < sizeof(*ev)) { |
| btd_error(adapter->dev_id, "Too small passkey request event"); |
| return; |
| } |
| |
| ba2str(&ev->addr.bdaddr, addr); |
| DBG("hci%u %s", index, addr); |
| |
| device = btd_adapter_get_device(adapter, &ev->addr.bdaddr, |
| ev->addr.type); |
| if (!device) { |
| btd_error(adapter->dev_id, |
| "Unable to get device object for %s", addr); |
| return; |
| } |
| |
| err = device_request_passkey(device, ev->addr.type); |
| if (err < 0) { |
| btd_error(adapter->dev_id, |
| "device_request_passkey: %s", strerror(-err)); |
| btd_adapter_passkey_reply(adapter, &ev->addr.bdaddr, |
| ev->addr.type, INVALID_PASSKEY); |
| } |
| } |
| |
| static void user_passkey_notify_callback(uint16_t index, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_ev_passkey_notify *ev = param; |
| struct btd_adapter *adapter = user_data; |
| struct btd_device *device; |
| uint32_t passkey; |
| char addr[18]; |
| int err; |
| |
| if (length < sizeof(*ev)) { |
| btd_error(adapter->dev_id, "Too small passkey notify event"); |
| return; |
| } |
| |
| ba2str(&ev->addr.bdaddr, addr); |
| DBG("hci%u %s", index, addr); |
| |
| device = btd_adapter_get_device(adapter, &ev->addr.bdaddr, |
| ev->addr.type); |
| if (!device) { |
| btd_error(adapter->dev_id, |
| "Unable to get device object for %s", addr); |
| return; |
| } |
| |
| passkey = get_le32(&ev->passkey); |
| |
| DBG("passkey %06u entered %u", passkey, ev->entered); |
| |
| err = device_notify_passkey(device, ev->addr.type, passkey, |
| ev->entered); |
| if (err < 0) |
| btd_error(adapter->dev_id, |
| "device_notify_passkey: %s", strerror(-err)); |
| } |
| |
| struct btd_adapter_pin_cb_iter *btd_adapter_pin_cb_iter_new( |
| struct btd_adapter *adapter) |
| { |
| struct btd_adapter_pin_cb_iter *iter = |
| g_new0(struct btd_adapter_pin_cb_iter, 1); |
| |
| iter->it = adapter->pin_callbacks; |
| iter->attempt = 1; |
| |
| return iter; |
| } |
| |
| void btd_adapter_pin_cb_iter_free(struct btd_adapter_pin_cb_iter *iter) |
| { |
| g_free(iter); |
| } |
| |
| bool btd_adapter_pin_cb_iter_end(struct btd_adapter_pin_cb_iter *iter) |
| { |
| return iter->it == NULL && iter->attempt == 0; |
| } |
| |
| static ssize_t btd_adapter_pin_cb_iter_next( |
| struct btd_adapter_pin_cb_iter *iter, |
| struct btd_adapter *adapter, |
| struct btd_device *device, |
| char *pin_buf, bool *display) |
| { |
| btd_adapter_pin_cb_t cb; |
| ssize_t ret; |
| |
| while (iter->it != NULL) { |
| cb = iter->it->data; |
| ret = cb(adapter, device, pin_buf, display, iter->attempt); |
| iter->attempt++; |
| if (ret > 0) |
| return ret; |
| iter->attempt = 1; |
| iter->it = g_slist_next(iter->it); |
| } |
| iter->attempt = 0; |
| |
| return 0; |
| } |
| |
| static void pin_code_request_callback(uint16_t index, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_ev_pin_code_request *ev = param; |
| struct btd_adapter *adapter = user_data; |
| struct btd_device *device; |
| bool display = false; |
| char pin[17]; |
| ssize_t pinlen; |
| char addr[18]; |
| int err; |
| struct btd_adapter_pin_cb_iter *iter; |
| |
| if (length < sizeof(*ev)) { |
| btd_error(adapter->dev_id, "Too small PIN code request event"); |
| return; |
| } |
| |
| ba2str(&ev->addr.bdaddr, addr); |
| |
| DBG("hci%u %s", adapter->dev_id, addr); |
| |
| device = btd_adapter_get_device(adapter, &ev->addr.bdaddr, |
| ev->addr.type); |
| if (!device) { |
| btd_error(adapter->dev_id, |
| "Unable to get device object for %s", addr); |
| return; |
| } |
| |
| /* Flag the request of a pincode to allow a bonding retry. */ |
| adapter->pincode_requested = true; |
| |
| memset(pin, 0, sizeof(pin)); |
| |
| iter = device_bonding_iter(device); |
| if (iter == NULL) |
| pinlen = 0; |
| else |
| pinlen = btd_adapter_pin_cb_iter_next(iter, adapter, device, |
| pin, &display); |
| |
| if (pinlen > 0 && (!ev->secure || pinlen == 16)) { |
| if (display && device_is_bonding(device, NULL)) { |
| err = device_notify_pincode(device, ev->secure, pin); |
| if (err < 0) { |
| btd_error(adapter->dev_id, |
| "device_notify_pin: %s", |
| strerror(-err)); |
| btd_adapter_pincode_reply(adapter, |
| &ev->addr.bdaddr, |
| NULL, 0); |
| } |
| } else { |
| btd_adapter_pincode_reply(adapter, &ev->addr.bdaddr, |
| pin, pinlen); |
| } |
| return; |
| } |
| |
| err = device_request_pincode(device, ev->secure); |
| if (err < 0) { |
| btd_error(adapter->dev_id, "device_request_pin: %s", |
| strerror(-err)); |
| btd_adapter_pincode_reply(adapter, &ev->addr.bdaddr, NULL, 0); |
| } |
| } |
| |
| int adapter_cancel_bonding(struct btd_adapter *adapter, const bdaddr_t *bdaddr, |
| uint8_t addr_type) |
| { |
| struct mgmt_addr_info cp; |
| char addr[18]; |
| |
| ba2str(bdaddr, addr); |
| DBG("hci%u bdaddr %s type %u", adapter->dev_id, addr, addr_type); |
| |
| memset(&cp, 0, sizeof(cp)); |
| bacpy(&cp.bdaddr, bdaddr); |
| cp.type = addr_type; |
| |
| if (mgmt_reply(adapter->mgmt, MGMT_OP_CANCEL_PAIR_DEVICE, |
| adapter->dev_id, sizeof(cp), &cp, |
| NULL, NULL, NULL) > 0) |
| return 0; |
| |
| return -EIO; |
| } |
| |
| static void check_oob_bonding_complete(struct btd_adapter *adapter, |
| const bdaddr_t *bdaddr, uint8_t status) |
| { |
| if (!adapter->oob_handler || !adapter->oob_handler->bonding_cb) |
| return; |
| |
| if (bacmp(bdaddr, &adapter->oob_handler->remote_addr) != 0) |
| return; |
| |
| adapter->oob_handler->bonding_cb(adapter, bdaddr, status, |
| adapter->oob_handler->user_data); |
| |
| g_free(adapter->oob_handler); |
| adapter->oob_handler = NULL; |
| } |
| |
| static void bonding_complete(struct btd_adapter *adapter, |
| const bdaddr_t *bdaddr, |
| uint8_t addr_type, uint8_t status) |
| { |
| struct btd_device *device; |
| |
| if (status == 0) |
| device = btd_adapter_get_device(adapter, bdaddr, addr_type); |
| else |
| device = btd_adapter_find_device(adapter, bdaddr, addr_type); |
| |
| if (device != NULL) |
| device_bonding_complete(device, addr_type, status); |
| |
| resume_discovery(adapter); |
| |
| check_oob_bonding_complete(adapter, bdaddr, status); |
| } |
| |
| /* bonding_attempt_complete() handles the end of a "bonding attempt" checking if |
| * it should begin a new attempt or complete the bonding. |
| */ |
| static void bonding_attempt_complete(struct btd_adapter *adapter, |
| const bdaddr_t *bdaddr, |
| uint8_t addr_type, uint8_t status) |
| { |
| struct btd_device *device; |
| char addr[18]; |
| |
| ba2str(bdaddr, addr); |
| DBG("hci%u bdaddr %s type %u status 0x%x", adapter->dev_id, addr, |
| addr_type, status); |
| |
| if (status == 0) |
| device = btd_adapter_get_device(adapter, bdaddr, addr_type); |
| else |
| device = btd_adapter_find_device(adapter, bdaddr, addr_type); |
| |
| if (status == MGMT_STATUS_AUTH_FAILED && adapter->pincode_requested) { |
| /* On faliure, issue a bonding_retry if possible. */ |
| if (device != NULL) { |
| if (device_bonding_attempt_retry(device) == 0) |
| return; |
| } |
| } |
| |
| /* Ignore disconnects during retry. */ |
| if (status == MGMT_STATUS_DISCONNECTED && |
| device && device_is_retrying(device)) |
| return; |
| |
| /* In any other case, finish the bonding. */ |
| bonding_complete(adapter, bdaddr, addr_type, status); |
| } |
| |
| struct pair_device_data { |
| struct btd_adapter *adapter; |
| bdaddr_t bdaddr; |
| uint8_t addr_type; |
| }; |
| |
| static void free_pair_device_data(void *user_data) |
| { |
| struct pair_device_data *data = user_data; |
| |
| g_free(data); |
| } |
| |
| static gboolean pair_device_timeout(gpointer user_data) |
| { |
| struct pair_device_data *data = user_data; |
| struct btd_adapter *adapter = data->adapter; |
| |
| btd_error(adapter->dev_id, "Pair device timed out for hci%u", |
| adapter->dev_id); |
| |
| adapter->pair_device_timeout = 0; |
| |
| adapter_cancel_bonding(adapter, &data->bdaddr, data->addr_type); |
| |
| return FALSE; |
| } |
| |
| static void pair_device_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_rp_pair_device *rp = param; |
| struct pair_device_data *data = user_data; |
| struct btd_adapter *adapter = data->adapter; |
| |
| DBG("%s (0x%02x)", mgmt_errstr(status), status); |
| |
| adapter->pair_device_id = 0; |
| |
| if (adapter->pair_device_timeout > 0) { |
| g_source_remove(adapter->pair_device_timeout); |
| adapter->pair_device_timeout = 0; |
| } |
| |
| /* Workaround for a kernel bug |
| * |
| * Broken kernels may reply to device pairing command with command |
| * status instead of command complete event e.g. if adapter was not |
| * powered. |
| */ |
| if (status != MGMT_STATUS_SUCCESS && length < sizeof(*rp)) { |
| btd_error(adapter->dev_id, "Pair device failed: %s (0x%02x)", |
| mgmt_errstr(status), status); |
| |
| bonding_attempt_complete(adapter, &data->bdaddr, |
| data->addr_type, status); |
| return; |
| } |
| |
| if (length < sizeof(*rp)) { |
| btd_error(adapter->dev_id, "Too small pair device response"); |
| return; |
| } |
| |
| bonding_attempt_complete(adapter, &rp->addr.bdaddr, rp->addr.type, |
| status); |
| } |
| |
| int adapter_create_bonding(struct btd_adapter *adapter, const bdaddr_t *bdaddr, |
| uint8_t addr_type, uint8_t io_cap) |
| { |
| if (adapter->pair_device_id > 0) { |
| btd_error(adapter->dev_id, |
| "Unable pair since another pairing is in progress"); |
| return -EBUSY; |
| } |
| |
| suspend_discovery(adapter); |
| |
| return adapter_bonding_attempt(adapter, bdaddr, addr_type, io_cap); |
| } |
| |
| /* Starts a new bonding attempt in a fresh new bonding_req or a retried one. */ |
| int adapter_bonding_attempt(struct btd_adapter *adapter, const bdaddr_t *bdaddr, |
| uint8_t addr_type, uint8_t io_cap) |
| { |
| struct mgmt_cp_pair_device cp; |
| char addr[18]; |
| struct pair_device_data *data; |
| unsigned int id; |
| |
| ba2str(bdaddr, addr); |
| DBG("hci%u bdaddr %s type %d io_cap 0x%02x", |
| adapter->dev_id, addr, addr_type, io_cap); |
| |
| /* Reset the pincode_requested flag for a new bonding attempt. */ |
| adapter->pincode_requested = false; |
| |
| memset(&cp, 0, sizeof(cp)); |
| bacpy(&cp.addr.bdaddr, bdaddr); |
| cp.addr.type = addr_type; |
| cp.io_cap = io_cap; |
| |
| data = g_new0(struct pair_device_data, 1); |
| data->adapter = adapter; |
| bacpy(&data->bdaddr, bdaddr); |
| data->addr_type = addr_type; |
| |
| id = mgmt_send(adapter->mgmt, MGMT_OP_PAIR_DEVICE, |
| adapter->dev_id, sizeof(cp), &cp, |
| pair_device_complete, data, |
| free_pair_device_data); |
| |
| if (id == 0) { |
| btd_error(adapter->dev_id, "Failed to pair %s for hci%u", |
| addr, adapter->dev_id); |
| free_pair_device_data(data); |
| return -EIO; |
| } |
| |
| adapter->pair_device_id = id; |
| |
| /* Due to a bug in the kernel it is possible that a LE pairing |
| * request never times out. Therefore, add a timer to clean up |
| * if no response arrives |
| */ |
| adapter->pair_device_timeout = g_timeout_add_seconds(BONDING_TIMEOUT, |
| pair_device_timeout, data); |
| |
| return 0; |
| } |
| |
| static void disconnect_notify(struct btd_device *dev, uint8_t reason) |
| { |
| GSList *l; |
| |
| for (l = disconnect_list; l; l = g_slist_next(l)) { |
| btd_disconnect_cb disconnect_cb = l->data; |
| disconnect_cb(dev, reason); |
| } |
| } |
| |
| static void dev_disconnected(struct btd_adapter *adapter, |
| const struct mgmt_addr_info *addr, |
| uint8_t reason) |
| { |
| struct btd_device *device; |
| char dst[18]; |
| |
| ba2str(&addr->bdaddr, dst); |
| |
| DBG("Device %s disconnected, reason %u", dst, reason); |
| |
| device = btd_adapter_find_device(adapter, &addr->bdaddr, addr->type); |
| if (device) { |
| adapter_remove_connection(adapter, device, addr->type); |
| disconnect_notify(device, reason); |
| } |
| |
| bonding_attempt_complete(adapter, &addr->bdaddr, addr->type, |
| MGMT_STATUS_DISCONNECTED); |
| } |
| |
| void btd_add_disconnect_cb(btd_disconnect_cb func) |
| { |
| disconnect_list = g_slist_append(disconnect_list, func); |
| } |
| |
| void btd_remove_disconnect_cb(btd_disconnect_cb func) |
| { |
| disconnect_list = g_slist_remove(disconnect_list, func); |
| } |
| |
| static void disconnect_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_rp_disconnect *rp = param; |
| struct btd_adapter *adapter = user_data; |
| |
| if (status == MGMT_STATUS_NOT_CONNECTED) { |
| btd_warn(adapter->dev_id, |
| "Disconnecting failed: already disconnected"); |
| } else if (status != MGMT_STATUS_SUCCESS) { |
| btd_error(adapter->dev_id, |
| "Failed to disconnect device: %s (0x%02x)", |
| mgmt_errstr(status), status); |
| return; |
| } |
| |
| if (length < sizeof(*rp)) { |
| btd_error(adapter->dev_id, |
| "Too small device disconnect response"); |
| return; |
| } |
| |
| dev_disconnected(adapter, &rp->addr, MGMT_DEV_DISCONN_LOCAL_HOST); |
| } |
| |
| int btd_adapter_disconnect_device(struct btd_adapter *adapter, |
| const bdaddr_t *bdaddr, |
| uint8_t bdaddr_type) |
| |
| { |
| struct mgmt_cp_disconnect cp; |
| |
| memset(&cp, 0, sizeof(cp)); |
| bacpy(&cp.addr.bdaddr, bdaddr); |
| cp.addr.type = bdaddr_type; |
| |
| if (mgmt_send(adapter->mgmt, MGMT_OP_DISCONNECT, |
| adapter->dev_id, sizeof(cp), &cp, |
| disconnect_complete, adapter, NULL) > 0) |
| return 0; |
| |
| return -EIO; |
| } |
| |
| static void auth_failed_callback(uint16_t index, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_ev_auth_failed *ev = param; |
| struct btd_adapter *adapter = user_data; |
| |
| if (length < sizeof(*ev)) { |
| btd_error(adapter->dev_id, "Too small auth failed mgmt event"); |
| return; |
| } |
| |
| bonding_attempt_complete(adapter, &ev->addr.bdaddr, ev->addr.type, |
| ev->status); |
| } |
| |
| static void store_link_key(struct btd_adapter *adapter, |
| struct btd_device *device, const uint8_t *key, |
| uint8_t type, uint8_t pin_length) |
| { |
| char device_addr[18]; |
| char filename[PATH_MAX]; |
| GKeyFile *key_file; |
| gsize length = 0; |
| char key_str[33]; |
| char *str; |
| int i; |
| |
| ba2str(device_get_address(device), device_addr); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", |
| btd_adapter_get_storage_dir(adapter), device_addr); |
| key_file = g_key_file_new(); |
| g_key_file_load_from_file(key_file, filename, 0, NULL); |
| |
| for (i = 0; i < 16; i++) |
| sprintf(key_str + (i * 2), "%2.2X", key[i]); |
| |
| g_key_file_set_string(key_file, "LinkKey", "Key", key_str); |
| |
| g_key_file_set_integer(key_file, "LinkKey", "Type", type); |
| g_key_file_set_integer(key_file, "LinkKey", "PINLength", pin_length); |
| |
| 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 new_link_key_callback(uint16_t index, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_ev_new_link_key *ev = param; |
| const struct mgmt_addr_info *addr = &ev->key.addr; |
| struct btd_adapter *adapter = user_data; |
| struct btd_device *device; |
| char dst[18]; |
| |
| if (length < sizeof(*ev)) { |
| btd_error(adapter->dev_id, "Too small new link key event"); |
| return; |
| } |
| |
| ba2str(&addr->bdaddr, dst); |
| |
| DBG("hci%u new key for %s type %u pin_len %u store_hint %u", |
| adapter->dev_id, dst, ev->key.type, ev->key.pin_len, |
| ev->store_hint); |
| |
| if (ev->key.pin_len > 16) { |
| btd_error(adapter->dev_id, |
| "Invalid PIN length (%u) in new_key event", |
| ev->key.pin_len); |
| return; |
| } |
| |
| device = btd_adapter_get_device(adapter, &addr->bdaddr, addr->type); |
| if (!device) { |
| btd_error(adapter->dev_id, |
| "Unable to get device object for %s", dst); |
| return; |
| } |
| |
| if (ev->store_hint) { |
| const struct mgmt_link_key_info *key = &ev->key; |
| |
| store_link_key(adapter, device, key->val, key->type, |
| key->pin_len); |
| |
| device_set_bonded(device, BDADDR_BREDR); |
| } |
| |
| bonding_complete(adapter, &addr->bdaddr, addr->type, 0); |
| } |
| |
| static void store_longtermkey(struct btd_adapter *adapter, const bdaddr_t *peer, |
| uint8_t bdaddr_type, const unsigned char *key, |
| uint8_t master, uint8_t authenticated, |
| uint8_t enc_size, uint16_t ediv, |
| uint64_t rand) |
| { |
| const char *group = master ? "LongTermKey" : "SlaveLongTermKey"; |
| char device_addr[18]; |
| char filename[PATH_MAX]; |
| GKeyFile *key_file; |
| char key_str[33]; |
| gsize length = 0; |
| char *str; |
| int i; |
| |
| if (master != 0x00 && master != 0x01) { |
| error("Unsupported LTK type %u", master); |
| return; |
| } |
| |
| ba2str(peer, device_addr); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", |
| btd_adapter_get_storage_dir(adapter), device_addr); |
| key_file = g_key_file_new(); |
| g_key_file_load_from_file(key_file, filename, 0, NULL); |
| |
| /* Old files may contain this so remove it in case it exists */ |
| g_key_file_remove_key(key_file, "LongTermKey", "Master", NULL); |
| |
| for (i = 0; i < 16; i++) |
| sprintf(key_str + (i * 2), "%2.2X", key[i]); |
| |
| g_key_file_set_string(key_file, group, "Key", key_str); |
| |
| g_key_file_set_integer(key_file, group, "Authenticated", |
| authenticated); |
| g_key_file_set_integer(key_file, group, "EncSize", enc_size); |
| |
| g_key_file_set_integer(key_file, group, "EDiv", ediv); |
| g_key_file_set_uint64(key_file, group, "Rand", rand); |
| |
| 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 new_long_term_key_callback(uint16_t index, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_ev_new_long_term_key *ev = param; |
| const struct mgmt_addr_info *addr = &ev->key.addr; |
| struct btd_adapter *adapter = user_data; |
| struct btd_device *device; |
| bool persistent; |
| char dst[18]; |
| |
| if (length < sizeof(*ev)) { |
| btd_error(adapter->dev_id, "Too small long term key event"); |
| return; |
| } |
| |
| ba2str(&addr->bdaddr, dst); |
| |
| DBG("hci%u new LTK for %s type %u enc_size %u", |
| adapter->dev_id, dst, ev->key.type, ev->key.enc_size); |
| |
| device = btd_adapter_get_device(adapter, &addr->bdaddr, addr->type); |
| if (!device) { |
| btd_error(adapter->dev_id, |
| "Unable to get device object for %s", dst); |
| return; |
| } |
| |
| /* |
| * Some older kernel versions set store_hint for long term keys |
| * from resolvable and unresolvable random addresses, but there |
| * is no point in storing these. Next time around the device |
| * address will be invalid. |
| * |
| * So only for identity addresses (public and static random) use |
| * the store_hint as an indication if the long term key should |
| * be persistently stored. |
| * |
| */ |
| if (addr->type == BDADDR_LE_RANDOM && |
| (addr->bdaddr.b[5] & 0xc0) != 0xc0) |
| persistent = false; |
| else |
| persistent = !!ev->store_hint; |
| |
| if (persistent) { |
| const struct mgmt_ltk_info *key = &ev->key; |
| uint16_t ediv; |
| uint64_t rand; |
| |
| ediv = le16_to_cpu(key->ediv); |
| rand = le64_to_cpu(key->rand); |
| |
| store_longtermkey(adapter, &key->addr.bdaddr, |
| key->addr.type, key->val, key->master, |
| key->type, key->enc_size, ediv, rand); |
| |
| device_set_bonded(device, addr->type); |
| } |
| |
| bonding_complete(adapter, &addr->bdaddr, addr->type, 0); |
| } |
| |
| static void store_csrk(struct btd_adapter *adapter, const bdaddr_t *peer, |
| uint8_t bdaddr_type, const unsigned char *key, |
| uint32_t counter, uint8_t type) |
| { |
| const char *group; |
| char device_addr[18]; |
| char filename[PATH_MAX]; |
| GKeyFile *key_file; |
| char key_str[33]; |
| gsize length = 0; |
| gboolean auth; |
| char *str; |
| int i; |
| |
| switch (type) { |
| case 0x00: |
| group = "LocalSignatureKey"; |
| auth = FALSE; |
| break; |
| case 0x01: |
| group = "RemoteSignatureKey"; |
| auth = FALSE; |
| break; |
| case 0x02: |
| group = "LocalSignatureKey"; |
| auth = TRUE; |
| break; |
| case 0x03: |
| group = "RemoteSignatureKey"; |
| auth = TRUE; |
| break; |
| default: |
| warn("Unsupported CSRK type %u", type); |
| return; |
| } |
| |
| ba2str(peer, device_addr); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", |
| btd_adapter_get_storage_dir(adapter), device_addr); |
| |
| key_file = g_key_file_new(); |
| g_key_file_load_from_file(key_file, filename, 0, NULL); |
| |
| for (i = 0; i < 16; i++) |
| sprintf(key_str + (i * 2), "%2.2X", key[i]); |
| |
| g_key_file_set_string(key_file, group, "Key", key_str); |
| g_key_file_set_integer(key_file, group, "Counter", counter); |
| g_key_file_set_boolean(key_file, group, "Authenticated", auth); |
| |
| 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 new_csrk_callback(uint16_t index, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_ev_new_csrk *ev = param; |
| const struct mgmt_addr_info *addr = &ev->key.addr; |
| const struct mgmt_csrk_info *key = &ev->key; |
| struct btd_adapter *adapter = user_data; |
| struct btd_device *device; |
| char dst[18]; |
| |
| if (length < sizeof(*ev)) { |
| btd_error(adapter->dev_id, "Too small CSRK event"); |
| return; |
| } |
| |
| ba2str(&addr->bdaddr, dst); |
| |
| DBG("hci%u new CSRK for %s type %u", adapter->dev_id, dst, |
| ev->key.type); |
| |
| device = btd_adapter_get_device(adapter, &addr->bdaddr, addr->type); |
| if (!device) { |
| btd_error(adapter->dev_id, |
| "Unable to get device object for %s", dst); |
| return; |
| } |
| |
| if (!ev->store_hint) |
| return; |
| |
| store_csrk(adapter, &key->addr.bdaddr, key->addr.type, key->val, 0, |
| key->type); |
| |
| btd_device_set_temporary(device, false); |
| } |
| |
| static void store_irk(struct btd_adapter *adapter, const bdaddr_t *peer, |
| uint8_t bdaddr_type, const unsigned char *key) |
| { |
| char device_addr[18]; |
| char filename[PATH_MAX]; |
| GKeyFile *key_file; |
| char *store_data; |
| char str[33]; |
| size_t length = 0; |
| int i; |
| |
| ba2str(peer, device_addr); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", |
| btd_adapter_get_storage_dir(adapter), device_addr); |
| key_file = g_key_file_new(); |
| g_key_file_load_from_file(key_file, filename, 0, NULL); |
| |
| for (i = 0; i < 16; i++) |
| sprintf(str + (i * 2), "%2.2X", key[i]); |
| |
| g_key_file_set_string(key_file, "IdentityResolvingKey", "Key", str); |
| |
| create_file(filename, S_IRUSR | S_IWUSR); |
| |
| store_data = g_key_file_to_data(key_file, &length, NULL); |
| g_file_set_contents(filename, store_data, length, NULL); |
| g_free(store_data); |
| |
| g_key_file_free(key_file); |
| } |
| |
| static void new_irk_callback(uint16_t index, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_ev_new_irk *ev = param; |
| const struct mgmt_addr_info *addr = &ev->key.addr; |
| const struct mgmt_irk_info *irk = &ev->key; |
| struct btd_adapter *adapter = user_data; |
| struct btd_device *device, *duplicate; |
| bool persistent; |
| char dst[18], rpa[18]; |
| |
| if (length < sizeof(*ev)) { |
| btd_error(adapter->dev_id, "Too small New IRK event"); |
| return; |
| } |
| |
| ba2str(&addr->bdaddr, dst); |
| ba2str(&ev->rpa, rpa); |
| |
| DBG("hci%u new IRK for %s RPA %s", adapter->dev_id, dst, rpa); |
| |
| if (bacmp(&ev->rpa, BDADDR_ANY)) { |
| device = btd_adapter_get_device(adapter, &ev->rpa, |
| BDADDR_LE_RANDOM); |
| duplicate = btd_adapter_find_device(adapter, &addr->bdaddr, |
| addr->type); |
| if (duplicate == device) |
| duplicate = NULL; |
| } else { |
| device = btd_adapter_get_device(adapter, &addr->bdaddr, |
| addr->type); |
| duplicate = NULL; |
| } |
| |
| if (!device) { |
| btd_error(adapter->dev_id, |
| "Unable to get device object for %s", dst); |
| return; |
| } |
| |
| device_update_addr(device, &addr->bdaddr, addr->type); |
| |
| if (duplicate) |
| device_merge_duplicate(device, duplicate); |
| |
| persistent = !!ev->store_hint; |
| if (!persistent) |
| return; |
| |
| store_irk(adapter, &addr->bdaddr, addr->type, irk->val); |
| |
| btd_device_set_temporary(device, false); |
| } |
| |
| static void store_conn_param(struct btd_adapter *adapter, const bdaddr_t *peer, |
| uint8_t bdaddr_type, uint16_t min_interval, |
| uint16_t max_interval, uint16_t latency, |
| uint16_t timeout) |
| { |
| char device_addr[18]; |
| char filename[PATH_MAX]; |
| GKeyFile *key_file; |
| char *store_data; |
| size_t length = 0; |
| |
| ba2str(peer, device_addr); |
| |
| DBG(""); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", |
| btd_adapter_get_storage_dir(adapter), device_addr); |
| key_file = g_key_file_new(); |
| g_key_file_load_from_file(key_file, filename, 0, NULL); |
| |
| g_key_file_set_integer(key_file, "ConnectionParameters", |
| "MinInterval", min_interval); |
| g_key_file_set_integer(key_file, "ConnectionParameters", |
| "MaxInterval", max_interval); |
| g_key_file_set_integer(key_file, "ConnectionParameters", |
| "Latency", latency); |
| g_key_file_set_integer(key_file, "ConnectionParameters", |
| "Timeout", timeout); |
| |
| create_file(filename, S_IRUSR | S_IWUSR); |
| |
| store_data = g_key_file_to_data(key_file, &length, NULL); |
| g_file_set_contents(filename, store_data, length, NULL); |
| g_free(store_data); |
| |
| g_key_file_free(key_file); |
| } |
| |
| static void new_conn_param(uint16_t index, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_ev_new_conn_param *ev = param; |
| struct btd_adapter *adapter = user_data; |
| uint16_t min, max, latency, timeout; |
| struct btd_device *dev; |
| char dst[18]; |
| |
| |
| if (length < sizeof(*ev)) { |
| btd_error(adapter->dev_id, |
| "Too small New Connection Parameter event"); |
| return; |
| } |
| |
| ba2str(&ev->addr.bdaddr, dst); |
| |
| min = btohs(ev->min_interval); |
| max = btohs(ev->max_interval); |
| latency = btohs(ev->latency); |
| timeout = btohs(ev->timeout); |
| |
| DBG("hci%u %s (%u) min 0x%04x max 0x%04x latency 0x%04x timeout 0x%04x", |
| adapter->dev_id, dst, ev->addr.type, min, max, latency, timeout); |
| |
| dev = btd_adapter_get_device(adapter, &ev->addr.bdaddr, ev->addr.type); |
| if (!dev) { |
| btd_error(adapter->dev_id, |
| "Unable to get device object for %s", dst); |
| return; |
| } |
| |
| if (!ev->store_hint) |
| return; |
| |
| store_conn_param(adapter, &ev->addr.bdaddr, ev->addr.type, |
| ev->min_interval, ev->max_interval, |
| ev->latency, ev->timeout); |
| } |
| |
| int adapter_set_io_capability(struct btd_adapter *adapter, uint8_t io_cap) |
| { |
| struct mgmt_cp_set_io_capability cp; |
| |
| memset(&cp, 0, sizeof(cp)); |
| cp.io_capability = io_cap; |
| |
| if (mgmt_send(adapter->mgmt, MGMT_OP_SET_IO_CAPABILITY, |
| adapter->dev_id, sizeof(cp), &cp, |
| NULL, NULL, NULL) > 0) |
| return 0; |
| |
| return -EIO; |
| } |
| |
| int btd_adapter_add_remote_oob_data(struct btd_adapter *adapter, |
| const bdaddr_t *bdaddr, |
| uint8_t *hash, uint8_t *randomizer) |
| { |
| struct mgmt_cp_add_remote_oob_data cp; |
| char addr[18]; |
| |
| ba2str(bdaddr, addr); |
| DBG("hci%d bdaddr %s", adapter->dev_id, addr); |
| |
| memset(&cp, 0, sizeof(cp)); |
| bacpy(&cp.addr.bdaddr, bdaddr); |
| memcpy(cp.hash192, hash, 16); |
| |
| if (randomizer) |
| memcpy(cp.rand192, randomizer, 16); |
| |
| if (mgmt_send(adapter->mgmt, MGMT_OP_ADD_REMOTE_OOB_DATA, |
| adapter->dev_id, sizeof(cp), &cp, |
| NULL, NULL, NULL) > 0) |
| return 0; |
| |
| return -EIO; |
| } |
| |
| int btd_adapter_remove_remote_oob_data(struct btd_adapter *adapter, |
| const bdaddr_t *bdaddr) |
| { |
| struct mgmt_cp_remove_remote_oob_data cp; |
| char addr[18]; |
| |
| ba2str(bdaddr, addr); |
| DBG("hci%d bdaddr %s", adapter->dev_id, addr); |
| |
| memset(&cp, 0, sizeof(cp)); |
| bacpy(&cp.addr.bdaddr, bdaddr); |
| |
| if (mgmt_send(adapter->mgmt, MGMT_OP_REMOVE_REMOTE_OOB_DATA, |
| adapter->dev_id, sizeof(cp), &cp, |
| NULL, NULL, NULL) > 0) |
| return 0; |
| |
| return -EIO; |
| } |
| |
| bool btd_adapter_ssp_enabled(struct btd_adapter *adapter) |
| { |
| if (adapter->current_settings & MGMT_SETTING_SSP) |
| return true; |
| |
| return false; |
| } |
| |
| void btd_adapter_set_oob_handler(struct btd_adapter *adapter, |
| struct oob_handler *handler) |
| { |
| adapter->oob_handler = handler; |
| } |
| |
| gboolean btd_adapter_check_oob_handler(struct btd_adapter *adapter) |
| { |
| return adapter->oob_handler != NULL; |
| } |
| |
| static void read_local_oob_data_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_rp_read_local_oob_data *rp = param; |
| struct btd_adapter *adapter = user_data; |
| const uint8_t *hash, *randomizer; |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| btd_error(adapter->dev_id, |
| "Read local OOB data failed: %s (0x%02x)", |
| mgmt_errstr(status), status); |
| hash = NULL; |
| randomizer = NULL; |
| } else if (length < sizeof(*rp)) { |
| btd_error(adapter->dev_id, |
| "Too small read local OOB data response"); |
| return; |
| } else { |
| hash = rp->hash192; |
| randomizer = rp->rand192; |
| } |
| |
| if (!adapter->oob_handler || !adapter->oob_handler->read_local_cb) |
| return; |
| |
| adapter->oob_handler->read_local_cb(adapter, hash, randomizer, |
| adapter->oob_handler->user_data); |
| |
| g_free(adapter->oob_handler); |
| adapter->oob_handler = NULL; |
| } |
| |
| int btd_adapter_read_local_oob_data(struct btd_adapter *adapter) |
| { |
| DBG("hci%u", adapter->dev_id); |
| |
| if (mgmt_send(adapter->mgmt, MGMT_OP_READ_LOCAL_OOB_DATA, |
| adapter->dev_id, 0, NULL, read_local_oob_data_complete, |
| adapter, NULL) > 0) |
| return 0; |
| |
| return -EIO; |
| } |
| |
| void btd_adapter_for_each_device(struct btd_adapter *adapter, |
| void (*cb)(struct btd_device *device, void *data), |
| void *data) |
| { |
| g_slist_foreach(adapter->devices, (GFunc) cb, data); |
| } |
| |
| static int adapter_cmp(gconstpointer a, gconstpointer b) |
| { |
| struct btd_adapter *adapter = (struct btd_adapter *) a; |
| const bdaddr_t *bdaddr = b; |
| |
| return bacmp(&adapter->bdaddr, bdaddr); |
| } |
| |
| static int adapter_id_cmp(gconstpointer a, gconstpointer b) |
| { |
| struct btd_adapter *adapter = (struct btd_adapter *) a; |
| uint16_t id = GPOINTER_TO_UINT(b); |
| |
| return adapter->dev_id == id ? 0 : -1; |
| } |
| |
| struct btd_adapter *adapter_find(const bdaddr_t *sba) |
| { |
| GSList *match; |
| |
| match = g_slist_find_custom(adapters, sba, adapter_cmp); |
| if (!match) |
| return NULL; |
| |
| return match->data; |
| } |
| |
| struct btd_adapter *adapter_find_by_id(int id) |
| { |
| GSList *match; |
| |
| match = g_slist_find_custom(adapters, GINT_TO_POINTER(id), |
| adapter_id_cmp); |
| if (!match) |
| return NULL; |
| |
| return match->data; |
| } |
| |
| void adapter_foreach(adapter_cb func, gpointer user_data) |
| { |
| g_slist_foreach(adapters, (GFunc) func, user_data); |
| } |
| |
| static int set_did(struct btd_adapter *adapter, uint16_t vendor, |
| uint16_t product, uint16_t version, uint16_t source) |
| { |
| struct mgmt_cp_set_device_id cp; |
| |
| DBG("hci%u source %x vendor %x product %x version %x", |
| adapter->dev_id, source, vendor, product, version); |
| |
| memset(&cp, 0, sizeof(cp)); |
| |
| cp.source = htobs(source); |
| cp.vendor = htobs(vendor); |
| cp.product = htobs(product); |
| cp.version = htobs(version); |
| |
| if (mgmt_send(adapter->mgmt, MGMT_OP_SET_DEVICE_ID, |
| adapter->dev_id, sizeof(cp), &cp, |
| NULL, NULL, NULL) > 0) |
| return 0; |
| |
| return -EIO; |
| } |
| |
| static void services_modified(struct gatt_db_attribute *attrib, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| |
| g_dbus_emit_property_changed(dbus_conn, adapter->path, |
| ADAPTER_INTERFACE, "UUIDs"); |
| } |
| |
| static int adapter_register(struct btd_adapter *adapter) |
| { |
| struct agent *agent; |
| struct gatt_db *db; |
| |
| if (powering_down) |
| return -EBUSY; |
| |
| adapter->path = g_strdup_printf("/org/bluez/hci%d", adapter->dev_id); |
| |
| if (!g_dbus_register_interface(dbus_conn, |
| adapter->path, ADAPTER_INTERFACE, |
| adapter_methods, NULL, |
| adapter_properties, adapter, |
| adapter_free)) { |
| btd_error(adapter->dev_id, |
| "Adapter interface init failed on path %s", |
| adapter->path); |
| g_free(adapter->path); |
| adapter->path = NULL; |
| return -EINVAL; |
| } |
| |
| if (adapters == NULL) |
| adapter->is_default = true; |
| |
| adapters = g_slist_append(adapters, adapter); |
| |
| agent = agent_get(NULL); |
| if (agent) { |
| uint8_t io_cap = agent_get_io_capability(agent); |
| adapter_set_io_capability(adapter, io_cap); |
| agent_unref(agent); |
| } |
| |
| /* Don't start GATT database and advertising managers on |
| * non-LE controllers. |
| */ |
| if (!(adapter->supported_settings & MGMT_SETTING_LE) || |
| main_opts.mode == BT_MODE_BREDR) |
| goto load; |
| |
| adapter->database = btd_gatt_database_new(adapter); |
| if (!adapter->database) { |
| btd_error(adapter->dev_id, |
| "Failed to create GATT database for adapter"); |
| adapters = g_slist_remove(adapters, adapter); |
| return -EINVAL; |
| } |
| |
| adapter->adv_manager = btd_adv_manager_new(adapter, adapter->mgmt); |
| |
| db = btd_gatt_database_get_db(adapter->database); |
| adapter->db_id = gatt_db_register(db, services_modified, |
| services_modified, |
| adapter, NULL); |
| |
| load: |
| load_config(adapter); |
| fix_storage(adapter); |
| load_drivers(adapter); |
| btd_profile_foreach(probe_profile, adapter); |
| clear_blocked(adapter); |
| load_devices(adapter); |
| |
| /* retrieve the active connections: address the scenario where |
| * the are active connections before the daemon've started */ |
| if (adapter->current_settings & MGMT_SETTING_POWERED) |
| load_connections(adapter); |
| |
| adapter->initialized = TRUE; |
| |
| if (main_opts.did_source) { |
| /* DeviceID record is added by sdpd-server before any other |
| * record is registered. */ |
| adapter_service_insert(adapter, sdp_record_find(0x10000)); |
| set_did(adapter, main_opts.did_vendor, main_opts.did_product, |
| main_opts.did_version, main_opts.did_source); |
| } |
| |
| DBG("Adapter %s registered", adapter->path); |
| |
| return 0; |
| } |
| |
| static int adapter_unregister(struct btd_adapter *adapter) |
| { |
| DBG("Unregister path: %s", adapter->path); |
| |
| adapters = g_slist_remove(adapters, adapter); |
| |
| if (adapter->is_default && adapters != NULL) { |
| struct btd_adapter *new_default; |
| |
| new_default = adapter_find_by_id(hci_get_route(NULL)); |
| if (new_default == NULL) |
| new_default = adapters->data; |
| |
| new_default->is_default = true; |
| } |
| |
| adapter_list = g_list_remove(adapter_list, adapter); |
| |
| adapter_remove(adapter); |
| btd_adapter_unref(adapter); |
| |
| return 0; |
| } |
| |
| static void disconnected_callback(uint16_t index, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_ev_device_disconnected *ev = param; |
| struct btd_adapter *adapter = user_data; |
| uint8_t reason; |
| |
| if (length < sizeof(struct mgmt_addr_info)) { |
| btd_error(adapter->dev_id, |
| "Too small device disconnected event"); |
| return; |
| } |
| |
| if (length < sizeof(*ev)) |
| reason = MGMT_DEV_DISCONN_UNKNOWN; |
| else |
| reason = ev->reason; |
| |
| dev_disconnected(adapter, &ev->addr, reason); |
| } |
| |
| static void connected_callback(uint16_t index, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_ev_device_connected *ev = param; |
| struct btd_adapter *adapter = user_data; |
| struct btd_device *device; |
| struct eir_data eir_data; |
| uint16_t eir_len; |
| char addr[18]; |
| bool name_known; |
| |
| if (length < sizeof(*ev)) { |
| btd_error(adapter->dev_id, "Too small device connected event"); |
| return; |
| } |
| |
| eir_len = btohs(ev->eir_len); |
| if (length < sizeof(*ev) + eir_len) { |
| btd_error(adapter->dev_id, "Too small device connected event"); |
| return; |
| } |
| |
| ba2str(&ev->addr.bdaddr, addr); |
| |
| DBG("hci%u device %s connected eir_len %u", index, addr, eir_len); |
| |
| device = btd_adapter_get_device(adapter, &ev->addr.bdaddr, |
| ev->addr.type); |
| if (!device) { |
| btd_error(adapter->dev_id, |
| "Unable to get device object for %s", addr); |
| return; |
| } |
| |
| memset(&eir_data, 0, sizeof(eir_data)); |
| if (eir_len > 0) |
| eir_parse(&eir_data, ev->eir, eir_len); |
| |
| if (eir_data.class != 0) |
| device_set_class(device, eir_data.class); |
| |
| adapter_add_connection(adapter, device, ev->addr.type); |
| |
| name_known = device_name_known(device); |
| |
| if (eir_data.name && (eir_data.name_complete || !name_known)) { |
| device_store_cached_name(device, eir_data.name); |
| btd_device_device_set_name(device, eir_data.name); |
| } |
| |
| if (eir_data.msd_list) |
| adapter_msd_notify(adapter, device, eir_data.msd_list); |
| |
| eir_data_free(&eir_data); |
| } |
| |
| static void device_blocked_callback(uint16_t index, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_ev_device_blocked *ev = param; |
| struct btd_adapter *adapter = user_data; |
| struct btd_device *device; |
| char addr[18]; |
| |
| if (length < sizeof(*ev)) { |
| btd_error(adapter->dev_id, "Too small device blocked event"); |
| return; |
| } |
| |
| ba2str(&ev->addr.bdaddr, addr); |
| DBG("hci%u %s blocked", index, addr); |
| |
| device = btd_adapter_find_device(adapter, &ev->addr.bdaddr, |
| ev->addr.type); |
| if (device) |
| device_block(device, TRUE); |
| } |
| |
| static void device_unblocked_callback(uint16_t index, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_ev_device_unblocked *ev = param; |
| struct btd_adapter *adapter = user_data; |
| struct btd_device *device; |
| char addr[18]; |
| |
| if (length < sizeof(*ev)) { |
| btd_error(adapter->dev_id, "Too small device unblocked event"); |
| return; |
| } |
| |
| ba2str(&ev->addr.bdaddr, addr); |
| DBG("hci%u %s unblocked", index, addr); |
| |
| device = btd_adapter_find_device(adapter, &ev->addr.bdaddr, |
| ev->addr.type); |
| if (device) |
| device_unblock(device, FALSE, TRUE); |
| } |
| |
| static void conn_fail_notify(struct btd_device *dev, uint8_t status) |
| { |
| GSList *l; |
| |
| for (l = conn_fail_list; l; l = g_slist_next(l)) { |
| btd_conn_fail_cb conn_fail_cb = l->data; |
| conn_fail_cb(dev, status); |
| } |
| } |
| |
| void btd_add_conn_fail_cb(btd_conn_fail_cb func) |
| { |
| conn_fail_list = g_slist_append(conn_fail_list, func); |
| } |
| |
| void btd_remove_conn_fail_cb(btd_conn_fail_cb func) |
| { |
| conn_fail_list = g_slist_remove(conn_fail_list, func); |
| } |
| |
| static void connect_failed_callback(uint16_t index, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_ev_connect_failed *ev = param; |
| struct btd_adapter *adapter = user_data; |
| struct btd_device *device; |
| char addr[18]; |
| |
| if (length < sizeof(*ev)) { |
| btd_error(adapter->dev_id, "Too small connect failed event"); |
| return; |
| } |
| |
| ba2str(&ev->addr.bdaddr, addr); |
| |
| DBG("hci%u %s status %u", index, addr, ev->status); |
| |
| device = btd_adapter_find_device(adapter, &ev->addr.bdaddr, |
| ev->addr.type); |
| if (device) { |
| conn_fail_notify(device, ev->status); |
| |
| /* If the device is in a bonding process cancel any auth request |
| * sent to the agent before proceeding, but keep the bonding |
| * request structure. */ |
| if (device_is_bonding(device, NULL)) |
| device_cancel_authentication(device, FALSE); |
| } |
| |
| /* In the case of security mode 3 devices */ |
| bonding_attempt_complete(adapter, &ev->addr.bdaddr, ev->addr.type, |
| ev->status); |
| |
| /* If the device is scheduled to retry the bonding wait until the retry |
| * happens. In other case, proceed with cancel the bondig. |
| */ |
| if (device && device_is_bonding(device, NULL) |
| && !device_is_retrying(device)) { |
| device_cancel_authentication(device, TRUE); |
| device_bonding_failed(device, ev->status); |
| } |
| |
| /* In the case the bonding was canceled or did exists, remove the device |
| * when it is temporary. */ |
| if (device && !device_is_bonding(device, NULL) |
| && device_is_temporary(device)) |
| btd_adapter_remove_device(adapter, device); |
| } |
| |
| static void remove_keys(struct btd_adapter *adapter, |
| struct btd_device *device, uint8_t type) |
| { |
| char device_addr[18]; |
| char filename[PATH_MAX]; |
| GKeyFile *key_file; |
| gsize length = 0; |
| char *str; |
| |
| ba2str(device_get_address(device), device_addr); |
| |
| snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", |
| btd_adapter_get_storage_dir(adapter), device_addr); |
| key_file = g_key_file_new(); |
| g_key_file_load_from_file(key_file, filename, 0, NULL); |
| |
| if (type == BDADDR_BREDR) { |
| g_key_file_remove_group(key_file, "LinkKey", NULL); |
| } else { |
| g_key_file_remove_group(key_file, "LongTermKey", NULL); |
| g_key_file_remove_group(key_file, "LocalSignatureKey", NULL); |
| g_key_file_remove_group(key_file, "RemoteSignatureKey", NULL); |
| g_key_file_remove_group(key_file, "IdentityResolvingKey", NULL); |
| } |
| |
| 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 unpaired_callback(uint16_t index, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_ev_device_unpaired *ev = param; |
| struct btd_adapter *adapter = user_data; |
| struct btd_device *device; |
| char addr[18]; |
| |
| if (length < sizeof(*ev)) { |
| btd_error(adapter->dev_id, "Too small device unpaired event"); |
| return; |
| } |
| |
| ba2str(&ev->addr.bdaddr, addr); |
| |
| DBG("hci%u addr %s", index, addr); |
| |
| device = btd_adapter_find_device(adapter, &ev->addr.bdaddr, |
| ev->addr.type); |
| if (!device) { |
| btd_warn(adapter->dev_id, |
| "No device object for unpaired device %s", addr); |
| return; |
| } |
| |
| remove_keys(adapter, device, ev->addr.type); |
| device_set_unpaired(device, ev->addr.type); |
| } |
| |
| static void clear_devices_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| if (status != MGMT_STATUS_SUCCESS) { |
| error("Failed to clear devices: %s (0x%02x)", |
| mgmt_errstr(status), status); |
| return; |
| } |
| } |
| |
| static int clear_devices(struct btd_adapter *adapter) |
| { |
| struct mgmt_cp_remove_device cp; |
| |
| if (!kernel_conn_control) |
| return 0; |
| |
| memset(&cp, 0, sizeof(cp)); |
| |
| DBG("sending clear devices command for index %u", adapter->dev_id); |
| |
| if (mgmt_send(adapter->mgmt, MGMT_OP_REMOVE_DEVICE, |
| adapter->dev_id, sizeof(cp), &cp, |
| clear_devices_complete, adapter, NULL) > 0) |
| return 0; |
| |
| btd_error(adapter->dev_id, "Failed to clear devices for index %u", |
| adapter->dev_id); |
| |
| return -EIO; |
| } |
| |
| static bool get_static_addr(struct btd_adapter *adapter) |
| { |
| struct bt_crypto *crypto; |
| GKeyFile *file; |
| char **addrs; |
| char mfg[7]; |
| char *str; |
| bool ret; |
| gsize len, i; |
| |
| snprintf(mfg, sizeof(mfg), "0x%04x", adapter->manufacturer); |
| |
| file = g_key_file_new(); |
| g_key_file_load_from_file(file, STORAGEDIR "/addresses", 0, NULL); |
| addrs = g_key_file_get_string_list(file, "Static", mfg, &len, NULL); |
| if (addrs) { |
| for (i = 0; i < len; i++) { |
| bdaddr_t addr; |
| |
| str2ba(addrs[i], &addr); |
| if (adapter_find(&addr)) |
| continue; |
| |
| /* Usable address found in list */ |
| bacpy(&adapter->bdaddr, &addr); |
| adapter->bdaddr_type = BDADDR_LE_RANDOM; |
| ret = true; |
| goto done; |
| } |
| |
| len++; |
| addrs = g_renew(char *, addrs, len + 1); |
| } else { |
| len = 1; |
| addrs = g_new(char *, len + 1); |
| } |
| |
| /* Initialize slot for new address */ |
| addrs[len - 1] = g_malloc(18); |
| addrs[len] = NULL; |
| |
| crypto = bt_crypto_new(); |
| if (!crypto) { |
| error("Failed to open crypto"); |
| ret = false; |
| goto done; |
| } |
| |
| ret = bt_crypto_random_bytes(crypto, &adapter->bdaddr, |
| sizeof(adapter->bdaddr)); |
| if (!ret) { |
| error("Failed to generate static address"); |
| bt_crypto_unref(crypto); |
| goto done; |
| } |
| |
| bt_crypto_unref(crypto); |
| |
| adapter->bdaddr.b[5] |= 0xc0; |
| adapter->bdaddr_type = BDADDR_LE_RANDOM; |
| |
| ba2str(&adapter->bdaddr, addrs[len - 1]); |
| |
| g_key_file_set_string_list(file, "Static", mfg, |
| (const char **)addrs, len); |
| |
| str = g_key_file_to_data(file, &len, NULL); |
| g_file_set_contents(STORAGEDIR "/addresses", str, len, NULL); |
| g_free(str); |
| |
| ret = true; |
| |
| done: |
| g_key_file_free(file); |
| g_strfreev(addrs); |
| |
| return ret; |
| } |
| |
| static bool set_static_addr(struct btd_adapter *adapter) |
| { |
| struct mgmt_cp_set_static_address cp; |
| |
| /* dual-mode adapters must have a public address */ |
| if (adapter->supported_settings & MGMT_SETTING_BREDR) |
| return false; |
| |
| if (!(adapter->supported_settings & MGMT_SETTING_LE)) |
| return false; |
| |
| DBG("Setting static address"); |
| |
| if (!get_static_addr(adapter)) |
| return false; |
| |
| bacpy(&cp.bdaddr, &adapter->bdaddr); |
| if (mgmt_send(adapter->mgmt, MGMT_OP_SET_STATIC_ADDRESS, |
| adapter->dev_id, sizeof(cp), &cp, |
| NULL, NULL, NULL) > 0) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static void read_info_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| struct btd_adapter *adapter = user_data; |
| const struct mgmt_rp_read_info *rp = param; |
| uint32_t missing_settings; |
| int err; |
| |
| DBG("index %u status 0x%02x", adapter->dev_id, status); |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| btd_error(adapter->dev_id, |
| "Failed to read info for index %u: %s (0x%02x)", |
| adapter->dev_id, mgmt_errstr(status), status); |
| goto failed; |
| } |
| |
| if (length < sizeof(*rp)) { |
| btd_error(adapter->dev_id, |
| "Too small read info complete response"); |
| goto failed; |
| } |
| |
| /* |
| * Store controller information for class of device, device |
| * name, short name and settings. |
| * |
| * During the lifetime of the controller these will be updated by |
| * events and the information is required to keep the current |
| * state of the controller. |
| */ |
| adapter->dev_class = rp->dev_class[0] | (rp->dev_class[1] << 8) | |
| (rp->dev_class[2] << 16); |
| adapter->name = g_strdup((const char *) rp->name); |
| adapter->short_name = g_strdup((const char *) rp->short_name); |
| |
| adapter->manufacturer = btohs(rp->manufacturer); |
| |
| adapter->supported_settings = btohl(rp->supported_settings); |
| adapter->current_settings = btohl(rp->current_settings); |
| |
| clear_uuids(adapter); |
| clear_devices(adapter); |
| |
| if (bacmp(&rp->bdaddr, BDADDR_ANY) == 0) { |
| if (!set_static_addr(adapter)) { |
| btd_error(adapter->dev_id, |
| "No Bluetooth address for index %u", |
| adapter->dev_id); |
| goto failed; |
| } |
| } else { |
| bacpy(&adapter->bdaddr, &rp->bdaddr); |
| if (!(adapter->supported_settings & MGMT_SETTING_LE)) |
| adapter->bdaddr_type = BDADDR_BREDR; |
| else |
| adapter->bdaddr_type = BDADDR_LE_PUBLIC; |
| } |
| |
| missing_settings = adapter->current_settings ^ |
| adapter->supported_settings; |
| |
| switch (main_opts.mode) { |
| case BT_MODE_DUAL: |
| if (missing_settings & MGMT_SETTING_SSP) |
| set_mode(adapter, MGMT_OP_SET_SSP, 0x01); |
| if (missing_settings & MGMT_SETTING_LE) |
| set_mode(adapter, MGMT_OP_SET_LE, 0x01); |
| if (missing_settings & MGMT_SETTING_BREDR) |
| set_mode(adapter, MGMT_OP_SET_BREDR, 0x01); |
| break; |
| case BT_MODE_BREDR: |
| if (!(adapter->supported_settings & MGMT_SETTING_BREDR)) { |
| btd_error(adapter->dev_id, |
| "Ignoring adapter withouth BR/EDR support"); |
| goto failed; |
| } |
| |
| if (missing_settings & MGMT_SETTING_SSP) |
| set_mode(adapter, MGMT_OP_SET_SSP, 0x01); |
| if (missing_settings & MGMT_SETTING_BREDR) |
| set_mode(adapter, MGMT_OP_SET_BREDR, 0x01); |
| if (adapter->current_settings & MGMT_SETTING_LE) |
| set_mode(adapter, MGMT_OP_SET_LE, 0x00); |
| break; |
| case BT_MODE_LE: |
| if (!(adapter->supported_settings & MGMT_SETTING_LE)) { |
| btd_error(adapter->dev_id, |
| "Ignoring adapter withouth LE support"); |
| goto failed; |
| } |
| |
| if (missing_settings & MGMT_SETTING_LE) |
| set_mode(adapter, MGMT_OP_SET_LE, 0x01); |
| if (adapter->current_settings & MGMT_SETTING_BREDR) |
| set_mode(adapter, MGMT_OP_SET_BREDR, 0x00); |
| break; |
| } |
| |
| if (missing_settings & MGMT_SETTING_SECURE_CONN) |
| set_mode(adapter, MGMT_OP_SET_SECURE_CONN, 0x01); |
| |
| if (adapter->supported_settings & MGMT_SETTING_PRIVACY) |
| set_privacy(adapter, main_opts.privacy); |
| |
| if (main_opts.fast_conn && |
| (missing_settings & MGMT_SETTING_FAST_CONNECTABLE)) |
| set_mode(adapter, MGMT_OP_SET_FAST_CONNECTABLE, 0x01); |
| |
| err = adapter_register(adapter); |
| if (err < 0) { |
| btd_error(adapter->dev_id, "Unable to register new adapter"); |
| goto failed; |
| } |
| |
| /* |
| * Register all event notification handlers for controller. |
| * |
| * The handlers are registered after a succcesful read of the |
| * controller info. From now on they can track updates and |
| * notifications. |
| */ |
| mgmt_register(adapter->mgmt, MGMT_EV_NEW_SETTINGS, adapter->dev_id, |
| new_settings_callback, adapter, NULL); |
| |
| mgmt_register(adapter->mgmt, MGMT_EV_CLASS_OF_DEV_CHANGED, |
| adapter->dev_id, |
| dev_class_changed_callback, |
| adapter, NULL); |
| mgmt_register(adapter->mgmt, MGMT_EV_LOCAL_NAME_CHANGED, |
| adapter->dev_id, |
| local_name_changed_callback, |
| adapter, NULL); |
| |
| mgmt_register(adapter->mgmt, MGMT_EV_DISCOVERING, |
| adapter->dev_id, |
| discovering_callback, |
| adapter, NULL); |
| |
| mgmt_register(adapter->mgmt, MGMT_EV_DEVICE_FOUND, |
| adapter->dev_id, |
| device_found_callback, |
| adapter, NULL); |
| |
| mgmt_register(adapter->mgmt, MGMT_EV_DEVICE_DISCONNECTED, |
| adapter->dev_id, |
| disconnected_callback, |
| adapter, NULL); |
| |
| mgmt_register(adapter->mgmt, MGMT_EV_DEVICE_CONNECTED, |
| adapter->dev_id, |
| connected_callback, |
| adapter, NULL); |
| |
| mgmt_register(adapter->mgmt, MGMT_EV_CONNECT_FAILED, |
| adapter->dev_id, |
| connect_failed_callback, |
| adapter, NULL); |
| |
| mgmt_register(adapter->mgmt, MGMT_EV_DEVICE_UNPAIRED, |
| adapter->dev_id, |
| unpaired_callback, |
| adapter, NULL); |
| |
| mgmt_register(adapter->mgmt, MGMT_EV_AUTH_FAILED, |
| adapter->dev_id, |
| auth_failed_callback, |
| adapter, NULL); |
| |
| mgmt_register(adapter->mgmt, MGMT_EV_NEW_LINK_KEY, |
| adapter->dev_id, |
| new_link_key_callback, |
| adapter, NULL); |
| |
| mgmt_register(adapter->mgmt, MGMT_EV_NEW_LONG_TERM_KEY, |
| adapter->dev_id, |
| new_long_term_key_callback, |
| adapter, NULL); |
| |
| mgmt_register(adapter->mgmt, MGMT_EV_NEW_CSRK, |
| adapter->dev_id, |
| new_csrk_callback, |
| adapter, NULL); |
| |
| mgmt_register(adapter->mgmt, MGMT_EV_NEW_IRK, |
| adapter->dev_id, |
| new_irk_callback, |
| adapter, NULL); |
| |
| mgmt_register(adapter->mgmt, MGMT_EV_NEW_CONN_PARAM, |
| adapter->dev_id, |
| new_conn_param, |
| adapter, NULL); |
| |
| mgmt_register(adapter->mgmt, MGMT_EV_DEVICE_BLOCKED, |
| adapter->dev_id, |
| device_blocked_callback, |
| adapter, NULL); |
| mgmt_register(adapter->mgmt, MGMT_EV_DEVICE_UNBLOCKED, |
| adapter->dev_id, |
| device_unblocked_callback, |
| adapter, NULL); |
| |
| mgmt_register(adapter->mgmt, MGMT_EV_PIN_CODE_REQUEST, |
| adapter->dev_id, |
| pin_code_request_callback, |
| adapter, NULL); |
| |
| mgmt_register(adapter->mgmt, MGMT_EV_USER_CONFIRM_REQUEST, |
| adapter->dev_id, |
| user_confirm_request_callback, |
| adapter, NULL); |
| |
| mgmt_register(adapter->mgmt, MGMT_EV_USER_PASSKEY_REQUEST, |
| adapter->dev_id, |
| user_passkey_request_callback, |
| adapter, NULL); |
| |
| mgmt_register(adapter->mgmt, MGMT_EV_PASSKEY_NOTIFY, |
| adapter->dev_id, |
| user_passkey_notify_callback, |
| adapter, NULL); |
| |
| set_dev_class(adapter); |
| |
| set_name(adapter, btd_adapter_get_name(adapter)); |
| |
| if (!(adapter->current_settings & MGMT_SETTING_BONDABLE)) |
| set_mode(adapter, MGMT_OP_SET_BONDABLE, 0x01); |
| |
| if (!kernel_conn_control) |
| set_mode(adapter, MGMT_OP_SET_CONNECTABLE, 0x01); |
| else if (adapter->current_settings & MGMT_SETTING_CONNECTABLE) |
| set_mode(adapter, MGMT_OP_SET_CONNECTABLE, 0x00); |
| |
| if (adapter->stored_discoverable && !adapter->discoverable_timeout) |
| set_discoverable(adapter, 0x01, 0); |
| |
| if (adapter->current_settings & MGMT_SETTING_POWERED) |
| adapter_start(adapter); |
| |
| return; |
| |
| failed: |
| /* |
| * Remove adapter from list in case of a failure. |
| * |
| * Leaving an adapter structure around for a controller that can |
| * not be initilized makes no sense at the moment. |
| * |
| * This is a simplification to avoid constant checks if the |
| * adapter is ready to do anything. |
| */ |
| adapter_list = g_list_remove(adapter_list, adapter); |
| |
| btd_adapter_unref(adapter); |
| } |
| |
| static void index_added(uint16_t index, uint16_t length, const void *param, |
| void *user_data) |
| { |
| struct btd_adapter *adapter; |
| |
| DBG("index %u", index); |
| |
| adapter = btd_adapter_lookup(index); |
| if (adapter) { |
| btd_warn(adapter->dev_id, |
| "Ignoring index added for an already existing adapter"); |
| return; |
| } |
| |
| adapter = btd_adapter_new(index); |
| if (!adapter) { |
| btd_error(index, |
| "Unable to create new adapter for index %u", index); |
| return; |
| } |
| |
| /* |
| * Protect against potential two executions of read controller info. |
| * |
| * In case the start of the daemon and the action of adding a new |
| * controller coincide this function might be called twice. |
| * |
| * To avoid the double execution of reading the controller info, |
| * add the adapter already to the list. If an adapter is already |
| * present, the second notification will cause a warning. If the |
| * command fails the adapter is removed from the list again. |
| */ |
| adapter_list = g_list_append(adapter_list, adapter); |
| |
| DBG("sending read info command for index %u", index); |
| |
| if (mgmt_send(mgmt_master, MGMT_OP_READ_INFO, index, 0, NULL, |
| read_info_complete, adapter, NULL) > 0) |
| return; |
| |
| btd_error(adapter->dev_id, |
| "Failed to read controller info for index %u", index); |
| |
| adapter_list = g_list_remove(adapter_list, adapter); |
| |
| btd_adapter_unref(adapter); |
| } |
| |
| static void index_removed(uint16_t index, uint16_t length, const void *param, |
| void *user_data) |
| { |
| struct btd_adapter *adapter; |
| |
| DBG("index %u", index); |
| |
| adapter = btd_adapter_lookup(index); |
| if (!adapter) { |
| warn("Ignoring index removal for a non-existent adapter"); |
| return; |
| } |
| |
| adapter_unregister(adapter); |
| } |
| |
| static void read_index_list_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_rp_read_index_list *rp = param; |
| uint16_t num; |
| int i; |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| error("Failed to read index list: %s (0x%02x)", |
| mgmt_errstr(status), status); |
| return; |
| } |
| |
| if (length < sizeof(*rp)) { |
| error("Wrong size of read index list response"); |
| return; |
| } |
| |
| num = btohs(rp->num_controllers); |
| |
| DBG("Number of controllers: %d", num); |
| |
| if (num * sizeof(uint16_t) + sizeof(*rp) != length) { |
| error("Incorrect packet size for index list response"); |
| return; |
| } |
| |
| for (i = 0; i < num; i++) { |
| uint16_t index; |
| |
| index = btohs(rp->index[i]); |
| |
| DBG("Found index %u", index); |
| |
| /* |
| * Pretend to be index added event notification. |
| * |
| * It is safe to just trigger the procedure for index |
| * added notification. It does check against itself. |
| */ |
| index_added(index, 0, NULL, NULL); |
| } |
| } |
| |
| static void read_commands_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_rp_read_commands *rp = param; |
| uint16_t num_commands, num_events; |
| const uint16_t *opcode; |
| size_t expected_len; |
| int i; |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| error("Failed to read supported commands: %s (0x%02x)", |
| mgmt_errstr(status), status); |
| return; |
| } |
| |
| if (length < sizeof(*rp)) { |
| error("Wrong size of read commands response"); |
| return; |
| } |
| |
| num_commands = btohs(rp->num_commands); |
| num_events = btohs(rp->num_events); |
| |
| DBG("Number of commands: %d", num_commands); |
| DBG("Number of events: %d", num_events); |
| |
| expected_len = sizeof(*rp) + num_commands * sizeof(uint16_t) + |
| num_events * sizeof(uint16_t); |
| |
| if (length < expected_len) { |
| error("Too small reply for supported commands: (%u != %zu)", |
| length, expected_len); |
| return; |
| } |
| |
| opcode = rp->opcodes; |
| |
| for (i = 0; i < num_commands; i++) { |
| uint16_t op = get_le16(opcode++); |
| |
| if (op == MGMT_OP_ADD_DEVICE) { |
| DBG("enabling kernel-side connection control"); |
| kernel_conn_control = true; |
| } |
| } |
| } |
| |
| static void read_version_complete(uint8_t status, uint16_t length, |
| const void *param, void *user_data) |
| { |
| const struct mgmt_rp_read_version *rp = param; |
| |
| if (status != MGMT_STATUS_SUCCESS) { |
| error("Failed to read version information: %s (0x%02x)", |
| mgmt_errstr(status), status); |
| return; |
| } |
| |
| if (length < sizeof(*rp)) { |
| error("Wrong size of read version response"); |
| return; |
| } |
| |
| mgmt_version = rp->version; |
| mgmt_revision = btohs(rp->revision); |
| |
| info("Bluetooth management interface %u.%u initialized", |
| mgmt_version, mgmt_revision); |
| |
| if (mgmt_version < 1) { |
| error("Version 1.0 or later of management interface required"); |
| abort(); |
| } |
| |
| DBG("sending read supported commands command"); |
| |
| /* |
| * It is irrelevant if this command succeeds or fails. In case of |
| * failure safe settings are assumed. |
| */ |
| mgmt_send(mgmt_master, MGMT_OP_READ_COMMANDS, |
| MGMT_INDEX_NONE, 0, NULL, |
| read_commands_complete, NULL, NULL); |
| |
| mgmt_register(mgmt_master, MGMT_EV_INDEX_ADDED, MGMT_INDEX_NONE, |
| index_added, NULL, NULL); |
| mgmt_register(mgmt_master, MGMT_EV_INDEX_REMOVED, MGMT_INDEX_NONE, |
| index_removed, NULL, NULL); |
| |
| DBG("sending read index list command"); |
| |
| if (mgmt_send(mgmt_master, MGMT_OP_READ_INDEX_LIST, |
| MGMT_INDEX_NONE, 0, NULL, |
| read_index_list_complete, NULL, NULL) > 0) |
| return; |
| |
| error("Failed to read controller index list"); |
| } |
| |
| static void mgmt_debug(const char *str, void *user_data) |
| { |
| const char *prefix = user_data; |
| |
| info("%s%s", prefix, str); |
| } |
| |
| int adapter_init(void) |
| { |
| dbus_conn = btd_get_dbus_connection(); |
| |
| mgmt_master = mgmt_new_default(); |
| if (!mgmt_master) { |
| error("Failed to access management interface"); |
| return -EIO; |
| } |
| |
| if (getenv("MGMT_DEBUG")) |
| mgmt_set_debug(mgmt_master, mgmt_debug, "mgmt: ", NULL); |
| |
| DBG("sending read version command"); |
| |
| if (mgmt_send(mgmt_master, MGMT_OP_READ_VERSION, |
| MGMT_INDEX_NONE, 0, NULL, |
| read_version_complete, NULL, NULL) > 0) |
| return 0; |
| |
| error("Failed to read management version information"); |
| |
| return -EIO; |
| } |
| |
| void adapter_cleanup(void) |
| { |
| g_list_free(adapter_list); |
| |
| while (adapters) { |
| struct btd_adapter *adapter = adapters->data; |
| |
| adapter_remove(adapter); |
| adapters = g_slist_remove(adapters, adapter); |
| btd_adapter_unref(adapter); |
| } |
| |
| /* |
| * In case there is another reference active, clear out |
| * registered handlers for index added and index removed. |
| * |
| * This is just an extra precaution to be safe, and in |
| * reality should not make a difference. |
| */ |
| mgmt_unregister_index(mgmt_master, MGMT_INDEX_NONE); |
| |
| /* |
| * In case there is another reference active, cancel |
| * all pending global commands. |
| * |
| * This is just an extra precaution to avoid callbacks |
| * that potentially then could leak memory or access |
| * an invalid structure. |
| */ |
| mgmt_cancel_index(mgmt_master, MGMT_INDEX_NONE); |
| |
| mgmt_unref(mgmt_master); |
| mgmt_master = NULL; |
| |
| dbus_conn = NULL; |
| } |
| |
| void adapter_shutdown(void) |
| { |
| GList *list; |
| |
| DBG(""); |
| |
| powering_down = true; |
| |
| for (list = g_list_first(adapter_list); list; |
| list = g_list_next(list)) { |
| struct btd_adapter *adapter = list->data; |
| |
| if (!(adapter->current_settings & MGMT_SETTING_POWERED)) |
| continue; |
| |
| clear_discoverable(adapter); |
| set_mode(adapter, MGMT_OP_SET_POWERED, 0x00); |
| |
| adapter_remaining++; |
| } |
| |
| if (!adapter_remaining) |
| btd_exit(); |
| } |
| |
| /* |
| * Check if workaround for broken ATT server socket behavior is needed |
| * where we need to connect an ATT client socket before pairing to get |
| * early access to the ATT channel. |
| */ |
| bool btd_le_connect_before_pairing(void) |
| { |
| if (MGMT_VERSION(mgmt_version, mgmt_revision) < MGMT_VERSION(1, 4)) |
| return true; |
| |
| return false; |
| } |