blob: 2241c4f6b74e86ebea63097855f351843ba8e292 [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
#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/sdp.h>
#include <bluetooth/sdp_lib.h>
#include <glib.h>
#include <dbus/dbus.h>
#include <gdbus.h>
#include "log.h"
#include "textfile.h"
#include "hcid.h"
#include "sdpd.h"
#include "manager.h"
#include "adapter.h"
#include "device.h"
#include "dbus-common.h"
#include "event.h"
#include "error.h"
#include "glib-helper.h"
#include "agent.h"
#include "storage.h"
#define ADV_TYPE_IND 0x00
#define ADV_TYPE_DIRECT_IND 0x01
#define IO_CAPABILITY_DISPLAYONLY 0x00
#define IO_CAPABILITY_DISPLAYYESNO 0x01
#define IO_CAPABILITY_KEYBOARDONLY 0x02
#define IO_CAPABILITY_NOINPUTNOOUTPUT 0x03
#define IO_CAPABILITY_INVALID 0xFF
#define check_address(address) bachk(address)
static DBusConnection *connection = NULL;
static GSList *adapter_drivers = NULL;
static GSList *ops_candidates = NULL;
const struct btd_adapter_ops *adapter_ops = NULL;
struct session_req {
struct btd_adapter *adapter;
DBusConnection *conn; /* Connection reference */
DBusMessage *msg; /* Unreplied message ref */
char *owner; /* Bus name of the owner */
guint id; /* Listener id */
uint8_t mode; /* Requested mode */
int refcount; /* Session refcount */
gboolean got_reply; /* Agent reply received */
};
struct service_auth {
service_auth_cb cb;
void *user_data;
struct btd_device *device;
struct btd_adapter *adapter;
};
struct btd_adapter {
uint16_t dev_id;
int up;
char *path; /* adapter object path */
bdaddr_t bdaddr; /* adapter Bluetooth Address */
uint32_t dev_class; /* Class of Device */
guint discov_timeout_id; /* discoverable timeout id */
guint stop_discov_id; /* stop inquiry/scanning id */
uint32_t discov_timeout; /* discoverable time(sec) */
guint pairable_timeout_id; /* pairable timeout id */
uint32_t pairable_timeout; /* pairable time(sec) */
uint8_t scan_mode; /* scan mode: SCAN_DISABLED, SCAN_PAGE,
* SCAN_INQUIRY */
uint8_t mode; /* off, connectable, discoverable,
* limited */
uint8_t global_mode; /* last valid global mode */
struct session_req *pending_mode;
int state; /* standard inq, periodic inq, name
* resolving, suspended discovery */
GSList *found_devices;
GSList *oor_devices; /* out of range device list */
struct agent *agent; /* For the new API */
guint auth_idle_id; /* Ongoing authorization */
GSList *connections; /* Connected devices */
GSList *devices; /* Devices structure pointers */
GSList *mode_sessions; /* Request Mode sessions */
GSList *disc_sessions; /* Discovery sessions */
guint scheduler_id; /* Scheduler handle */
sdp_list_t *services; /* Services associated to adapter */
struct hci_dev dev; /* hci info */
gboolean pairable; /* pairable state */
gboolean off_requested; /* DEVDOWN ioctl was called */
gint ref;
GSList *powered_callbacks;
gboolean name_stored;
};
static void adapter_set_pairable_timeout(struct btd_adapter *adapter,
guint interval);
static int found_device_cmp(const struct remote_dev_info *d1,
const struct remote_dev_info *d2)
{
int ret;
if (bacmp(&d2->bdaddr, BDADDR_ANY)) {
ret = bacmp(&d1->bdaddr, &d2->bdaddr);
if (ret)
return ret;
}
if (d2->name_status != NAME_ANY) {
ret = (d1->name_status - d2->name_status);
if (ret)
return ret;
}
return 0;
}
static void dev_info_free(struct remote_dev_info *dev)
{
g_free(dev->name);
g_free(dev->alias);
g_slist_foreach(dev->services, (GFunc) g_free, NULL);
g_slist_free(dev->services);
g_strfreev(dev->uuids);
g_free(dev);
}
/*
* Device name expansion
* %d - device id
*/
static char *expand_name(char *dst, int size, char *str, int dev_id)
{
register int sp, np, olen;
char *opt, buf[10];
if (!str || !dst)
return NULL;
sp = np = 0;
while (np < size - 1 && str[sp]) {
switch (str[sp]) {
case '%':
opt = NULL;
switch (str[sp+1]) {
case 'd':
sprintf(buf, "%d", dev_id);
opt = buf;
break;
case 'h':
opt = main_opts.host_name;
break;
case '%':
dst[np++] = str[sp++];
/* fall through */
default:
sp++;
continue;
}
if (opt) {
/* substitute */
olen = strlen(opt);
if (np + olen < size - 1)
memcpy(dst + np, opt, olen);
np += olen;
}
sp += 2;
continue;
case '\\':
sp++;
/* fall through */
default:
dst[np++] = str[sp++];
break;
}
}
dst[np] = '\0';
return dst;
}
int btd_adapter_set_class(struct btd_adapter *adapter, uint8_t major,
uint8_t minor)
{
return adapter_ops->set_dev_class(adapter->dev_id, major, minor);
}
static int pending_remote_name_cancel(struct btd_adapter *adapter)
{
struct remote_dev_info *dev, match;
int err;
/* find the pending remote name request */
memset(&match, 0, sizeof(struct remote_dev_info));
bacpy(&match.bdaddr, BDADDR_ANY);
match.name_status = NAME_REQUESTED;
dev = adapter_search_found_devices(adapter, &match);
if (!dev) /* no pending request */
return -ENODATA;
err = adapter_ops->cancel_resolve_name(adapter->dev_id, &dev->bdaddr);
if (err < 0)
error("Remote name cancel failed: %s(%d)",
strerror(errno), errno);
return err;
}
int adapter_resolve_names(struct btd_adapter *adapter)
{
struct remote_dev_info *dev, match;
int err;
memset(&match, 0, sizeof(struct remote_dev_info));
bacpy(&match.bdaddr, BDADDR_ANY);
match.name_status = NAME_REQUIRED;
dev = adapter_search_found_devices(adapter, &match);
if (!dev)
return -ENODATA;
/* send at least one request or return failed if the list is empty */
do {
/* flag to indicate the current remote name requested */
dev->name_status = NAME_REQUESTED;
err = adapter_ops->resolve_name(adapter->dev_id, &dev->bdaddr);
if (!err)
break;
error("Unable to send HCI remote name req: %s (%d)",
strerror(errno), errno);
/* if failed, request the next element */
/* remove the element from the list */
adapter_remove_found_device(adapter, &dev->bdaddr);
/* get the next element */
dev = adapter_search_found_devices(adapter, &match);
} while (dev);
return err;
}
static const char *mode2str(uint8_t mode)
{
switch(mode) {
case MODE_OFF:
return "off";
case MODE_CONNECTABLE:
return "connectable";
case MODE_DISCOVERABLE:
return "discoverable";
default:
return "unknown";
}
}
static uint8_t get_mode(const bdaddr_t *bdaddr, 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 if (strcasecmp("on", mode) == 0) {
char onmode[14], srcaddr[18];
ba2str(bdaddr, srcaddr);
if (read_on_mode(srcaddr, onmode, sizeof(onmode)) < 0)
return MODE_CONNECTABLE;
return get_mode(bdaddr, onmode);
} else
return MODE_UNKNOWN;
}
static void adapter_set_limited_discoverable(struct btd_adapter *adapter,
gboolean limited)
{
DBG("%s", limited ? "TRUE" : "FALSE");
adapter_ops->set_limited_discoverable(adapter->dev_id, limited);
}
static void adapter_remove_discov_timeout(struct btd_adapter *adapter)
{
if (!adapter)
return;
if (adapter->discov_timeout_id == 0)
return;
g_source_remove(adapter->discov_timeout_id);
adapter->discov_timeout_id = 0;
}
static gboolean discov_timeout_handler(gpointer user_data)
{
struct btd_adapter *adapter = user_data;
adapter->discov_timeout_id = 0;
adapter_ops->set_connectable(adapter->dev_id, TRUE);
return FALSE;
}
static void adapter_set_discov_timeout(struct btd_adapter *adapter,
guint interval)
{
if (adapter->discov_timeout_id) {
g_source_remove(adapter->discov_timeout_id);
adapter->discov_timeout_id = 0;
}
if (interval == 0) {
adapter_set_limited_discoverable(adapter, FALSE);
return;
}
/* Set limited discoverable if pairable and interval between 0 to 60
sec */
if (adapter->pairable && interval <= 60)
adapter_set_limited_discoverable(adapter, TRUE);
else
adapter_set_limited_discoverable(adapter, FALSE);
adapter->discov_timeout_id = g_timeout_add_seconds(interval,
discov_timeout_handler,
adapter);
}
static struct session_req *session_ref(struct session_req *req)
{
req->refcount++;
DBG("%p: ref=%d", req, req->refcount);
return req;
}
static struct session_req *create_session(struct btd_adapter *adapter,
DBusConnection *conn, DBusMessage *msg,
uint8_t mode, GDBusWatchFunction cb)
{
const char *sender = dbus_message_get_sender(msg);
struct session_req *req;
req = g_new0(struct session_req, 1);
req->adapter = adapter;
req->conn = dbus_connection_ref(conn);
req->msg = dbus_message_ref(msg);
req->mode = mode;
if (cb == NULL)
return session_ref(req);
req->owner = g_strdup(sender);
req->id = g_dbus_add_disconnect_watch(conn, sender, cb, req, NULL);
info("%s session %p with %s activated",
req->mode ? "Mode" : "Discovery", req, sender);
return session_ref(req);
}
static int adapter_set_mode(struct btd_adapter *adapter, uint8_t mode)
{
int err;
if (mode == MODE_CONNECTABLE)
err = adapter_ops->set_connectable(adapter->dev_id, TRUE);
else
err = adapter_ops->set_discoverable(adapter->dev_id, TRUE);
if (err < 0)
return err;
if (mode == MODE_CONNECTABLE)
return 0;
adapter_remove_discov_timeout(adapter);
if (adapter->discov_timeout)
adapter_set_discov_timeout(adapter, adapter->discov_timeout);
return 0;
}
static int set_mode(struct btd_adapter *adapter, uint8_t new_mode,
DBusMessage *msg)
{
int err;
const char *modestr;
gboolean discoverable;
if (adapter->pending_mode != NULL)
return -EALREADY;
discoverable = new_mode == MODE_DISCOVERABLE;
if (!adapter->up && new_mode != MODE_OFF) {
err = adapter_ops->power_on(adapter->dev_id, discoverable);
if (err < 0)
return err;
goto done;
}
if (adapter->up && new_mode == MODE_OFF) {
err = adapter_ops->power_off(adapter->dev_id);
if (err < 0)
return err;
adapter->off_requested = TRUE;
goto done;
}
if (new_mode == adapter->mode)
return 0;
err = adapter_set_mode(adapter, new_mode);
if (err < 0)
return err;
done:
modestr = mode2str(new_mode);
write_device_mode(&adapter->bdaddr, modestr);
DBG("%s", modestr);
if (msg != NULL)
/* Wait for mode change to reply */
adapter->pending_mode = create_session(adapter, connection,
msg, new_mode, NULL);
else
/* Nothing to reply just write the new mode */
adapter->mode = new_mode;
return 0;
}
static DBusMessage *set_discoverable(DBusConnection *conn, DBusMessage *msg,
gboolean discoverable, void *data)
{
struct btd_adapter *adapter = data;
uint8_t mode;
int err;
mode = discoverable ? MODE_DISCOVERABLE : MODE_CONNECTABLE;
if (mode == adapter->mode)
return dbus_message_new_method_return(msg);
err = set_mode(adapter, mode, msg);
if (err < 0)
return btd_error_failed(msg, strerror(-err));
return NULL;
}
static DBusMessage *set_powered(DBusConnection *conn, DBusMessage *msg,
gboolean powered, void *data)
{
struct btd_adapter *adapter = data;
uint8_t mode;
int err;
if (powered) {
mode = get_mode(&adapter->bdaddr, "on");
return set_discoverable(conn, msg, mode == MODE_DISCOVERABLE,
data);
}
mode = MODE_OFF;
if (mode == adapter->mode)
return dbus_message_new_method_return(msg);
err = set_mode(adapter, mode, msg);
if (err < 0)
return btd_error_failed(msg, strerror(-err));
return NULL;
}
static DBusMessage *set_pairable(DBusConnection *conn, DBusMessage *msg,
gboolean pairable, void *data)
{
struct btd_adapter *adapter = data;
int err;
if (adapter->scan_mode == SCAN_DISABLED)
return btd_error_not_ready(msg);
if (pairable == adapter->pairable)
goto done;
if (!(adapter->scan_mode & SCAN_INQUIRY))
goto store;
err = set_mode(adapter, MODE_DISCOVERABLE, NULL);
if (err < 0 && msg)
return btd_error_failed(msg, strerror(-err));
store:
adapter->pairable = pairable;
adapter_ops->set_pairable(adapter->dev_id, pairable);
write_device_pairable(&adapter->bdaddr, pairable);
emit_property_changed(connection, adapter->path,
ADAPTER_INTERFACE, "Pairable",
DBUS_TYPE_BOOLEAN, &pairable);
if (pairable && adapter->pairable_timeout)
adapter_set_pairable_timeout(adapter,
adapter->pairable_timeout);
done:
return msg ? dbus_message_new_method_return(msg) : NULL;
}
static gboolean pairable_timeout_handler(void *data)
{
set_pairable(NULL, NULL, FALSE, data);
return FALSE;
}
static void adapter_set_pairable_timeout(struct btd_adapter *adapter,
guint interval)
{
if (adapter->pairable_timeout_id) {
g_source_remove(adapter->pairable_timeout_id);
adapter->pairable_timeout_id = 0;
}
if (interval == 0)
return;
adapter->pairable_timeout_id = g_timeout_add_seconds(interval,
pairable_timeout_handler,
adapter);
}
static struct session_req *find_session(GSList *list, const char *sender)
{
GSList *l;
for (l = list; l; l = l->next) {
struct session_req *req = l->data;
if (g_str_equal(req->owner, sender))
return req;
}
return NULL;
}
static uint8_t get_needed_mode(struct btd_adapter *adapter, uint8_t mode)
{
GSList *l;
if (adapter->global_mode > mode)
mode = adapter->global_mode;
for (l = adapter->mode_sessions; l; l = l->next) {
struct session_req *req = l->data;
if (req->mode > mode)
mode = req->mode;
}
return mode;
}
static void stop_discovery(struct btd_adapter *adapter, gboolean suspend)
{
pending_remote_name_cancel(adapter);
if (suspend == FALSE) {
g_slist_foreach(adapter->found_devices,
(GFunc) dev_info_free, NULL);
g_slist_free(adapter->found_devices);
adapter->found_devices = NULL;
}
if (adapter->oor_devices) {
g_slist_free(adapter->oor_devices);
adapter->oor_devices = NULL;
}
/* Reset if suspended, otherwise remove timer (software scheduler)
or request inquiry to stop */
if (adapter->state & STATE_SUSPENDED) {
adapter->state &= ~STATE_SUSPENDED;
return;
}
if (adapter->scheduler_id) {
g_source_remove(adapter->scheduler_id);
adapter->scheduler_id = 0;
return;
}
if (adapter->state & STATE_LE_SCAN)
adapter_ops->stop_scanning(adapter->dev_id);
else
adapter_ops->stop_inquiry(adapter->dev_id);
}
static void session_remove(struct session_req *req)
{
struct btd_adapter *adapter = req->adapter;
/* Ignore set_mode session */
if (req->owner == NULL)
return;
DBG("%s session %p with %s deactivated",
req->mode ? "Mode" : "Discovery", req, req->owner);
if (req->mode) {
uint8_t mode;
adapter->mode_sessions = g_slist_remove(adapter->mode_sessions,
req);
mode = get_needed_mode(adapter, adapter->global_mode);
if (mode == adapter->mode)
return;
DBG("Switching to '%s' mode", mode2str(mode));
set_mode(adapter, mode, NULL);
} else {
adapter->disc_sessions = g_slist_remove(adapter->disc_sessions,
req);
if (adapter->disc_sessions)
return;
DBG("Stopping discovery");
stop_discovery(adapter, FALSE);
}
}
static void session_free(struct session_req *req)
{
if (req->id)
g_dbus_remove_watch(req->conn, req->id);
session_remove(req);
if (req->msg) {
dbus_message_unref(req->msg);
if (!req->got_reply && req->mode && req->adapter->agent)
agent_cancel(req->adapter->agent);
}
if (req->conn)
dbus_connection_unref(req->conn);
g_free(req->owner);
g_free(req);
}
static void session_owner_exit(DBusConnection *conn, void *user_data)
{
struct session_req *req = user_data;
req->id = 0;
session_free(req);
}
static void session_unref(struct session_req *req)
{
req->refcount--;
DBG("%p: ref=%d", req, req->refcount);
if (req->refcount)
return;
session_free(req);
}
static void confirm_mode_cb(struct agent *agent, DBusError *derr, void *data)
{
struct session_req *req = data;
int err;
DBusMessage *reply;
req->got_reply = TRUE;
if (derr && dbus_error_is_set(derr)) {
reply = dbus_message_new_error(req->msg, derr->name,
derr->message);
g_dbus_send_message(req->conn, reply);
session_unref(req);
return;
}
err = set_mode(req->adapter, req->mode, NULL);
if (err < 0)
reply = btd_error_failed(req->msg, strerror(-err));
else
reply = dbus_message_new_method_return(req->msg);
g_dbus_send_message(req->conn, reply);
dbus_message_unref(req->msg);
req->msg = NULL;
if (!find_session(req->adapter->mode_sessions, req->owner))
session_unref(req);
}
static DBusMessage *set_discoverable_timeout(DBusConnection *conn,
DBusMessage *msg,
uint32_t timeout,
void *data)
{
struct btd_adapter *adapter = data;
const char *path;
if (adapter->discov_timeout == timeout && timeout == 0)
return dbus_message_new_method_return(msg);
if (adapter->scan_mode & SCAN_INQUIRY)
adapter_set_discov_timeout(adapter, timeout);
adapter->discov_timeout = timeout;
write_discoverable_timeout(&adapter->bdaddr, timeout);
path = dbus_message_get_path(msg);
emit_property_changed(conn, path,
ADAPTER_INTERFACE, "DiscoverableTimeout",
DBUS_TYPE_UINT32, &timeout);
return dbus_message_new_method_return(msg);
}
static DBusMessage *set_pairable_timeout(DBusConnection *conn,
DBusMessage *msg,
uint32_t timeout,
void *data)
{
struct btd_adapter *adapter = data;
const char *path;
if (adapter->pairable_timeout == timeout && timeout == 0)
return dbus_message_new_method_return(msg);
if (adapter->pairable)
adapter_set_pairable_timeout(adapter, timeout);
adapter->pairable_timeout = timeout;
write_pairable_timeout(&adapter->bdaddr, timeout);
path = dbus_message_get_path(msg);
emit_property_changed(conn, path,
ADAPTER_INTERFACE, "PairableTimeout",
DBUS_TYPE_UINT32, &timeout);
return dbus_message_new_method_return(msg);
}
void btd_adapter_class_changed(struct btd_adapter *adapter, uint32_t new_class)
{
uint8_t class[3];
class[2] = (new_class >> 16) & 0xff;
class[1] = (new_class >> 8) & 0xff;
class[0] = new_class & 0xff;
write_local_class(&adapter->bdaddr, class);
adapter->dev_class = new_class;
emit_property_changed(connection, adapter->path,
ADAPTER_INTERFACE, "Class",
DBUS_TYPE_UINT32, &new_class);
}
void adapter_update_local_name(struct btd_adapter *adapter, const char *name)
{
struct hci_dev *dev = &adapter->dev;
if (strncmp(name, dev->name, MAX_NAME_LENGTH) == 0)
return;
strncpy(dev->name, name, MAX_NAME_LENGTH);
if (!adapter->name_stored) {
char *name_ptr = dev->name;
write_local_name(&adapter->bdaddr, dev->name);
if (connection)
emit_property_changed(connection, adapter->path,
ADAPTER_INTERFACE, "Name",
DBUS_TYPE_STRING, &name_ptr);
}
adapter->name_stored = FALSE;
}
static DBusMessage *set_name(DBusConnection *conn, DBusMessage *msg,
const char *name, void *data)
{
struct btd_adapter *adapter = data;
struct hci_dev *dev = &adapter->dev;
char *name_ptr = dev->name;
if (!g_utf8_validate(name, -1, NULL)) {
error("Name change failed: supplied name isn't valid UTF-8");
return btd_error_invalid_args(msg);
}
if (strncmp(name, dev->name, MAX_NAME_LENGTH) == 0)
goto done;
strncpy(dev->name, name, MAX_NAME_LENGTH);
write_local_name(&adapter->bdaddr, name);
emit_property_changed(connection, adapter->path,
ADAPTER_INTERFACE, "Name",
DBUS_TYPE_STRING, &name_ptr);
if (adapter->up) {
int err = adapter_ops->set_name(adapter->dev_id, name);
if (err < 0)
return btd_error_failed(msg, strerror(-err));
adapter->name_stored = TRUE;
}
done:
return dbus_message_new_method_return(msg);
}
struct btd_device *adapter_find_device(struct btd_adapter *adapter,
const char *dest)
{
struct btd_device *device;
GSList *l;
if (!adapter)
return NULL;
l = g_slist_find_custom(adapter->devices, dest,
(GCompareFunc) device_address_cmp);
if (!l)
return NULL;
device = l->data;
return device;
}
struct btd_device *adapter_find_connection(struct btd_adapter *adapter,
uint16_t handle)
{
GSList *l;
for (l = adapter->connections; l; l = l->next) {
struct btd_device *device = l->data;
if (device_has_connection(device, handle))
return device;
}
return NULL;
}
static void adapter_update_devices(struct btd_adapter *adapter)
{
char **devices;
int i;
GSList *l;
/* Devices */
devices = g_new0(char *, g_slist_length(adapter->devices) + 1);
for (i = 0, l = adapter->devices; l; l = l->next, i++) {
struct btd_device *dev = l->data;
devices[i] = (char *) device_get_path(dev);
}
emit_array_property_changed(connection, adapter->path,
ADAPTER_INTERFACE, "Devices",
DBUS_TYPE_OBJECT_PATH, &devices, i);
g_free(devices);
}
static void adapter_emit_uuids_updated(struct btd_adapter *adapter)
{
char **uuids;
int i;
sdp_list_t *list;
uuids = g_new0(char *, sdp_list_len(adapter->services) + 1);
for (i = 0, list = adapter->services; list; list = list->next) {
char *uuid;
sdp_record_t *rec = list->data;
uuid = bt_uuid2string(&rec->svclass);
if (uuid)
uuids[i++] = uuid;
}
emit_array_property_changed(connection, adapter->path,
ADAPTER_INTERFACE, "UUIDs", DBUS_TYPE_STRING, &uuids, i);
g_strfreev(uuids);
}
/*
* adapter_services_inc_rem - Insert or remove UUID from adapter
*/
static void adapter_service_ins_rem(const bdaddr_t *bdaddr, void *rec,
gboolean insert)
{
struct btd_adapter *adapter;
GSList *adapters;
adapters = NULL;
if (bacmp(bdaddr, BDADDR_ANY) != 0) {
/* Only one adapter */
adapter = manager_find_adapter(bdaddr);
if (!adapter)
return;
adapters = g_slist_append(adapters, adapter);
} else
/* Emit D-Bus msg to all adapters */
adapters = manager_get_adapters();
for (; adapters; adapters = adapters->next) {
adapter = adapters->data;
if (insert == TRUE)
adapter->services = sdp_list_insert_sorted(
adapter->services, rec,
record_sort);
else
adapter->services = sdp_list_remove(adapter->services,
rec);
adapter_emit_uuids_updated(adapter);
}
}
void adapter_service_insert(const bdaddr_t *bdaddr, void *rec)
{
/* TRUE to include service*/
adapter_service_ins_rem(bdaddr, rec, TRUE);
}
void adapter_service_remove(const bdaddr_t *bdaddr, void *rec)
{
/* FALSE to remove service*/
adapter_service_ins_rem(bdaddr, rec, FALSE);
}
sdp_list_t *adapter_get_services(struct btd_adapter *adapter)
{
return adapter->services;
}
static struct btd_device *adapter_create_device(DBusConnection *conn,
struct btd_adapter *adapter,
const char *address,
device_type_t type)
{
struct btd_device *device;
const char *path;
DBG("%s", address);
device = device_create(conn, adapter, address, type);
if (!device)
return NULL;
device_set_temporary(device, TRUE);
adapter->devices = g_slist_append(adapter->devices, device);
path = device_get_path(device);
g_dbus_emit_signal(conn, adapter->path,
ADAPTER_INTERFACE, "DeviceCreated",
DBUS_TYPE_OBJECT_PATH, &path,
DBUS_TYPE_INVALID);
adapter_update_devices(adapter);
return device;
}
void adapter_remove_device(DBusConnection *conn, struct btd_adapter *adapter,
struct btd_device *device,
gboolean remove_storage)
{
const gchar *dev_path = device_get_path(device);
struct agent *agent;
adapter->devices = g_slist_remove(adapter->devices, device);
adapter->connections = g_slist_remove(adapter->connections, device);
adapter_update_devices(adapter);
g_dbus_emit_signal(conn, adapter->path,
ADAPTER_INTERFACE, "DeviceRemoved",
DBUS_TYPE_OBJECT_PATH, &dev_path,
DBUS_TYPE_INVALID);
agent = device_get_agent(device);
if (agent && device_is_authorizing(device))
agent_cancel(agent);
device_remove(device, remove_storage);
}
struct btd_device *adapter_get_device(DBusConnection *conn,
struct btd_adapter *adapter,
const gchar *address)
{
struct btd_device *device;
DBG("%s", address);
if (!adapter)
return NULL;
device = adapter_find_device(adapter, address);
if (device)
return device;
return adapter_create_device(conn, adapter, address,
DEVICE_TYPE_BREDR);
}
static gboolean stop_scanning(gpointer user_data)
{
struct btd_adapter *adapter = user_data;
adapter_ops->stop_scanning(adapter->dev_id);
return FALSE;
}
static int start_discovery(struct btd_adapter *adapter)
{
int err, type;
/* Do not start if suspended */
if (adapter->state & STATE_SUSPENDED)
return 0;
/* Postpone discovery if still resolving names */
if (adapter->state & STATE_RESOLVNAME)
return 1;
pending_remote_name_cancel(adapter);
type = adapter_get_discover_type(adapter) & ~DISC_RESOLVNAME;
switch (type) {
case DISC_STDINQ:
case DISC_INTERLEAVE:
err = adapter_ops->start_inquiry(adapter->dev_id,
0x08, FALSE);
break;
case DISC_PINQ:
err = adapter_ops->start_inquiry(adapter->dev_id,
0x08, TRUE);
break;
case DISC_LE:
err = adapter_ops->start_scanning(adapter->dev_id);
break;
default:
err = -EINVAL;
}
return err;
}
static DBusMessage *adapter_start_discovery(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct session_req *req;
struct btd_adapter *adapter = data;
const char *sender = dbus_message_get_sender(msg);
int err;
if (!adapter->up)
return btd_error_not_ready(msg);
req = find_session(adapter->disc_sessions, sender);
if (req) {
session_ref(req);
return dbus_message_new_method_return(msg);
}
if (adapter->disc_sessions)
goto done;
err = start_discovery(adapter);
if (err < 0)
return btd_error_failed(msg, strerror(-err));
done:
req = create_session(adapter, conn, msg, 0,
session_owner_exit);
adapter->disc_sessions = g_slist_append(adapter->disc_sessions, req);
return dbus_message_new_method_return(msg);
}
static DBusMessage *adapter_stop_discovery(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct btd_adapter *adapter = data;
struct session_req *req;
const char *sender = dbus_message_get_sender(msg);
if (!adapter->up)
return btd_error_not_ready(msg);
req = find_session(adapter->disc_sessions, sender);
if (!req)
return btd_error_failed(msg, "Invalid discovery session");
session_unref(req);
info("Stopping discovery");
return dbus_message_new_method_return(msg);
}
struct remote_device_list_t {
GSList *list;
time_t time;
};
static DBusMessage *get_properties(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct btd_adapter *adapter = data;
const char *property;
DBusMessage *reply;
DBusMessageIter iter;
DBusMessageIter dict;
char str[MAX_NAME_LENGTH + 1], srcaddr[18];
gboolean value;
char **devices, **uuids;
int i;
GSList *l;
sdp_list_t *list;
ba2str(&adapter->bdaddr, srcaddr);
if (check_address(srcaddr) < 0)
return btd_error_invalid_args(msg);
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
dbus_message_iter_init_append(reply, &iter);
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
/* Address */
property = srcaddr;
dict_append_entry(&dict, "Address", DBUS_TYPE_STRING, &property);
/* Name */
memset(str, 0, sizeof(str));
strncpy(str, (char *) adapter->dev.name, MAX_NAME_LENGTH);
property = str;
dict_append_entry(&dict, "Name", DBUS_TYPE_STRING, &property);
/* Class */
dict_append_entry(&dict, "Class",
DBUS_TYPE_UINT32, &adapter->dev_class);
/* Powered */
value = (adapter->up && !adapter->off_requested) ? TRUE : FALSE;
dict_append_entry(&dict, "Powered", DBUS_TYPE_BOOLEAN, &value);
/* Discoverable */
value = adapter->scan_mode & SCAN_INQUIRY ? TRUE : FALSE;
dict_append_entry(&dict, "Discoverable", DBUS_TYPE_BOOLEAN, &value);
/* Pairable */
dict_append_entry(&dict, "Pairable", DBUS_TYPE_BOOLEAN,
&adapter->pairable);
/* DiscoverableTimeout */
dict_append_entry(&dict, "DiscoverableTimeout",
DBUS_TYPE_UINT32, &adapter->discov_timeout);
/* PairableTimeout */
dict_append_entry(&dict, "PairableTimeout",
DBUS_TYPE_UINT32, &adapter->pairable_timeout);
if (adapter->state & (STATE_PINQ | STATE_STDINQ | STATE_LE_SCAN))
value = TRUE;
else
value = FALSE;
/* Discovering */
dict_append_entry(&dict, "Discovering", DBUS_TYPE_BOOLEAN, &value);
/* Devices */
devices = g_new0(char *, g_slist_length(adapter->devices) + 1);
for (i = 0, l = adapter->devices; l; l = l->next, i++) {
struct btd_device *dev = l->data;
devices[i] = (char *) device_get_path(dev);
}
dict_append_array(&dict, "Devices", DBUS_TYPE_OBJECT_PATH,
&devices, i);
g_free(devices);
/* UUIDs */
uuids = g_new0(char *, sdp_list_len(adapter->services) + 1);
for (i = 0, list = adapter->services; list; list = list->next) {
sdp_record_t *rec = list->data;
char *uuid;
uuid = bt_uuid2string(&rec->svclass);
if (uuid)
uuids[i++] = uuid;
}
dict_append_array(&dict, "UUIDs", DBUS_TYPE_STRING, &uuids, i);
g_strfreev(uuids);
dbus_message_iter_close_container(&iter, &dict);
return reply;
}
static DBusMessage *set_property(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct btd_adapter *adapter = data;
DBusMessageIter iter;
DBusMessageIter sub;
const char *property;
char srcaddr[18];
ba2str(&adapter->bdaddr, srcaddr);
if (!dbus_message_iter_init(msg, &iter))
return btd_error_invalid_args(msg);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
return btd_error_invalid_args(msg);
dbus_message_iter_get_basic(&iter, &property);
dbus_message_iter_next(&iter);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
return btd_error_invalid_args(msg);
dbus_message_iter_recurse(&iter, &sub);
if (g_str_equal("Name", property)) {
const char *name;
if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING)
return btd_error_invalid_args(msg);
dbus_message_iter_get_basic(&sub, &name);
return set_name(conn, msg, name, data);
} else if (g_str_equal("Powered", property)) {
gboolean powered;
if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
return btd_error_invalid_args(msg);
dbus_message_iter_get_basic(&sub, &powered);
return set_powered(conn, msg, powered, data);
} else if (g_str_equal("Discoverable", property)) {
gboolean discoverable;
if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
return btd_error_invalid_args(msg);
dbus_message_iter_get_basic(&sub, &discoverable);
return set_discoverable(conn, msg, discoverable, data);
} else if (g_str_equal("DiscoverableTimeout", property)) {
uint32_t timeout;
if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT32)
return btd_error_invalid_args(msg);
dbus_message_iter_get_basic(&sub, &timeout);
return set_discoverable_timeout(conn, msg, timeout, data);
} else if (g_str_equal("Pairable", property)) {
gboolean pairable;
if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
return btd_error_invalid_args(msg);
dbus_message_iter_get_basic(&sub, &pairable);
return set_pairable(conn, msg, pairable, data);
} else if (g_str_equal("PairableTimeout", property)) {
uint32_t timeout;
if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT32)
return btd_error_invalid_args(msg);
dbus_message_iter_get_basic(&sub, &timeout);
return set_pairable_timeout(conn, msg, timeout, data);
}
return btd_error_invalid_args(msg);
}
static DBusMessage *request_session(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct btd_adapter *adapter = data;
struct session_req *req;
const char *sender = dbus_message_get_sender(msg);
uint8_t new_mode;
int err;
if (!adapter->agent)
return btd_error_agent_not_available(msg);
if (!adapter->mode_sessions)
adapter->global_mode = adapter->mode;
new_mode = get_mode(&adapter->bdaddr, "on");
req = find_session(adapter->mode_sessions, sender);
if (req) {
session_ref(req);
return dbus_message_new_method_return(msg);
} else {
req = create_session(adapter, conn, msg, new_mode,
session_owner_exit);
adapter->mode_sessions = g_slist_append(adapter->mode_sessions,
req);
}
/* No need to change mode */
if (adapter->mode >= new_mode)
return dbus_message_new_method_return(msg);
err = agent_confirm_mode_change(adapter->agent, mode2str(new_mode),
confirm_mode_cb, req, NULL);
if (err < 0) {
session_unref(req);
return btd_error_failed(msg, strerror(-err));
}
return NULL;
}
static DBusMessage *release_session(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct btd_adapter *adapter = data;
struct session_req *req;
const char *sender = dbus_message_get_sender(msg);
req = find_session(adapter->mode_sessions, sender);
if (!req)
return btd_error_failed(msg, "Invalid Session");
session_unref(req);
return dbus_message_new_method_return(msg);
}
static DBusMessage *list_devices(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct btd_adapter *adapter = data;
DBusMessage *reply;
GSList *l;
DBusMessageIter iter;
DBusMessageIter array_iter;
const gchar *dev_path;
if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING))
return btd_error_invalid_args(msg);
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
dbus_message_iter_init_append(reply, &iter);
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
DBUS_TYPE_OBJECT_PATH_AS_STRING, &array_iter);
for (l = adapter->devices; l; l = l->next) {
struct btd_device *device = l->data;
dev_path = device_get_path(device);
dbus_message_iter_append_basic(&array_iter,
DBUS_TYPE_OBJECT_PATH, &dev_path);
}
dbus_message_iter_close_container(&iter, &array_iter);
return reply;
}
static DBusMessage *cancel_device_creation(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct btd_adapter *adapter = data;
const gchar *address, *sender = dbus_message_get_sender(msg);
struct btd_device *device;
if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address,
DBUS_TYPE_INVALID) == FALSE)
return btd_error_invalid_args(msg);
if (check_address(address) < 0)
return btd_error_invalid_args(msg);
device = adapter_find_device(adapter, address);
if (!device || !device_is_creating(device, NULL))
return btd_error_does_not_exist(msg);
if (!device_is_creating(device, sender))
return btd_error_not_authorized(msg);
device_set_temporary(device, TRUE);
if (device_is_connected(device)) {
device_request_disconnect(device, msg);
return NULL;
}
adapter_remove_device(conn, adapter, device, TRUE);
return dbus_message_new_method_return(msg);
}
static device_type_t flags2type(uint8_t flags)
{
/* Inferring the remote type based on the EIR Flags field */
/* For LE only and dual mode the following flags must be zero */
if (flags & (EIR_SIM_CONTROLLER | EIR_SIM_HOST))
return DEVICE_TYPE_UNKNOWN;
/* Limited or General discoverable mode bit must be enabled */
if (!(flags & (EIR_LIM_DISC | EIR_GEN_DISC)))
return DEVICE_TYPE_UNKNOWN;
if (flags & EIR_BREDR_UNSUP)
return DEVICE_TYPE_LE;
else
return DEVICE_TYPE_DUALMODE;
}
static gboolean event_is_connectable(uint8_t type)
{
switch (type) {
case ADV_TYPE_IND:
case ADV_TYPE_DIRECT_IND:
return TRUE;
default:
return FALSE;
}
}
static DBusMessage *create_device(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct btd_adapter *adapter = data;
struct btd_device *device;
struct remote_dev_info *dev, match;
const gchar *address;
int err;
device_type_t type;
if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address,
DBUS_TYPE_INVALID) == FALSE)
return btd_error_invalid_args(msg);
if (check_address(address) < 0)
return btd_error_invalid_args(msg);
if (!adapter->up)
return btd_error_not_ready(msg);
if (adapter_find_device(adapter, address))
return btd_error_already_exists(msg);
DBG("%s", address);
memset(&match, 0, sizeof(struct remote_dev_info));
str2ba(address, &match.bdaddr);
match.name_status = NAME_ANY;
dev = adapter_search_found_devices(adapter, &match);
if (dev && dev->flags)
type = flags2type(dev->flags);
else
type = DEVICE_TYPE_BREDR;
device = adapter_create_device(conn, adapter, address, type);
if (!device)
return NULL;
if (type == DEVICE_TYPE_LE && !event_is_connectable(dev->evt_type)) {
/* Device is not connectable */
const char *path = device_get_path(device);
DBusMessage *reply;
reply = dbus_message_new_method_return(msg);
dbus_message_append_args(reply,
DBUS_TYPE_OBJECT_PATH, &path,
DBUS_TYPE_INVALID);
return reply;
}
err = device_browse(device, conn, msg, NULL, FALSE);
if (err < 0) {
adapter_remove_device(conn, adapter, device, TRUE);
return btd_error_failed(msg, strerror(-err));
}
return NULL;
}
static uint8_t parse_io_capability(const char *capability)
{
if (g_str_equal(capability, ""))
return IO_CAPABILITY_DISPLAYYESNO;
if (g_str_equal(capability, "DisplayOnly"))
return IO_CAPABILITY_DISPLAYONLY;
if (g_str_equal(capability, "DisplayYesNo"))
return IO_CAPABILITY_DISPLAYYESNO;
if (g_str_equal(capability, "KeyboardOnly"))
return IO_CAPABILITY_KEYBOARDONLY;
if (g_str_equal(capability, "NoInputNoOutput"))
return IO_CAPABILITY_NOINPUTNOOUTPUT;
return IO_CAPABILITY_INVALID;
}
static DBusMessage *create_paired_device(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct btd_adapter *adapter = data;
struct btd_device *device;
const gchar *address, *agent_path, *capability, *sender;
uint8_t cap;
if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address,
DBUS_TYPE_OBJECT_PATH, &agent_path,
DBUS_TYPE_STRING, &capability,
DBUS_TYPE_INVALID) == FALSE)
return btd_error_invalid_args(msg);
if (check_address(address) < 0)
return btd_error_invalid_args(msg);
if (!adapter->up)
return btd_error_not_ready(msg);
sender = dbus_message_get_sender(msg);
if (adapter->agent &&
agent_matches(adapter->agent, sender, agent_path)) {
error("Refusing adapter agent usage as device specific one");
return btd_error_invalid_args(msg);
}
cap = parse_io_capability(capability);
if (cap == IO_CAPABILITY_INVALID)
return btd_error_invalid_args(msg);
device = adapter_get_device(conn, adapter, address);
if (!device)
return btd_error_failed(msg,
"Unable to create a new device object");
return device_create_bonding(device, conn, msg, agent_path, cap);
}
static gint device_path_cmp(struct btd_device *device, const gchar *path)
{
const gchar *dev_path = device_get_path(device);
return strcasecmp(dev_path, path);
}
static DBusMessage *remove_device(DBusConnection *conn, DBusMessage *msg,
void *data)
{
struct btd_adapter *adapter = data;
struct btd_device *device;
const char *path;
GSList *l;
if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
DBUS_TYPE_INVALID) == FALSE)
return btd_error_invalid_args(msg);
l = g_slist_find_custom(adapter->devices,
path, (GCompareFunc) device_path_cmp);
if (!l)
return btd_error_does_not_exist(msg);
device = l->data;
if (device_is_temporary(device) || device_is_busy(device))
return g_dbus_create_error(msg,
ERROR_INTERFACE ".DoesNotExist",
"Device creation in progress");
device_set_temporary(device, TRUE);
if (!device_is_connected(device)) {
adapter_remove_device(conn, adapter, device, TRUE);
return dbus_message_new_method_return(msg);
}
device_request_disconnect(device, msg);
return NULL;
}
static DBusMessage *find_device(DBusConnection *conn, DBusMessage *msg,
void *data)
{
struct btd_adapter *adapter = data;
struct btd_device *device;
DBusMessage *reply;
const gchar *address;
GSList *l;
const gchar *dev_path;
if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address,
DBUS_TYPE_INVALID))
return btd_error_invalid_args(msg);
l = g_slist_find_custom(adapter->devices,
address, (GCompareFunc) device_address_cmp);
if (!l)
return btd_error_does_not_exist(msg);
device = l->data;
reply = dbus_message_new_method_return(msg);
if (!reply)
return NULL;
dev_path = device_get_path(device);
dbus_message_append_args(reply,
DBUS_TYPE_OBJECT_PATH, &dev_path,
DBUS_TYPE_INVALID);
return reply;
}
static void agent_removed(struct agent *agent, struct btd_adapter *adapter)
{
adapter->agent = NULL;
}
static DBusMessage *register_agent(DBusConnection *conn, DBusMessage *msg,
void *data)
{
const char *path, *name, *capability;
struct agent *agent;
struct btd_adapter *adapter = data;
uint8_t cap;
if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
DBUS_TYPE_STRING, &capability, DBUS_TYPE_INVALID))
return NULL;
if (adapter->agent)
return btd_error_already_exists(msg);
cap = parse_io_capability(capability);
if (cap == IO_CAPABILITY_INVALID)
return btd_error_invalid_args(msg);
name = dbus_message_get_sender(msg);
agent = agent_create(adapter, name, path, cap,
(agent_remove_cb) agent_removed, adapter);
if (!agent)
return btd_error_failed(msg, "Failed to create a new agent");
adapter->agent = agent;
DBG("Agent registered for hci%d at %s:%s", adapter->dev_id, name,
path);
return dbus_message_new_method_return(msg);
}
static DBusMessage *unregister_agent(DBusConnection *conn, DBusMessage *msg,
void *data)
{
const char *path, *name;
struct btd_adapter *adapter = data;
if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
DBUS_TYPE_INVALID))
return NULL;
name = dbus_message_get_sender(msg);
if (!adapter->agent || !agent_matches(adapter->agent, name, path))
return btd_error_does_not_exist(msg);
agent_free(adapter->agent);
adapter->agent = NULL;
return dbus_message_new_method_return(msg);
}
static GDBusMethodTable adapter_methods[] = {
{ "GetProperties", "", "a{sv}",get_properties },
{ "SetProperty", "sv", "", set_property,
G_DBUS_METHOD_FLAG_ASYNC},
{ "RequestSession", "", "", request_session,
G_DBUS_METHOD_FLAG_ASYNC},
{ "ReleaseSession", "", "", release_session },
{ "StartDiscovery", "", "", adapter_start_discovery },
{ "StopDiscovery", "", "", adapter_stop_discovery,
G_DBUS_METHOD_FLAG_ASYNC},
{ "ListDevices", "", "ao", list_devices,
G_DBUS_METHOD_FLAG_DEPRECATED},
{ "CreateDevice", "s", "o", create_device,
G_DBUS_METHOD_FLAG_ASYNC},
{ "CreatePairedDevice", "sos", "o", create_paired_device,
G_DBUS_METHOD_FLAG_ASYNC},
{ "CancelDeviceCreation","s", "", cancel_device_creation,
G_DBUS_METHOD_FLAG_ASYNC},
{ "RemoveDevice", "o", "", remove_device,
G_DBUS_METHOD_FLAG_ASYNC},
{ "FindDevice", "s", "o", find_device },
{ "RegisterAgent", "os", "", register_agent },
{ "UnregisterAgent", "o", "", unregister_agent },
{ }
};
static GDBusSignalTable adapter_signals[] = {
{ "PropertyChanged", "sv" },
{ "DeviceCreated", "o" },
{ "DeviceRemoved", "o" },
{ "DeviceFound", "sa{sv}" },
{ "DeviceDisappeared", "s" },
{ }
};
static void create_stored_device_from_profiles(char *key, char *value,
void *user_data)
{
struct btd_adapter *adapter = user_data;
GSList *uuids = bt_string2list(value);
struct btd_device *device;
if (g_slist_find_custom(adapter->devices,
key, (GCompareFunc) device_address_cmp))
return;
device = device_create(connection, adapter, key, DEVICE_TYPE_BREDR);
if (!device)
return;
device_set_temporary(device, FALSE);
adapter->devices = g_slist_append(adapter->devices, device);
device_probe_drivers(device, uuids);
g_slist_foreach(uuids, (GFunc) g_free, NULL);
g_slist_free(uuids);
}
static void create_stored_device_from_linkkeys(char *key, char *value,
void *user_data)
{
struct btd_adapter *adapter = user_data;
struct btd_device *device;
if (g_slist_find_custom(adapter->devices, key,
(GCompareFunc) device_address_cmp))
return;
device = device_create(connection, adapter, key, DEVICE_TYPE_BREDR);
if (device) {
device_set_temporary(device, FALSE);
adapter->devices = g_slist_append(adapter->devices, device);
}
}
static void create_stored_device_from_blocked(char *key, char *value,
void *user_data)
{
struct btd_adapter *adapter = user_data;
struct btd_device *device;
if (g_slist_find_custom(adapter->devices,
key, (GCompareFunc) device_address_cmp))
return;
device = device_create(connection, adapter, key, DEVICE_TYPE_BREDR);
if (device) {
device_set_temporary(device, FALSE);
adapter->devices = g_slist_append(adapter->devices, device);
}
}
static void load_devices(struct btd_adapter *adapter)
{
char filename[PATH_MAX + 1];
char srcaddr[18];
ba2str(&adapter->bdaddr, srcaddr);
create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "profiles");
textfile_foreach(filename, create_stored_device_from_profiles,
adapter);
create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "linkkeys");
textfile_foreach(filename, create_stored_device_from_linkkeys,
adapter);
create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "blocked");
textfile_foreach(filename, create_stored_device_from_blocked, adapter);
}
int btd_adapter_block_address(struct btd_adapter *adapter, bdaddr_t *bdaddr)
{
return adapter_ops->block_device(adapter->dev_id, bdaddr);
}
int btd_adapter_unblock_address(struct btd_adapter *adapter, bdaddr_t *bdaddr)
{
return adapter_ops->unblock_device(adapter->dev_id, bdaddr);
}
static void clear_blocked(struct btd_adapter *adapter)
{
int err;
err = adapter_ops->unblock_device(adapter->dev_id, BDADDR_ANY);
if (err < 0)
error("Clearing blocked list failed: %s (%d)",
strerror(-err), -err);
}
static void probe_driver(gpointer data, gpointer user_data)
{
struct btd_adapter *adapter = data;
struct btd_adapter_driver *driver = user_data;
int err;
if (!adapter->up)
return;
err = driver->probe(adapter);
if (err < 0)
error("%s: %s (%d)", driver->name, strerror(-err), -err);
}
static void load_drivers(struct btd_adapter *adapter)
{
GSList *l;
for (l = adapter_drivers; l; l = l->next) {
struct btd_adapter_driver *driver = l->data;
if (driver->probe == NULL)
continue;
probe_driver(adapter, driver);
}
}
static void load_connections(struct btd_adapter *adapter)
{
GSList *l, *conns;
int err;
err = adapter_ops->get_conn_list(adapter->dev_id, &conns);
if (err < 0) {
error("Unable to fetch existing connections: %s (%d)",
strerror(-err), -err);
return;
}
for (l = conns; l != NULL; l = g_slist_next(l)) {
struct hci_conn_info *ci = l->data;
struct btd_device *device;
char address[18];
ba2str(&ci->bdaddr, address);
device = adapter_get_device(connection, adapter, address);
if (device)
adapter_add_connection(adapter, device, ci->handle);
}
g_slist_foreach(conns, (GFunc) g_free, NULL);
g_slist_free(conns);
}
static int get_discoverable_timeout(const char *src)
{
int timeout;
if (read_discoverable_timeout(src, &timeout) == 0)
return timeout;
return main_opts.discovto;
}
static int get_pairable_timeout(const char *src)
{
int timeout;
if (read_pairable_timeout(src, &timeout) == 0)
return timeout;
return main_opts.pairto;
}
static void call_adapter_powered_callbacks(struct btd_adapter *adapter,
gboolean powered)
{
GSList *l;
for (l = adapter->powered_callbacks; l; l = l->next) {
btd_adapter_powered_cb cb = l->data;
cb(adapter, powered);
}
}
static void emit_device_disappeared(gpointer data, gpointer user_data)
{
struct remote_dev_info *dev = data;
struct btd_adapter *adapter = user_data;
char address[18];
const char *paddr = address;
ba2str(&dev->bdaddr, address);
g_dbus_emit_signal(connection, adapter->path,
ADAPTER_INTERFACE, "DeviceDisappeared",
DBUS_TYPE_STRING, &paddr,
DBUS_TYPE_INVALID);
adapter->found_devices = g_slist_remove(adapter->found_devices, dev);
}
static void update_oor_devices(struct btd_adapter *adapter)
{
g_slist_foreach(adapter->oor_devices, emit_device_disappeared, adapter);
g_slist_foreach(adapter->oor_devices, (GFunc) dev_info_free, NULL);
g_slist_free(adapter->oor_devices);
adapter->oor_devices = g_slist_copy(adapter->found_devices);
}
static gboolean bredr_capable(struct btd_adapter *adapter)
{
struct hci_dev *dev = &adapter->dev;
return (dev->features[4] & LMP_NO_BREDR) == 0 ? TRUE : FALSE;
}
static gboolean le_capable(struct btd_adapter *adapter)
{
struct hci_dev *dev = &adapter->dev;
return (dev->features[4] & LMP_LE &&
dev->extfeatures[0] & LMP_HOST_LE) ? TRUE : FALSE;
}
int adapter_get_discover_type(struct btd_adapter *adapter)
{
gboolean le, bredr;
int type;
le = le_capable(adapter);
bredr = bredr_capable(adapter);
if (le)
type = bredr ? DISC_INTERLEAVE : DISC_LE;
else
type = main_opts.discov_interval ? DISC_STDINQ :
DISC_PINQ;
if (main_opts.name_resolv)
type |= DISC_RESOLVNAME;
return type;
}
void btd_adapter_get_state(struct btd_adapter *adapter, uint8_t *mode,
uint8_t *on_mode, gboolean *pairable)
{
char str[14], address[18];
ba2str(&adapter->bdaddr, address);
if (mode) {
if (main_opts.remember_powered == FALSE)
*mode = main_opts.mode;
else if (read_device_mode(address, str, sizeof(str)) < 0)
*mode = main_opts.mode;
else
*mode = get_mode(&adapter->bdaddr, str);
}
if (on_mode) {
if (main_opts.remember_powered == FALSE)
*on_mode = main_opts.mode;
else if (read_on_mode(address, str, sizeof(str)) < 0)
*on_mode = main_opts.mode;
else
*on_mode = get_mode(&adapter->bdaddr, str);
}
if (pairable)
*pairable = adapter->pairable;
}
void btd_adapter_start(struct btd_adapter *adapter)
{
char address[18];
uint8_t cls[3];
gboolean powered;
ba2str(&adapter->bdaddr, address);
adapter->dev_class = 0;
adapter->off_requested = FALSE;
adapter->up = TRUE;
adapter->discov_timeout = get_discoverable_timeout(address);
adapter->pairable_timeout = get_pairable_timeout(address);
adapter->state = STATE_IDLE;
adapter->mode = MODE_CONNECTABLE;
if (main_opts.le)
adapter_ops->enable_le(adapter->dev_id);
adapter_ops->set_name(adapter->dev_id, adapter->dev.name);
if (read_local_class(&adapter->bdaddr, cls) < 0) {
uint32_t class = htobl(main_opts.class);
memcpy(cls, &class, 3);
}
btd_adapter_set_class(adapter, cls[1], cls[0]);
powered = TRUE;
emit_property_changed(connection, adapter->path,
ADAPTER_INTERFACE, "Powered",
DBUS_TYPE_BOOLEAN, &powered);
call_adapter_powered_callbacks(adapter, TRUE);
adapter_ops->disable_cod_cache(adapter->dev_id);
info("Adapter %s has been enabled", adapter->path);
}
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_cancel_bonding(device,
HCI_OE_USER_ENDED_CONNECTION);
}
}
static void unload_drivers(struct btd_adapter *adapter)
{
GSList *l;
for (l = adapter_drivers; l; l = l->next) {
struct btd_adapter_driver *driver = l->data;
if (driver->remove)
driver->remove(adapter);
}
}
static void set_mode_complete(struct btd_adapter *adapter)
{
struct session_req *pending;
const char *modestr;
int err;
DBG("");
if (adapter->pending_mode == NULL)
return;
pending = adapter->pending_mode;
adapter->pending_mode = NULL;
err = (pending->mode != adapter->mode) ? -EINVAL : 0;
if (pending->msg != NULL) {
DBusMessage *msg = pending->msg;
DBusMessage *reply;
if (err < 0)
reply = btd_error_failed(msg, strerror(-err));
else
reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
g_dbus_send_message(connection, reply);
}
modestr = mode2str(adapter->mode);
DBG("%s", modestr);
/* restore if the mode doesn't matches the pending */
if (err != 0) {
write_device_mode(&adapter->bdaddr, modestr);
error("unable to set mode: %s", mode2str(pending->mode));
}
session_unref(pending);
}
int btd_adapter_stop(struct btd_adapter *adapter)
{
gboolean powered, discoverable, pairable;
/* cancel pending timeout */
if (adapter->discov_timeout_id) {
g_source_remove(adapter->discov_timeout_id);
adapter->discov_timeout_id = 0;
}
/* check pending requests */
reply_pending_requests(adapter);
stop_discovery(adapter, FALSE);
if (adapter->disc_sessions) {
g_slist_foreach(adapter->disc_sessions, (GFunc) session_free,
NULL);
g_slist_free(adapter->disc_sessions);
adapter->disc_sessions = NULL;
}
while (adapter->connections) {
struct btd_device *device = adapter->connections->data;
adapter_remove_connection(adapter, device, 0);
}
if (adapter->scan_mode == (SCAN_PAGE | SCAN_INQUIRY)) {
discoverable = FALSE;
emit_property_changed(connection, adapter->path,
ADAPTER_INTERFACE, "Discoverable",
DBUS_TYPE_BOOLEAN, &discoverable);
}
if ((adapter->scan_mode & SCAN_PAGE) && adapter->pairable == TRUE) {
pairable = FALSE;
emit_property_changed(connection, adapter->path,
ADAPTER_INTERFACE, "Pairable",
DBUS_TYPE_BOOLEAN, &pairable);
}
powered = FALSE;
emit_property_changed(connection, adapter->path, ADAPTER_INTERFACE,
"Powered", DBUS_TYPE_BOOLEAN, &powered);
adapter->up = 0;
adapter->scan_mode = SCAN_DISABLED;
adapter->mode = MODE_OFF;
adapter->state = STATE_IDLE;
adapter->off_requested = FALSE;
adapter->name_stored = FALSE;
call_adapter_powered_callbacks(adapter, FALSE);
info("Adapter %s has been disabled", adapter->path);
set_mode_complete(adapter);
return 0;
}
int adapter_update_ssp_mode(struct btd_adapter *adapter, uint8_t mode)
{
struct hci_dev *dev = &adapter->dev;
dev->ssp_mode = mode;
return 0;
}
static void adapter_free(gpointer user_data)
{
struct btd_adapter *adapter = user_data;
agent_free(adapter->agent);
adapter->agent = NULL;
DBG("%p", adapter);
if (adapter->auth_idle_id)
g_source_remove(adapter->auth_idle_id);
sdp_list_free(adapter->services, NULL);
g_free(adapter->path);
g_free(adapter);
}
struct btd_adapter *btd_adapter_ref(struct btd_adapter *adapter)
{
adapter->ref++;
DBG("%p: ref=%d", adapter, adapter->ref);
return adapter;
}
void btd_adapter_unref(struct btd_adapter *adapter)
{
gchar *path;
adapter->ref--;
DBG("%p: ref=%d", adapter, adapter->ref);
if (adapter->ref > 0)
return;
path = g_strdup(adapter->path);
g_dbus_unregister_interface(connection, path, ADAPTER_INTERFACE);
g_free(path);
}
gboolean adapter_init(struct btd_adapter *adapter)
{
struct hci_version ver;
struct hci_dev *dev;
int err;
/* adapter_ops makes sure that newly registered adapters always
* start off as powered */
adapter->up = TRUE;
adapter_ops->read_bdaddr(adapter->dev_id, &adapter->bdaddr);
if (bacmp(&adapter->bdaddr, BDADDR_ANY) == 0) {
error("No address available for hci%d", adapter->dev_id);
return FALSE;
}
err = adapter_ops->read_local_version(adapter->dev_id, &ver);
if (err < 0) {
error("Can't read version info for hci%d: %s (%d)",
adapter->dev_id, strerror(-err), -err);
return FALSE;
}
dev = &adapter->dev;
dev->hci_rev = ver.hci_rev;
dev->lmp_ver = ver.lmp_ver;
dev->lmp_subver = ver.lmp_subver;
dev->manufacturer = ver.manufacturer;
err = adapter_ops->read_local_features(adapter->dev_id, dev->features);
if (err < 0) {
error("Can't read features for hci%d: %s (%d)",
adapter->dev_id, strerror(-err), -err);
return FALSE;
}
if (read_local_name(&adapter->bdaddr, adapter->dev.name) < 0)
expand_name(adapter->dev.name, MAX_NAME_LENGTH, main_opts.name,
adapter->dev_id);
sdp_init_services_list(&adapter->bdaddr);
btd_adapter_services_updated(adapter);
load_drivers(adapter);
clear_blocked(adapter);
load_devices(adapter);
/* Set pairable mode */
if (read_device_pairable(&adapter->bdaddr, &adapter->pairable) < 0)
adapter->pairable = TRUE;
/* retrieve the active connections: address the scenario where
* the are active connections before the daemon've started */
load_connections(adapter);
return TRUE;
}
struct btd_adapter *adapter_create(DBusConnection *conn, int id)
{
char path[MAX_PATH_LENGTH];
struct btd_adapter *adapter;
const char *base_path = manager_get_base_path();
if (!connection)
connection = conn;
adapter = g_try_new0(struct btd_adapter, 1);
if (!adapter) {
error("adapter_create: failed to alloc memory for hci%d", id);
return NULL;
}
adapter->dev_id = id;
snprintf(path, sizeof(path), "%s/hci%d", base_path, id);
adapter->path = g_strdup(path);
if (!g_dbus_register_interface(conn, path, ADAPTER_INTERFACE,
adapter_methods, adapter_signals, NULL,
adapter, adapter_free)) {
error("Adapter interface init failed on path %s", path);
adapter_free(adapter);
return NULL;
}
return btd_adapter_ref(adapter);
}
void adapter_remove(struct btd_adapter *adapter)
{
GSList *l;
DBG("Removing adapter %s", adapter->path);
for (l = adapter->devices; l; l = l->next)
device_remove(l->data, FALSE);
g_slist_free(adapter->devices);
unload_drivers(adapter);
/* Return adapter to down state if it was not up on init */
adapter_ops->restore_powered(adapter->dev_id);
btd_adapter_unref(adapter);
}
uint16_t adapter_get_dev_id(struct btd_adapter *adapter)
{
return adapter->dev_id;
}
const gchar *adapter_get_path(struct btd_adapter *adapter)
{
if (!adapter)
return NULL;
return adapter->path;
}
void adapter_get_address(struct btd_adapter *adapter, bdaddr_t *bdaddr)
{
bacpy(bdaddr, &adapter->bdaddr);
}
void adapter_set_state(struct btd_adapter *adapter, int state)
{
const char *path = adapter->path;
gboolean discov_active;
int previous, type;
if (adapter->state == state)
return;
previous = adapter->state;
adapter->state = state;
type = adapter_get_discover_type(adapter);
switch (state) {
case STATE_STDINQ:
case STATE_PINQ:
discov_active = TRUE;
/* Started a new session while resolving names ? */
if (previous & STATE_RESOLVNAME)
return;
break;
case STATE_LE_SCAN:
/* Scanning enabled */
adapter->stop_discov_id = g_timeout_add(5120,
stop_scanning, adapter);
/* For dual mode: don't send "Discovering = TRUE" */
if (bredr_capable(adapter) == TRUE)
return;
/* LE only */
discov_active = TRUE;
break;
case STATE_IDLE:
/*
* Interleave: from inquiry to scanning. Interleave is not
* applicable to requests triggered by external applications.
*/
if (adapter->disc_sessions && (type & DISC_INTERLEAVE) &&
(previous & STATE_STDINQ)) {
adapter_ops->start_scanning(adapter->dev_id);
return;
}
/* BR/EDR only: inquiry finished */
discov_active = FALSE;
break;
default:
discov_active = FALSE;
break;
}
if (discov_active == FALSE) {
update_oor_devices(adapter);
if (type & DISC_RESOLVNAME) {
if (adapter_resolve_names(adapter) == 0) {
adapter->state |= STATE_RESOLVNAME;
return;
}
}
} else if (adapter->disc_sessions && main_opts.discov_interval)
adapter->scheduler_id = g_timeout_add_seconds(
main_opts.discov_interval,
(GSourceFunc) start_discovery,
adapter);
emit_property_changed(connection, path,
ADAPTER_INTERFACE, "Discovering",
DBUS_TYPE_BOOLEAN, &discov_active);
}
int adapter_get_state(struct btd_adapter *adapter)
{
return adapter->state;
}
struct remote_dev_info *adapter_search_found_devices(struct btd_adapter *adapter,
struct remote_dev_info *match)
{
GSList *l;
l = g_slist_find_custom(adapter->found_devices, match,
(GCompareFunc) found_device_cmp);
if (l)
return l->data;
return NULL;
}
static int dev_rssi_cmp(struct remote_dev_info *d1, struct remote_dev_info *d2)
{
int rssi1, rssi2;
rssi1 = d1->rssi < 0 ? -d1->rssi : d1->rssi;
rssi2 = d2->rssi < 0 ? -d2->rssi : d2->rssi;
return rssi1 - rssi2;
}
static void append_dict_valist(DBusMessageIter *iter,
const char *first_key,
va_list var_args)
{
DBusMessageIter dict;
const char *key;
int type;
int n_elements;
void *val;
dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
key = first_key;
while (key) {
type = va_arg(var_args, int);
val = va_arg(var_args, void *);
if (type == DBUS_TYPE_ARRAY) {
n_elements = va_arg(var_args, int);
if (n_elements > 0)
dict_append_array(&dict, key, DBUS_TYPE_STRING,
val, n_elements);
} else
dict_append_entry(&dict, key, type, val);
key = va_arg(var_args, char *);
}
dbus_message_iter_close_container(iter, &dict);
}
static void emit_device_found(const char *path, const char *address,
const char *first_key, ...)
{
DBusMessage *signal;
DBusMessageIter iter;
va_list var_args;
signal = dbus_message_new_signal(path, ADAPTER_INTERFACE,
"DeviceFound");
if (!signal) {
error("Unable to allocate new %s.DeviceFound signal",
ADAPTER_INTERFACE);
return;
}
dbus_message_iter_init_append(signal, &iter);
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &address);
va_start(var_args, first_key);
append_dict_valist(&iter, first_key, var_args);
va_end(var_args);
g_dbus_send_message(connection, signal);
}
static char **strlist2array(GSList *list)
{
GSList *l;
unsigned int i, n;
char **array;
if (list == NULL)
return NULL;
n = g_slist_length(list);
array = g_new0(char *, n + 1);
for (l = list, i = 0; l; l = l->next, i++)
array[i] = g_strdup((const gchar *) l->data);
return array;
}
static GSList *get_eir_uuids(uint8_t *eir_data, size_t eir_length, GSList *list)
{
uint16_t len = 0;
size_t total;
size_t uuid16_count = 0;
size_t uuid32_count = 0;
size_t uuid128_count = 0;
uint8_t *uuid16;
uint8_t *uuid32;
uint8_t *uuid128;
uuid_t service;
char *uuid_str;
unsigned int i;
if (eir_data == NULL || eir_length == 0)
return list;
while (len < eir_length - 1) {
uint8_t field_len = eir_data[0];
/* Check for the end of EIR */
if (field_len == 0)
break;
switch (eir_data[1]) {
case EIR_UUID16_SOME:
case EIR_UUID16_ALL:
uuid16_count = field_len / 2;
uuid16 = &eir_data[2];
break;
case EIR_UUID32_SOME:
case EIR_UUID32_ALL:
uuid32_count = field_len / 4;
uuid32 = &eir_data[2];
break;
case EIR_UUID128_SOME:
case EIR_UUID128_ALL:
uuid128_count = field_len / 16;
uuid128 = &eir_data[2];
break;
}
len += field_len + 1;
eir_data += field_len + 1;
}
/* Bail out if got incorrect length */
if (len > eir_length)
return list;
total = uuid16_count + uuid32_count + uuid128_count;
if (!total)
return list;
/* Generate uuids in SDP format (EIR data is Little Endian) */
service.type = SDP_UUID16;
for (i = 0; i < uuid16_count; i++) {
uint16_t val16 = uuid16[1];
val16 = (val16 << 8) + uuid16[0];
service.value.uuid16 = val16;
uuid_str = bt_uuid2string(&service);
if (g_slist_find_custom(list, uuid_str,
(GCompareFunc) strcmp) == NULL)
list = g_slist_append(list, uuid_str);
else
g_free(uuid_str);
uuid16 += 2;
}
service.type = SDP_UUID32;
for (i = uuid16_count; i < uuid32_count + uuid16_count; i++) {
uint32_t val32 = uuid32[3];
int k;
for (k = 2; k >= 0; k--)
val32 = (val32 << 8) + uuid32[k];
service.value.uuid32 = val32;
uuid_str = bt_uuid2string(&service);
if (g_slist_find_custom(list, uuid_str,
(GCompareFunc) strcmp) == NULL)
list = g_slist_append(list, uuid_str);
else
g_free(uuid_str);
uuid32 += 4;
}
service.type = SDP_UUID128;
for (i = uuid32_count + uuid16_count; i < total; i++) {
int k;
for (k = 0; k < 16; k++)
service.value.uuid128.data[k] = uuid128[16 - k - 1];
uuid_str = bt_uuid2string(&service);
if (g_slist_find_custom(list, uuid_str,
(GCompareFunc) strcmp) == NULL)
list = g_slist_append(list, uuid_str);
else
g_free(uuid_str);
uuid128 += 16;
}
return list;
}
void adapter_emit_device_found(struct btd_adapter *adapter,
struct remote_dev_info *dev,
uint8_t *eir_data, size_t eir_length)
{
struct btd_device *device;
char peer_addr[18], local_addr[18];
const char *icon, *paddr = peer_addr;
dbus_bool_t paired = FALSE;
dbus_int16_t rssi = dev->rssi;
char *alias;
size_t uuid_count;
ba2str(&dev->bdaddr, peer_addr);
ba2str(&adapter->bdaddr, local_addr);
device = adapter_find_device(adapter, paddr);
if (device)
paired = device_is_paired(device);
/* Extract UUIDs from extended inquiry response if any */
dev->services = get_eir_uuids(eir_data, eir_length, dev->services);
uuid_count = g_slist_length(dev->services);
if (dev->services && dev->uuid_count != uuid_count) {
g_strfreev(dev->uuids);
dev->uuids = strlist2array(dev->services);
dev->uuid_count = uuid_count;
}
if (dev->le) {
emit_device_found(adapter->path, paddr,
"Address", DBUS_TYPE_STRING, &paddr,
"RSSI", DBUS_TYPE_INT16, &rssi,
"Name", DBUS_TYPE_STRING, &dev->name,
"Paired", DBUS_TYPE_BOOLEAN, &paired,
"UUIDs", DBUS_TYPE_ARRAY, &dev->uuids,
dev->uuid_count, NULL);
return;
}
icon = class_to_icon(dev->class);
if (!dev->alias) {
if (!dev->name) {
alias = g_strdup(peer_addr);
g_strdelimit(alias, ":", '-');
} else
alias = g_strdup(dev->name);
} else
alias = g_strdup(dev->alias);
emit_device_found(adapter->path, paddr,
"Address", DBUS_TYPE_STRING, &paddr,
"Class", DBUS_TYPE_UINT32, &dev->class,
"Icon", DBUS_TYPE_STRING, &icon,
"RSSI", DBUS_TYPE_INT16, &rssi,
"Name", DBUS_TYPE_STRING, &dev->name,
"Alias", DBUS_TYPE_STRING, &alias,
"LegacyPairing", DBUS_TYPE_BOOLEAN, &dev->legacy,
"Paired", DBUS_TYPE_BOOLEAN, &paired,
"UUIDs", DBUS_TYPE_ARRAY, &dev->uuids, dev->uuid_count,
NULL);
g_free(alias);
}
static struct remote_dev_info *get_found_dev(struct btd_adapter *adapter,
const bdaddr_t *bdaddr,
gboolean *new_dev)
{
struct remote_dev_info *dev, match;
memset(&match, 0, sizeof(struct remote_dev_info));
bacpy(&match.bdaddr, bdaddr);
match.name_status = NAME_ANY;
dev = adapter_search_found_devices(adapter, &match);
if (dev) {
*new_dev = FALSE;
/* Out of range list update */
adapter->oor_devices = g_slist_remove(adapter->oor_devices,
dev);
} else {
*new_dev = TRUE;
dev = g_new0(struct remote_dev_info, 1);
bacpy(&dev->bdaddr, bdaddr);
adapter->found_devices = g_slist_prepend(adapter->found_devices,
dev);
}
return dev;
}
static gboolean extract_eir_flags(uint8_t *flags, uint8_t *eir_data)
{
if (eir_data[0] == 0)
return FALSE;
if (eir_data[1] != EIR_FLAGS)
return FALSE;
/* For now, only one octet is used for flags */
if (flags)
*flags = eir_data[2];
return TRUE;
}
void adapter_update_device_from_info(struct btd_adapter *adapter,
le_advertising_info *info)
{
struct remote_dev_info *dev;
bdaddr_t bdaddr;
gboolean new_dev;
int8_t rssi;
rssi = *(info->data + info->length);
bdaddr = info->bdaddr;
dev = get_found_dev(adapter, &bdaddr, &new_dev);
if (new_dev) {
dev->le = TRUE;
dev->evt_type = info->evt_type;
} else if (dev->rssi == rssi)
return;
dev->rssi = rssi;
adapter->found_devices = g_slist_sort(adapter->found_devices,
(GCompareFunc) dev_rssi_cmp);
if (info->length) {
char *tmp_name = bt_extract_eir_name(info->data, NULL);
if (tmp_name) {
g_free(dev->name);
dev->name = tmp_name;
}
extract_eir_flags(info->data, &dev->flags);
}
/* FIXME: check if other information was changed before emitting the
* signal */
adapter_emit_device_found(adapter, dev, info->data, info->length);
}
void adapter_update_found_devices(struct btd_adapter *adapter, bdaddr_t *bdaddr,
int8_t rssi, uint32_t class, const char *name,
const char *alias, gboolean legacy,
name_status_t name_status, uint8_t *eir_data)
{
struct remote_dev_info *dev;
gboolean new_dev;
dev = get_found_dev(adapter, bdaddr, &new_dev);
if (new_dev) {
if (name)
dev->name = g_strdup(name);
if (alias)
dev->alias = g_strdup(alias);
dev->le = FALSE;
dev->class = class;
dev->legacy = legacy;
dev->name_status = name_status;
} else if (dev->rssi == rssi)
return;
dev->rssi = rssi;
adapter->found_devices = g_slist_sort(adapter->found_devices,
(GCompareFunc) dev_rssi_cmp);
adapter_emit_device_found(adapter, dev, eir_data, EIR_DATA_LENGTH);
}
int adapter_remove_found_device(struct btd_adapter *adapter, bdaddr_t *bdaddr)
{
struct remote_dev_info *dev, match;
memset(&match, 0, sizeof(struct remote_dev_info));
bacpy(&match.bdaddr, bdaddr);
dev = adapter_search_found_devices(adapter, &match);
if (!dev)
return -1;
dev->name_status = NAME_NOT_REQUIRED;
return 0;
}
void adapter_mode_changed(struct btd_adapter *adapter, uint8_t scan_mode)
{
const gchar *path = adapter_get_path(adapter);
gboolean discoverable, pairable;
if (adapter->scan_mode == scan_mode)
return;
adapter_remove_discov_timeout(adapter);
switch (scan_mode) {
case SCAN_DISABLED:
adapter->mode = MODE_OFF;
discoverable = FALSE;
pairable = FALSE;
break;
case SCAN_PAGE:
adapter->mode = MODE_CONNECTABLE;
discoverable = FALSE;
pairable = adapter->pairable;
break;
case (SCAN_PAGE | SCAN_INQUIRY):
adapter->mode = MODE_DISCOVERABLE;
discoverable = TRUE;
pairable = adapter->pairable;
if (adapter->discov_timeout != 0)
adapter_set_discov_timeout(adapter,
adapter->discov_timeout);
break;
case SCAN_INQUIRY:
/* Address the scenario where a low-level application like
* hciconfig changed the scan mode */
if (adapter->discov_timeout != 0)
adapter_set_discov_timeout(adapter,
adapter->discov_timeout);
/* ignore, this event should not be sent */
default:
/* ignore, reserved */
return;
}
/* If page scanning gets toggled emit the Pairable property */
if ((adapter->scan_mode & SCAN_PAGE) != (scan_mode & SCAN_PAGE))
emit_property_changed(connection, adapter->path,
ADAPTER_INTERFACE, "Pairable",
DBUS_TYPE_BOOLEAN, &pairable);
if (!discoverable)
adapter_set_limited_discoverable(adapter, FALSE);
emit_property_changed(connection, path,
ADAPTER_INTERFACE, "Discoverable",
DBUS_TYPE_BOOLEAN, &discoverable);
adapter->scan_mode = scan_mode;
set_mode_complete(adapter);
}
struct agent *adapter_get_agent(struct btd_adapter *adapter)
{
if (!adapter || !adapter->agent)
return NULL;
return adapter->agent;
}
void adapter_add_connection(struct btd_adapter *adapter,
struct btd_device *device, uint16_t handle)
{
if (g_slist_find(adapter->connections, device)) {
error("Unable to add connection %d", handle);
return;
}
device_add_connection(device, connection, handle);
adapter->connections = g_slist_append(adapter->connections, device);
}
void adapter_remove_connection(struct btd_adapter *adapter,
struct btd_device *device, uint16_t handle)
{
bdaddr_t bdaddr;
if (!g_slist_find(adapter->connections, device)) {
error("No matching connection for handle %u", handle);
return;
}
device_remove_connection(device, connection, handle);
adapter->connections = g_slist_remove(adapter->connections, device);
/* clean pending HCI cmds */
device_get_address(device, &bdaddr);
if (device_is_authenticating(device))
device_cancel_authentication(device, TRUE);
if (device_is_temporary(device)) {
const char *path = device_get_path(device);
DBG("Removing temporary device %s", path);
adapter_remove_device(connection, adapter, device, TRUE);
}
}
gboolean adapter_has_discov_sessions(struct btd_adapter *adapter)
{
if (!adapter || !adapter->disc_sessions)
return FALSE;
return TRUE;
}
void adapter_suspend_discovery(struct btd_adapter *adapter)
{
if (adapter->disc_sessions == NULL ||
adapter->state & STATE_SUSPENDED)
return;
DBG("Suspending discovery");
stop_discovery(adapter, TRUE);
adapter->state |= STATE_SUSPENDED;
}
void adapter_resume_discovery(struct btd_adapter *adapter)
{
if (adapter->disc_sessions == NULL)
return;
DBG("Resuming discovery");
adapter->state &= ~STATE_SUSPENDED;
start_discovery(adapter);
}
int btd_register_adapter_driver(struct btd_adapter_driver *driver)
{
GSList *adapters;
adapter_drivers = g_slist_append(adapter_drivers, driver);
if (driver->probe == NULL)
return 0;
adapters = manager_get_adapters();
g_slist_foreach(adapters, probe_driver, driver);
return 0;
}
void btd_unregister_adapter_driver(struct btd_adapter_driver *driver)
{
adapter_drivers = g_slist_remove(adapter_drivers, driver);
}
static void agent_auth_cb(struct agent *agent, DBusError *derr,
void *user_data)
{
struct service_auth *auth = user_data;
device_set_authorizing(auth->device, FALSE);
auth->cb(derr, auth->user_data);
}
static gboolean auth_idle_cb(gpointer user_data)
{
struct service_auth *auth = user_data;
struct btd_adapter *adapter = auth->adapter;
adapter->auth_idle_id = 0;
auth->cb(NULL, auth->user_data);
return FALSE;
}
static int btd_adapter_authorize(struct btd_adapter *adapter,
const bdaddr_t *dst,
const char *uuid,
service_auth_cb cb, void *user_data)
{
struct service_auth *auth;
struct btd_device *device;
struct agent *agent;
char address[18];
const gchar *dev_path;
int err;
ba2str(dst, address);
device = adapter_find_device(adapter, address);
if (!device)
return -EPERM;
/* Device connected? */
if (!g_slist_find(adapter->connections, device))
return -ENOTCONN;
if (adapter->auth_idle_id)
return -EBUSY;
auth = g_try_new0(struct service_auth, 1);
if (!auth)
return -ENOMEM;
auth->cb = cb;
auth->user_data = user_data;
auth->device = device;
auth->adapter = adapter;
if (device_is_trusted(device) == TRUE) {
adapter->auth_idle_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE,
auth_idle_cb, auth,
g_free);
return 0;
}
agent = device_get_agent(device);
if (!agent) {
g_free(auth);
return -EPERM;
}
dev_path = device_get_path(device);
err = agent_authorize(agent, dev_path, uuid, agent_auth_cb, auth, g_free);
if (err < 0)
g_free(auth);
else
device_set_authorizing(device, TRUE);
return err;
}
int btd_request_authorization(const bdaddr_t *src, const bdaddr_t *dst,
const char *uuid, service_auth_cb cb, void *user_data)
{
struct btd_adapter *adapter;
GSList *adapters;
if (src == NULL || dst == NULL)
return -EINVAL;
if (bacmp(src, BDADDR_ANY) != 0)
goto proceed;
/* Handle request authorization for ANY adapter */
adapters = manager_get_adapters();
for (; adapters; adapters = adapters->next) {
int err;
adapter = adapters->data;
err = btd_adapter_authorize(adapter, dst, uuid, cb, user_data);
if (err == 0)
return 0;
}
return -EPERM;
proceed:
adapter = manager_find_adapter(src);
if (!adapter)
return -EPERM;
return btd_adapter_authorize(adapter, dst, uuid, cb, user_data);
}
int btd_cancel_authorization(const bdaddr_t *src, const bdaddr_t *dst)
{
struct btd_adapter *adapter = manager_find_adapter(src);
struct btd_device *device;
struct agent *agent;
char address[18];
int err;
if (!adapter)
return -EPERM;
ba2str(dst, address);
device = adapter_find_device(adapter, address);
if (!device)
return -EPERM;
if (adapter->auth_idle_id) {
g_source_remove(adapter->auth_idle_id);
adapter->auth_idle_id = 0;
return 0;
}
/*
* FIXME: Cancel fails if authorization is requested to adapter's
* agent and in the meanwhile CreatePairedDevice is called.
*/
agent = device_get_agent(device);
if (!agent)
return -EPERM;
err = agent_cancel(agent);
if (err == 0)
device_set_authorizing(device, FALSE);
return err;
}
static gchar *adapter_any_path = NULL;
static int adapter_any_refcount = 0;
const char *adapter_any_get_path(void)
{
return adapter_any_path;
}
const char *btd_adapter_any_request_path(void)
{
if (adapter_any_refcount > 0)
return adapter_any_path;
adapter_any_path = g_strdup_printf("%s/any", manager_get_base_path());
adapter_any_refcount++;
return adapter_any_path;
}
void btd_adapter_any_release_path(void)
{
adapter_any_refcount--;
if (adapter_any_refcount > 0)
return;
g_free(adapter_any_path);
adapter_any_path = NULL;
}
gboolean adapter_is_pairable(struct btd_adapter *adapter)
{
return adapter->pairable;
}
gboolean adapter_powering_down(struct btd_adapter *adapter)
{
return adapter->off_requested;
}
int btd_adapter_restore_powered(struct btd_adapter *adapter)
{
char mode[14], address[18];
gboolean discoverable;
if (!adapter_ops)
return -EINVAL;
if (!main_opts.remember_powered)
return -EINVAL;
if (adapter->up)
return 0;
ba2str(&adapter->bdaddr, address);
if (read_device_mode(address, mode, sizeof(mode)) == 0 &&
g_str_equal(mode, "off"))
return 0;
discoverable = get_mode(&adapter->bdaddr, mode) == MODE_DISCOVERABLE;
return adapter_ops->power_on(adapter->dev_id, discoverable);
}
int btd_adapter_switch_online(struct btd_adapter *adapter)
{
gboolean discoverable;
if (!adapter_ops)
return -EINVAL;
if (adapter->up)
return 0;
discoverable = get_mode(&adapter->bdaddr, "on") == MODE_DISCOVERABLE;
return adapter_ops->power_on(adapter->dev_id, discoverable);
}
int btd_adapter_switch_offline(struct btd_adapter *adapter)
{
if (!adapter_ops)
return -EINVAL;
if (!adapter->up)
return 0;
return adapter_ops->power_off(adapter->dev_id);
}
int btd_register_adapter_ops(struct btd_adapter_ops *ops, gboolean priority)
{
if (ops->setup == NULL)
return -EINVAL;
if (priority)
ops_candidates = g_slist_prepend(ops_candidates, ops);
else
ops_candidates = g_slist_append(ops_candidates, ops);
return 0;
}
void btd_adapter_cleanup_ops(struct btd_adapter_ops *ops)
{
ops_candidates = g_slist_remove(ops_candidates, ops);
ops->cleanup();
if (adapter_ops == ops)
adapter_ops = NULL;
}
int adapter_ops_setup(void)
{
GSList *l;
int ret;
if (!ops_candidates)
return -EINVAL;
for (l = ops_candidates; l != NULL; l = g_slist_next(l)) {
struct btd_adapter_ops *ops = l->data;
ret = ops->setup();
if (ret < 0)
continue;
adapter_ops = ops;
break;
}
return ret;
}
void btd_adapter_register_powered_callback(struct btd_adapter *adapter,
btd_adapter_powered_cb cb)
{
adapter->powered_callbacks =
g_slist_append(adapter->powered_callbacks, cb);
}
void btd_adapter_unregister_powered_callback(struct btd_adapter *adapter,
btd_adapter_powered_cb cb)
{
adapter->powered_callbacks =
g_slist_remove(adapter->powered_callbacks, cb);
}
int btd_adapter_set_fast_connectable(struct btd_adapter *adapter,
gboolean enable)
{
if (!adapter_ops)
return -EINVAL;
if (!adapter->up)
return -EINVAL;
return adapter_ops->set_fast_connectable(adapter->dev_id, enable);
}
int btd_adapter_read_clock(struct btd_adapter *adapter, int handle, int which,
int timeout, uint32_t *clock, uint16_t *accuracy)
{
if (!adapter_ops)
return -EINVAL;
if (!adapter->up)
return -EINVAL;
return adapter_ops->read_clock(adapter->dev_id, handle, which,
timeout, clock, accuracy);
}
int btd_adapter_get_conn_handle(struct btd_adapter *adapter,
const bdaddr_t *bdaddr, int *handle)
{
if (!adapter_ops)
return -EINVAL;
if (!adapter->up)
return -EINVAL;
return adapter_ops->get_conn_handle(adapter->dev_id, bdaddr, handle);
}
int btd_adapter_disconnect_device(struct btd_adapter *adapter, uint16_t handle)
{
return adapter_ops->disconnect(adapter->dev_id, handle);
}
int btd_adapter_remove_bonding(struct btd_adapter *adapter, bdaddr_t *bdaddr)
{
return adapter_ops->remove_bonding(adapter->dev_id, bdaddr);
}
int btd_adapter_request_authentication(struct btd_adapter *adapter,
uint16_t handle)
{
return adapter_ops->request_authentication(adapter->dev_id, handle);
}
int btd_adapter_pincode_reply(struct btd_adapter *adapter, bdaddr_t *bdaddr,
const char *pin)
{
return adapter_ops->pincode_reply(adapter->dev_id, bdaddr, pin);
}
int btd_adapter_confirm_reply(struct btd_adapter *adapter, bdaddr_t *bdaddr,
gboolean success)
{
return adapter_ops->confirm_reply(adapter->dev_id, bdaddr, success);
}
int btd_adapter_passkey_reply(struct btd_adapter *adapter, bdaddr_t *bdaddr,
uint32_t passkey)
{
return adapter_ops->passkey_reply(adapter->dev_id, bdaddr, passkey);
}
int btd_adapter_get_auth_info(struct btd_adapter *adapter, bdaddr_t *bdaddr,
uint8_t *auth)
{
return adapter_ops->get_auth_info(adapter->dev_id, bdaddr, auth);
}
int btd_adapter_read_scan_enable(struct btd_adapter *adapter)
{
return adapter_ops->read_scan_enable(adapter->dev_id);
}
void btd_adapter_update_local_ext_features(struct btd_adapter *adapter,
const uint8_t *features)
{
struct hci_dev *dev = &adapter->dev;
memcpy(dev->extfeatures, features, 8);
}
int btd_adapter_get_remote_name(struct btd_adapter *adapter, bdaddr_t *bdaddr)
{
return adapter_ops->resolve_name(adapter->dev_id, bdaddr);
}
int btd_adapter_get_remote_version(struct btd_adapter *adapter,
uint16_t handle, gboolean delayed)
{
return adapter_ops->get_remote_version(adapter->dev_id, handle,
delayed);
}
int btd_adapter_encrypt_link(struct btd_adapter *adapter, bdaddr_t *bdaddr,
bt_hci_result_t cb, gpointer user_data)
{
return adapter_ops->encrypt_link(adapter->dev_id, bdaddr, cb, user_data);
}
int btd_adapter_set_did(struct btd_adapter *adapter, uint16_t vendor,
uint16_t product, uint16_t version)
{
return adapter_ops->set_did(adapter->dev_id, vendor, product, version);
}
int btd_adapter_services_updated(struct btd_adapter *adapter)
{
return adapter_ops->services_updated(adapter->dev_id);
}