blob: 11f939445921d302bcc9aec44fcd4a4c95ccee29 [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 "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;
};
struct watch_client {
struct btd_adapter *adapter;
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 */
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 */
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_advertising *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 */
};
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;
}
static void store_adapter_info(struct btd_adapter *adapter)
{
GKeyFile *key_file;
char filename[PATH_MAX];
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);
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_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_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)
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;
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 discovery return parameters");
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 (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_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 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->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);
}
}
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_and_tx_power(gpointer a)
{
struct btd_device *dev = a;
device_set_rssi(dev, 0);
device_set_tx_power(dev, 127);
}
static void discovery_cleanup(struct btd_adapter *adapter)
{
g_slist_free_full(adapter->discovery_found,
invalidate_rssi_and_tx_power);
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_device_is_connected(dev))
btd_adapter_remove_device(adapter, dev);
}
return FALSE;
}
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 void 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;
}
/*
* 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;
}
g_free(adapter->current_discovery_filter);
adapter->current_discovery_filter = sd_cp;
trigger_start_discovery(adapter, 0);
}
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->set_filter_list = g_slist_remove(adapter->set_filter_list,
client);
adapter->discovery_list = g_slist_remove(adapter->discovery_list,
client);
if (client->discovery_filter) {
free_discovery_filter(client->discovery_filter);
client->discovery_filter = NULL;
}
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->set_filter_list = g_slist_remove(adapter->set_filter_list,
client);
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) {
update_discovery_filter(adapter);
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);
}
/*
* 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;
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) {
adapter->set_filter_list = g_slist_remove(
adapter->set_filter_list, client);
adapter->discovery_list = g_slist_prepend(
adapter->discovery_list, client);
update_discovery_filter(adapter);
return dbus_message_new_method_return(msg);
}
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,
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.
*/
update_discovery_filter(adapter);
return dbus_message_new_method_return(msg);
}
static bool parse_uuids(DBusMessageIter *value, GSList **uuids)
{
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));
*uuids = g_slist_prepend(*uuids, strdup(uuidstr));
dbus_message_iter_next(&arriter);
}
return true;
}
static bool parse_rssi(DBusMessageIter *value, int16_t *rssi)
{
if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_INT16)
return false;
dbus_message_iter_get_basic(value, rssi);
/* -127 <= RSSI <= +20 (spec V4.2 [Vol 2, Part E] 7.7.65.2) */
if (*rssi > 20 || *rssi < -127)
return false;
return true;
}
static bool parse_pathloss(DBusMessageIter *value, uint16_t *pathloss)
{
if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_UINT16)
return false;
dbus_message_iter_get_basic(value, pathloss);
/* pathloss filter must be smaller that PATHLOSS_MAX */
if (*pathloss > PATHLOSS_MAX)
return false;
return true;
}
static bool parse_transport(DBusMessageIter *value, uint8_t *transport)
{
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"))
*transport = SCAN_TYPE_BREDR;
else if (!strcmp(transport_str, "le"))
*transport = SCAN_TYPE_LE;
else if (strcmp(transport_str, "auto"))
return false;
return true;
}
static bool parse_discovery_filter_entry(char *key, DBusMessageIter *value,
struct discovery_filter *filter)
{
if (!strcmp("UUIDs", key))
return parse_uuids(value, &filter->uuids);
if (!strcmp("RSSI", key))
return parse_rssi(value, &filter->rssi);
if (!strcmp("Pathloss", key))
return parse_pathloss(value, &filter->pathloss);
if (!strcmp("Transport", key))
return parse_transport(value, &filter->type);
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);
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",
(*filter)->type, (*filter)->rssi, (*filter)->pathloss);
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);
g_free(client->owner);
g_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,
discovery_destroy);
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 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);
if (adapter->discovery_list) {
update_discovery_filter(adapter);
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;
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 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:
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 ? : ""