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