blob: 6d7d61504c2c27a83f15bae2eb63c92d7880a444 [file] [log] [blame]
/*
*
* 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, &ltk->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, &ltk->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, &param->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(&param->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;
}