blob: 4d38930710acbbe2e2509c27eafd550476d6791d [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/hci.h>
#include <bluetooth/hci_lib.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 "dbus-hci.h"
#include "error.h"
#include "glib-helper.h"
#include "agent.h"
#include "storage.h"
#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
/* Limited Discoverable bit mask in CoD */
#define LIMITED_BIT 0x002000
#define check_address(address) bachk(address)
static DBusConnection *connection = NULL;
static GSList *adapter_drivers = 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 */
};
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 */
guint discov_timeout_id; /* discoverable timeout 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
* resloving */
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 */
int8_t tx_power; /* inq response tx power level */
gboolean pairable; /* pairable state */
gboolean initialized;
gboolean already_up; /* adapter was already up on init */
gboolean off_requested; /* DEVDOWN ioctl was called */
uint32_t current_cod; /* Adapter's current class */
uint32_t pending_cod;
uint32_t wanted_cod; /* CoD cache */
gboolean cache_enable;
gint ref;
};
static void adapter_set_pairable_timeout(struct btd_adapter *adapter,
guint interval);
static inline DBusMessage *invalid_args(DBusMessage *msg)
{
return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments",
"Invalid arguments in method call");
}
static inline DBusMessage *adapter_not_ready(DBusMessage *msg)
{
return g_dbus_create_error(msg, ERROR_INTERFACE ".NotReady",
"Adapter is not ready");
}
static inline DBusMessage *failed_strerror(DBusMessage *msg, int err)
{
return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
strerror(err));
}
static inline DBusMessage *not_in_progress(DBusMessage *msg, const char *str)
{
return g_dbus_create_error(msg, ERROR_INTERFACE ".NotInProgress", str);
}
static inline DBusMessage *not_authorized(DBusMessage *msg)
{
return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAuthorized",
"Not authorized");
}
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_free(dev);
}
void clear_found_devices_list(struct btd_adapter *adapter)
{
if (!adapter->found_devices)
return;
g_slist_foreach(adapter->found_devices, (GFunc) dev_info_free, NULL);
g_slist_free(adapter->found_devices);
adapter->found_devices = NULL;
}
static int adapter_set_service_classes(struct btd_adapter *adapter,
uint8_t value)
{
int err;
/* Update only the service class, keep the limited bit,
* major/minor class bits intact */
adapter->wanted_cod &= 0x00ffff;
adapter->wanted_cod |= (value << 16);
/* If we already have the CoD we want or the cache is enabled or an
* existing CoD write is in progress just bail out */
if (adapter->current_cod == adapter->wanted_cod ||
adapter->cache_enable || adapter->pending_cod)
return 0;
DBG("Changing service classes to 0x%06x", adapter->wanted_cod);
err = adapter_ops->set_class(adapter->dev_id, adapter->wanted_cod);
if (err < 0)
error("Adapter class update failed: %s(%d)",
strerror(err), err);
else
adapter->pending_cod = adapter->wanted_cod;
return err;
}
int btd_adapter_set_class(struct btd_adapter *adapter, uint8_t major,
uint8_t minor)
{
int err;
/* Update only the major and minor class bits keeping remaining bits
* intact*/
adapter->wanted_cod &= 0xffe000;
adapter->wanted_cod |= ((major & 0x1f) << 8) | minor;
if (adapter->wanted_cod == adapter->current_cod ||
adapter->cache_enable || adapter->pending_cod)
return 0;
DBG("Changing Major/Minor class to 0x%06x", adapter->wanted_cod);
err = adapter_ops->set_class(adapter->dev_id, adapter->wanted_cod);
if (err < 0)
error("Adapter class update failed: %s(%d)",
strerror(err), err);
else
adapter->pending_cod = adapter->wanted_cod;
return err;
}
int pending_remote_name_cancel(struct btd_adapter *adapter)
{
struct remote_dev_info *dev, match;
int err = 0;
/* 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:
case MODE_LIMITED:
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("limited", mode) == 0)
return MODE_LIMITED;
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_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);
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)
return;
adapter->discov_timeout_id = g_timeout_add_seconds(interval,
discov_timeout_handler,
adapter);
}
static void adapter_set_limited_discoverable(struct btd_adapter *adapter,
gboolean limited)
{
/* Check if limited bit needs to be set/reset */
if (limited)
adapter->wanted_cod |= LIMITED_BIT;
else
adapter->wanted_cod &= ~LIMITED_BIT;
/* If we dont need the toggling, save an unnecessary CoD write */
if (adapter->pending_cod ||
adapter->wanted_cod == adapter->current_cod)
return;
if (adapter_ops->set_limited_discoverable(adapter->dev_id,
adapter->wanted_cod, limited) == 0)
adapter->pending_cod = adapter->wanted_cod;
}
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 set_mode(struct btd_adapter *adapter, uint8_t new_mode,
DBusMessage *msg)
{
int err;
if (adapter->pending_mode != NULL)
return -EALREADY;
if (!adapter->up && new_mode != MODE_OFF) {
err = adapter_ops->set_powered(adapter->dev_id, TRUE);
if (err < 0)
return err;
}
if (adapter->up && new_mode == MODE_OFF) {
err = adapter_ops->set_powered(adapter->dev_id, FALSE);
if (err < 0)
return err;
adapter->off_requested = TRUE;
goto done;
}
if (new_mode == adapter->mode)
return 0;
if (new_mode == MODE_CONNECTABLE)
err = adapter_ops->set_connectable(adapter->dev_id);
else
err = adapter_ops->set_discoverable(adapter->dev_id);
if (err < 0)
return err;
if (new_mode > MODE_CONNECTABLE) {
adapter_remove_discov_timeout(adapter);
if (adapter->discov_timeout)
adapter_set_discov_timeout(adapter,
adapter->discov_timeout);
if (new_mode != MODE_LIMITED && adapter->mode == MODE_LIMITED)
adapter_set_limited_discoverable(adapter, FALSE);
}
done:
DBG("%s", mode2str(new_mode));
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 */
const char *modestr = mode2str(new_mode);
adapter->mode = new_mode;
write_device_mode(&adapter->bdaddr, modestr);
}
return 0;
}
static DBusMessage *set_powered(DBusConnection *conn, DBusMessage *msg,
gboolean powered, void *data)
{
struct btd_adapter *adapter = data;
uint8_t mode;
int err;
mode = powered ? get_mode(&adapter->bdaddr, "on") : MODE_OFF;
if (mode == adapter->mode)
return dbus_message_new_method_return(msg);
err = set_mode(adapter, mode, NULL);
if (err < 0)
return failed_strerror(msg, -err);
return dbus_message_new_method_return(msg);
}
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 == MODE_DISCOVERABLE && adapter->pairable &&
adapter->discov_timeout > 0 &&
adapter->discov_timeout <= 60)
mode = MODE_LIMITED;
if (mode == adapter->mode)
return dbus_message_new_method_return(msg);
err = set_mode(adapter, mode, msg);
if (err < 0)
return failed_strerror(msg, -err);
return 0;
}
static DBusMessage *set_pairable(DBusConnection *conn, DBusMessage *msg,
gboolean pairable, void *data)
{
struct btd_adapter *adapter = data;
uint8_t mode;
int err;
if (adapter->scan_mode == SCAN_DISABLED)
return adapter_not_ready(msg);
if (pairable == adapter->pairable)
goto done;
if (!(adapter->scan_mode & SCAN_INQUIRY))
goto store;
mode = (pairable && adapter->discov_timeout > 0 &&
adapter->discov_timeout <= 60) ?
MODE_LIMITED : MODE_DISCOVERABLE;
err = set_mode(adapter, mode, NULL);
if (err < 0 && msg)
return failed_strerror(msg, -err);
store:
adapter->pairable = 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 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");
pending_remote_name_cancel(adapter);
clear_found_devices_list(adapter);
g_slist_free(adapter->oor_devices);
adapter->oor_devices = NULL;
if (adapter->scheduler_id) {
g_source_remove(adapter->scheduler_id);
adapter->scheduler_id = 0;
}
adapter_ops->stop_discovery(adapter->dev_id);
}
}
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->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;
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 = failed_strerror(req->msg, -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);
}
static void update_ext_inquiry_response(struct btd_adapter *adapter)
{
uint8_t fec = 0, data[240];
struct hci_dev *dev = &adapter->dev;
int dd;
if (!(dev->features[6] & LMP_EXT_INQ))
return;
memset(data, 0, sizeof(data));
dd = hci_open_dev(adapter->dev_id);
if (dd < 0)
return;
if (dev->ssp_mode > 0)
create_ext_inquiry_response(dev->name, adapter->tx_power,
adapter->services, data);
if (hci_write_ext_inquiry_response(dd, fec, data,
HCI_REQ_TIMEOUT) < 0)
error("Can't write extended inquiry response: %s (%d)",
strerror(errno), errno);
hci_close_dev(dd);
}
void adapter_set_class_complete(bdaddr_t *bdaddr, uint8_t status)
{
uint8_t class[3];
struct btd_adapter *adapter;
int err;
if (status)
return;
adapter = manager_find_adapter(bdaddr);
if (!adapter) {
error("Unable to find matching adapter");
return;
}
if (adapter->pending_cod == 0)
return;
adapter->current_cod = adapter->pending_cod;
adapter->pending_cod = 0;
class[2] = (adapter->current_cod >> 16) & 0xff;
class[1] = (adapter->current_cod >> 8) & 0xff;
class[0] = adapter->current_cod & 0xff;
write_local_class(&adapter->bdaddr, class);
emit_property_changed(connection, adapter->path,
ADAPTER_INTERFACE, "Class",
DBUS_TYPE_UINT32, &adapter->current_cod);
update_ext_inquiry_response(adapter);
if (adapter->wanted_cod == adapter->current_cod)
return;
if (adapter->wanted_cod & LIMITED_BIT &&
!(adapter->current_cod & LIMITED_BIT))
err = adapter_ops->set_limited_discoverable(adapter->dev_id,
adapter->wanted_cod, TRUE);
else if (!(adapter->wanted_cod & LIMITED_BIT) &&
adapter->current_cod & LIMITED_BIT)
err = adapter_ops->set_limited_discoverable(adapter->dev_id,
adapter->wanted_cod, FALSE);
else
err = adapter_ops->set_class(adapter->dev_id,
adapter->wanted_cod);
if (err == 0)
adapter->pending_cod = adapter->wanted_cod;
}
void adapter_update_tx_power(bdaddr_t *bdaddr, uint8_t status, void *ptr)
{
struct btd_adapter *adapter;
if (status)
return;
adapter = manager_find_adapter(bdaddr);
if (!adapter) {
error("Unable to find matching adapter");
return;
}
adapter->tx_power = *((int8_t *) ptr);
DBG("inquiry respone tx power level is %d", adapter->tx_power);
update_ext_inquiry_response(adapter);
}
void adapter_update_local_name(bdaddr_t *bdaddr, uint8_t status, void *ptr)
{
read_local_name_rp rp;
struct hci_dev *dev;
struct btd_adapter *adapter;
gchar *name;
if (status)
return;
adapter = manager_find_adapter(bdaddr);
if (!adapter) {
error("Unable to find matching adapter");
return;
}
dev = &adapter->dev;
memcpy(&rp, ptr, MAX_NAME_LENGTH);
if (strncmp((char *) rp.name, dev->name, MAX_NAME_LENGTH) == 0)
return;
strncpy(dev->name, (char *) rp.name, MAX_NAME_LENGTH);
write_local_name(bdaddr, dev->name);
update_ext_inquiry_response(adapter);
name = g_strdup(dev->name);
if (connection)
emit_property_changed(connection, adapter->path,
ADAPTER_INTERFACE, "Name",
DBUS_TYPE_STRING, &name);
g_free(name);
}
void adapter_setname_complete(bdaddr_t *local, uint8_t status)
{
struct btd_adapter *adapter;
int err;
if (status)
return;
adapter = manager_find_adapter(local);
if (!adapter) {
error("No matching adapter found");
return;
}
err = adapter_ops->read_name(adapter->dev_id);
if (err < 0)
error("Sending getting name command failed: %s (%d)",
strerror(errno), errno);
}
static DBusMessage *set_name(DBusConnection *conn, DBusMessage *msg,
const char *name, void *data)
{
struct btd_adapter *adapter = data;
struct hci_dev *dev = &adapter->dev;
if (!g_utf8_validate(name, -1, NULL)) {
error("Name change failed: supplied name isn't valid UTF-8");
return invalid_args(msg);
}
if (strncmp(name, dev->name, MAX_NAME_LENGTH) == 0)
goto done;
if (!adapter->up) {
strncpy(dev->name, name, MAX_NAME_LENGTH);
write_local_name(&adapter->bdaddr, name);
} else {
int err = adapter_ops->set_name(adapter->dev_id, name);
if (err < 0)
return failed_strerror(msg, err);
}
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);
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, i++) {
sdp_record_t *rec = list->data;
uuids[i] = bt_uuid2string(&rec->svclass);
}
emit_array_property_changed(connection, adapter->path,
ADAPTER_INTERFACE, "UUIDs", DBUS_TYPE_STRING, &uuids);
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;
}
struct btd_device *adapter_create_device(DBusConnection *conn,
struct btd_adapter *adapter,
const char *address)
{
struct btd_device *device;
const char *path;
DBG("%s", address);
device = device_create(conn, adapter, address);
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);
}
static int adapter_start_inquiry(struct btd_adapter *adapter)
{
gboolean periodic = TRUE;
if (main_opts.discov_interval)
periodic = FALSE;
pending_remote_name_cancel(adapter);
return adapter_ops->start_discovery(adapter->dev_id, periodic);
}
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 adapter_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;
if (main_opts.name_resolv)
adapter->state |= RESOLVE_NAME;
err = adapter_start_inquiry(adapter);
if (err < 0)
return failed_strerror(msg, -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 adapter_not_ready(msg);
req = find_session(adapter->disc_sessions, sender);
if (!req)
return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
"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 adapter_not_ready(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->current_cod);
/* 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 & PERIODIC_INQUIRY || adapter->state & STD_INQUIRY)
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, i++) {
sdp_record_t *rec = list->data;
uuids[i] = bt_uuid2string(&rec->svclass);
}
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 invalid_args(msg);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
return 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 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 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 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 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 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 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 invalid_args(msg);
dbus_message_iter_get_basic(&sub, &timeout);
return set_pairable_timeout(conn, msg, timeout, data);
}
return 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 g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
"No agent registered");
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 failed_strerror(msg, -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 g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
"No Mode to release");
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 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 invalid_args(msg);
if (check_address(address) < 0)
return invalid_args(msg);
device = adapter_find_device(adapter, address);
if (!device || !device_is_creating(device, NULL))
return not_in_progress(msg, "Device creation not in progress");
if (!device_is_creating(device, sender))
return 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 DBusMessage *create_device(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct btd_adapter *adapter = data;
struct btd_device *device;
const gchar *address;
if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address,
DBUS_TYPE_INVALID) == FALSE)
return invalid_args(msg);
if (check_address(address) < 0)
return invalid_args(msg);
if (adapter_find_device(adapter, address))
return g_dbus_create_error(msg,
ERROR_INTERFACE ".AlreadyExists",
"Device already exists");
DBG("%s", address);
device = adapter_create_device(conn, adapter, address);
if (!device)
return NULL;
device_browse(device, conn, msg, NULL, FALSE);
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_generic(DBusConnection *conn,
DBusMessage *msg, void *data, const gboolean oob)
{
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 invalid_args(msg);
if (check_address(address) < 0)
return invalid_args(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 invalid_args(msg);
}
cap = parse_io_capability(capability);
if (cap == IO_CAPABILITY_INVALID)
return invalid_args(msg);
device = adapter_get_device(conn, adapter, address);
if (!device)
return g_dbus_create_error(msg,
ERROR_INTERFACE ".Failed",
"Unable to create a new device object");
return device_create_bonding(device, conn, msg, agent_path, cap, oob);
}
static DBusMessage *create_paired_device_oob(DBusConnection *conn,
DBusMessage *msg, void *data)
{
return create_paired_device_generic(conn, msg, data, TRUE);
}
static DBusMessage *create_paired_device(DBusConnection *conn,
DBusMessage *msg, void *data)
{
return create_paired_device_generic(conn, msg, data, FALSE);
}
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 invalid_args(msg);
l = g_slist_find_custom(adapter->devices,
path, (GCompareFunc) device_path_cmp);
if (!l)
return g_dbus_create_error(msg,
ERROR_INTERFACE ".DoesNotExist",
"Device does not exist");
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 invalid_args(msg);
l = g_slist_find_custom(adapter->devices,
address, (GCompareFunc) device_address_cmp);
if (!l)
return g_dbus_create_error(msg,
ERROR_INTERFACE ".DoesNotExist",
"Device does not exist");
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_generic(DBusConnection *conn, DBusMessage *msg,
void *data, const char *path, const char *capability,
const gboolean oob)
{
const char *name;
struct agent *agent;
struct btd_adapter *adapter = data;
uint8_t cap;
if (adapter->agent)
return g_dbus_create_error(msg,
ERROR_INTERFACE ".AlreadyExists",
"Agent already exists");
cap = parse_io_capability(capability);
if (cap == IO_CAPABILITY_INVALID)
return invalid_args(msg);
name = dbus_message_get_sender(msg);
agent = agent_create(adapter, name, path, cap, oob,
(agent_remove_cb) agent_removed, adapter);
if (!agent)
return g_dbus_create_error(msg,
ERROR_INTERFACE ".Failed",
"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 *register_agent_oob(DBusConnection *conn, DBusMessage *msg,
void *data)
{
const char *path, *capability;
gboolean oob;
if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
DBUS_TYPE_STRING, &capability,
DBUS_TYPE_BOOLEAN, &oob, DBUS_TYPE_INVALID))
return NULL;
return register_agent_generic(conn, msg, data, path, capability, oob);
}
static DBusMessage *register_agent(DBusConnection *conn, DBusMessage *msg,
void *data)
{
const char *path, *capability;
if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
DBUS_TYPE_STRING, &capability, DBUS_TYPE_INVALID))
return NULL;
return register_agent_generic(conn, msg, data, path, capability, FALSE);
}
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 g_dbus_create_error(msg,
ERROR_INTERFACE ".DoesNotExist",
"No such agent");
agent_free(adapter->agent);
adapter->agent = NULL;
return dbus_message_new_method_return(msg);
}
static sdp_record_t *create_rfcomm_record(struct btd_adapter *adapter,
const char *name, uuid_t uuid, uint8_t channel)
{
uuid_t root_uuid, l2cap_uuid, rfcomm_uuid;
sdp_list_t *svclass, *root, *proto;
sdp_record_t *record;
record = sdp_record_alloc();
if (!record)
return NULL;
sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
root = sdp_list_append(NULL, &root_uuid);
sdp_set_browse_groups(record, root);
sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
proto = sdp_list_append(NULL, sdp_list_append(NULL, &l2cap_uuid));
sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
proto = sdp_list_append(proto, sdp_list_append(
sdp_list_append(NULL, &rfcomm_uuid),
sdp_data_alloc(SDP_UINT8, &channel)));
sdp_set_access_protos(record, sdp_list_append(NULL, proto));
svclass = sdp_list_append(NULL, &uuid);
sdp_set_service_classes(record, svclass);
sdp_set_info_attr(record, name, NULL, NULL);
return record;
}
static DBusMessage *add_rfcomm_service_record(DBusConnection *conn,
DBusMessage *msg, void *data)
{
uuid_t uuid;
const char *name;
uint8_t channel;
uint32_t *uuid_p;
uint32_t uuid_net[4]; // network order
uint64_t uuid_host[2]; // host
sdp_record_t *record;
struct btd_adapter *adapter = data;
DBusMessage *reply;
if (!dbus_message_get_args(msg, NULL,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_UINT64, &uuid_host[0],
DBUS_TYPE_UINT64, &uuid_host[1],
DBUS_TYPE_UINT16, &channel,
DBUS_TYPE_INVALID))
return invalid_args(msg);
uuid_p = (uint32_t *)uuid_host;
uuid_net[1] = htonl(*uuid_p++);
uuid_net[0] = htonl(*uuid_p++);
uuid_net[3] = htonl(*uuid_p++);
uuid_net[2] = htonl(*uuid_p++);
sdp_uuid128_create(&uuid, (void *)uuid_net);
record = create_rfcomm_record(adapter, name, uuid, channel);
if (!record)
return g_dbus_create_error(msg,
ERROR_INTERFACE ".Failed",
"Failed to create sdp record");
if (add_record_to_server(&adapter->bdaddr, record))
return g_dbus_create_error(msg,
ERROR_INTERFACE ".Failed",
"Failed to register sdp record");
reply = dbus_message_new_method_return(msg);
dbus_message_append_args(reply,
DBUS_TYPE_UINT32, &record->handle,
DBUS_TYPE_INVALID);
return reply;
}
static DBusMessage *remove_service_record(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct btd_adapter *adapter = data;
dbus_uint32_t handle;
if (!dbus_message_get_args(msg, NULL,
DBUS_TYPE_UINT32, &handle,
DBUS_TYPE_INVALID))
return invalid_args(msg);
if (remove_record_from_server(handle))
return g_dbus_create_error(msg,
ERROR_INTERFACE ".Failed",
"Failed to remove sdp record");
return dbus_message_new_method_return(msg);
}
static int add_headset_ag_record(struct btd_adapter* adapter)
{
sdp_list_t *svclass_id, *pfseq, *apseq, *root;
uuid_t root_uuid, svclass_uuid, ga_svclass_uuid, l2cap_uuid, rfcomm_uuid;
sdp_profile_desc_t profile;
sdp_list_t *aproto, *proto[2];
sdp_record_t *record;
uint8_t u8 = 11;
sdp_data_t *channel;
uint8_t netid = 0x01; // ???? profile document
sdp_data_t *network = sdp_data_alloc(SDP_UINT8, &netid);
int ret = 0;
record = sdp_record_alloc();
if (!record) return -1;
sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
root = sdp_list_append(0, &root_uuid);
sdp_set_browse_groups(record, root);
sdp_uuid16_create(&svclass_uuid, HEADSET_AGW_SVCLASS_ID);
svclass_id = sdp_list_append(0, &svclass_uuid);
sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID);
svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid);
sdp_set_service_classes(record, svclass_id);
sdp_uuid16_create(&profile.uuid, HEADSET_PROFILE_ID);
profile.version = 0x0100;
pfseq = sdp_list_append(0, &profile);
sdp_set_profile_descs(record, pfseq);
sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
proto[0] = sdp_list_append(0, &l2cap_uuid);
apseq = sdp_list_append(0, proto[0]);
sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
proto[1] = sdp_list_append(0, &rfcomm_uuid);
channel = sdp_data_alloc(SDP_UINT8, &u8);
proto[1] = sdp_list_append(proto[1], channel);
apseq = sdp_list_append(apseq, proto[1]);
aproto = sdp_list_append(0, apseq);
sdp_set_access_protos(record, aproto);
sdp_set_info_attr(record, "Voice Gateway", 0, 0);
sdp_attr_add(record, SDP_ATTR_EXTERNAL_NETWORK, network);
if (add_record_to_server(&adapter->bdaddr, record) < 0)
ret = -1;
sdp_data_free(channel);
sdp_list_free(proto[0], 0);
sdp_list_free(proto[1], 0);
sdp_list_free(apseq, 0);
sdp_list_free(aproto, 0);
if (!ret)
return record->handle;
return ret;
}
static int add_handsfree_ag_record(struct btd_adapter* adapter) {
sdp_list_t *svclass_id, *pfseq, *apseq, *root;
uuid_t root_uuid, svclass_uuid, ga_svclass_uuid, l2cap_uuid, rfcomm_uuid;
sdp_profile_desc_t profile;
sdp_list_t *aproto, *proto[2];
sdp_record_t *record;
uint8_t u8 = 10;
uint16_t u16 = 0x17;
#ifdef ANDROID
u16 = 0x07;
#endif
sdp_data_t *channel, *features;
uint8_t netid = 0x01; // ???? profile document
sdp_data_t *network = sdp_data_alloc(SDP_UINT8, &netid);
int ret = 0;
record = sdp_record_alloc();
if (!record) return -1;
sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
root = sdp_list_append(0, &root_uuid);
sdp_set_browse_groups(record, root);
sdp_uuid16_create(&svclass_uuid, HANDSFREE_AGW_SVCLASS_ID);
svclass_id = sdp_list_append(0, &svclass_uuid);
sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID);
svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid);
sdp_set_service_classes(record, svclass_id);
sdp_uuid16_create(&profile.uuid, HANDSFREE_PROFILE_ID);
profile.version = 0x0105;
pfseq = sdp_list_append(0, &profile);
sdp_set_profile_descs(record, pfseq);
sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
proto[0] = sdp_list_append(0, &l2cap_uuid);
apseq = sdp_list_append(0, proto[0]);
sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
proto[1] = sdp_list_append(0, &rfcomm_uuid);
channel = sdp_data_alloc(SDP_UINT8, &u8);
proto[1] = sdp_list_append(proto[1], channel);
apseq = sdp_list_append(apseq, proto[1]);
features = sdp_data_alloc(SDP_UINT16, &u16);
sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
aproto = sdp_list_append(0, apseq);
sdp_set_access_protos(record, aproto);
sdp_set_info_attr(record, "Voice Gateway", 0, 0);
sdp_attr_add(record, SDP_ATTR_EXTERNAL_NETWORK, network);
if (add_record_to_server(&adapter->bdaddr, record) < 0)
ret = -1;
sdp_data_free(channel);
sdp_list_free(proto[0], 0);
sdp_list_free(proto[1], 0);
sdp_list_free(apseq, 0);
sdp_list_free(aproto, 0);
if (!ret)
return record->handle;
return ret;
}
static int add_pbap_pse_record(struct btd_adapter *adapter)
{
sdp_list_t *svclass_id, *pfseq, *apseq, *root;
uuid_t root_uuid, pbap_uuid, l2cap_uuid, rfcomm_uuid, obex_uuid;
sdp_profile_desc_t profile[1];
sdp_list_t *aproto, *proto[3];
sdp_record_t *record;
uint8_t u8 = 19;
sdp_data_t *channel;
uint8_t formats[] = {0x01};
uint8_t dtd = SDP_UINT8;
sdp_data_t *sflist;
int ret = 0;
record = sdp_record_alloc();
if (!record) return -1;
sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
root = sdp_list_append(0, &root_uuid);
sdp_set_browse_groups(record, root);
sdp_uuid16_create(&pbap_uuid, PBAP_PSE_SVCLASS_ID);
svclass_id = sdp_list_append(0, &pbap_uuid);
sdp_set_service_classes(record, svclass_id);
sdp_uuid16_create(&profile[0].uuid, PBAP_PROFILE_ID);
profile[0].version = 0x0100;
pfseq = sdp_list_append(0, profile);
sdp_set_profile_descs(record, pfseq);
sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
proto[0] = sdp_list_append(0, &l2cap_uuid);
apseq = sdp_list_append(0, proto[0]);
sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
proto[1] = sdp_list_append(0, &rfcomm_uuid);
channel = sdp_data_alloc(SDP_UINT8, &u8);
proto[1] = sdp_list_append(proto[1], channel);
apseq = sdp_list_append(apseq, proto[1]);
sdp_uuid16_create(&obex_uuid, OBEX_UUID);
proto[2] = sdp_list_append(0, &obex_uuid);
apseq = sdp_list_append(apseq, proto[2]);
aproto = sdp_list_append(0, apseq);
sdp_set_access_protos(record, aproto);
sflist = sdp_data_alloc(dtd,formats);
sdp_attr_add(record, SDP_ATTR_SUPPORTED_REPOSITORIES, sflist);
sdp_set_info_attr(record, "OBEX Phonebook Access Server", 0, 0);
if (add_record_to_server(&adapter->bdaddr, record) < 0)
ret = -1;
sdp_data_free(channel);
sdp_list_free(proto[0], 0);
sdp_list_free(proto[1], 0);
sdp_list_free(proto[2], 0);
sdp_list_free(apseq, 0);
sdp_list_free(aproto, 0);
if (!ret)
return record->handle;
return ret;
}
static int add_opush_record(struct btd_adapter *adapter)
{
sdp_list_t *svclass_id, *pfseq, *apseq, *root;
uuid_t root_uuid, opush_uuid, l2cap_uuid, rfcomm_uuid, obex_uuid;
sdp_profile_desc_t profile[1];
sdp_list_t *aproto, *proto[3];
sdp_record_t *record;
uint8_t u8 = 12;
sdp_data_t *channel;
#ifdef ANDROID
uint8_t formats[] = { 0x01, 0x02, 0xff };
#else
uint8_t formats[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0xff };
#endif
void *dtds[sizeof(formats)], *values[sizeof(formats)];
unsigned int i;
uint8_t dtd = SDP_UINT8;
sdp_data_t *sflist;
int ret = 0;
record = sdp_record_alloc();
if (!record) return -1;
sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
root = sdp_list_append(0, &root_uuid);
sdp_set_browse_groups(record, root);
sdp_uuid16_create(&opush_uuid, OBEX_OBJPUSH_SVCLASS_ID);
svclass_id = sdp_list_append(0, &opush_uuid);
sdp_set_service_classes(record, svclass_id);
sdp_uuid16_create(&profile[0].uuid, OBEX_OBJPUSH_PROFILE_ID);
profile[0].version = 0x0100;
pfseq = sdp_list_append(0, profile);
sdp_set_profile_descs(record, pfseq);
sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
proto[0] = sdp_list_append(0, &l2cap_uuid);
apseq = sdp_list_append(0, proto[0]);
sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
proto[1] = sdp_list_append(0, &rfcomm_uuid);
channel = sdp_data_alloc(SDP_UINT8, &u8);
proto[1] = sdp_list_append(proto[1], channel);
apseq = sdp_list_append(apseq, proto[1]);
sdp_uuid16_create(&obex_uuid, OBEX_UUID);
proto[2] = sdp_list_append(0, &obex_uuid);
apseq = sdp_list_append(apseq, proto[2]);
aproto = sdp_list_append(0, apseq);
sdp_set_access_protos(record, aproto);
for (i = 0; i < sizeof(formats); i++) {
dtds[i] = &dtd;
values[i] = &formats[i];
}
sflist = sdp_seq_alloc(dtds, values, sizeof(formats));
sdp_attr_add(record, SDP_ATTR_SUPPORTED_FORMATS_LIST, sflist);
sdp_set_info_attr(record, "OBEX Object Push", 0, 0);
if (add_record_to_server(&adapter->bdaddr, record) < 0)
ret = -1;
sdp_data_free(channel);
sdp_list_free(proto[0], 0);
sdp_list_free(proto[1], 0);
sdp_list_free(proto[2], 0);
sdp_list_free(apseq, 0);
sdp_list_free(aproto, 0);
if (!ret)
return record->handle;
return ret;
}
static DBusMessage *add_reserved_service_records(DBusConnection *conn,
DBusMessage *msg, void *data) {
DBusMessage *reply;
struct btd_adapter *adapter = data;
uint32_t *svc_classes;
uint32_t *handles;
uint32_t len, i;
int ret;
if (dbus_message_get_args(msg, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32,
&svc_classes, &len, DBUS_TYPE_INVALID) == FALSE)
return invalid_args(msg);
handles = g_malloc0(sizeof(uint32_t) * len);
for (i = 0; i < len; i++) {
switch (svc_classes[i]) {
case PBAP_PSE_SVCLASS_ID:
ret = add_pbap_pse_record(adapter);
break;
case HEADSET_AGW_SVCLASS_ID:
ret = add_headset_ag_record(adapter);
break;
case HANDSFREE_AGW_SVCLASS_ID:
ret = add_handsfree_ag_record(adapter);
break;
case OBEX_OBJPUSH_SVCLASS_ID:
ret = add_opush_record(adapter);
break;
}
if (ret < 0) {
g_free(handles);
return g_dbus_create_error(msg,
ERROR_INTERFACE ".Failed", "Failed to add sdp record");
} else
handles[i] = ret;
}
reply = dbus_message_new_method_return(msg);
dbus_message_append_args(reply, DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32,
&handles, len, DBUS_TYPE_INVALID);
g_free(handles);
return reply;
}
static DBusMessage *remove_reserved_service_records(DBusConnection *conn,
DBusMessage *msg, void *data) {
uint32_t *handles;
uint32_t len, i;
if (dbus_message_get_args(msg, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32,
&handles, &len, DBUS_TYPE_INVALID) == FALSE)
return invalid_args(msg);
for (i = 0; i < len; i++)
if (remove_record_from_server(handles[i]))
return g_dbus_create_error(msg,
ERROR_INTERFACE ".Failed", "Failed to remove sdp record");
return dbus_message_new_method_return(msg);
}
static DBusMessage *read_local_oob_data(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct btd_adapter *adapter = data;
DBusMessage *reply;
uint8_t hash[16], randomizer[16];
uint8_t *hash_ptr = hash;
uint8_t *r_ptr = randomizer;
int dd, err;
dd = hci_open_dev(adapter->dev_id);
if (dd < 0) {
err = -errno;
goto fail;
}
err = hci_read_local_oob_data(dd, hash, randomizer, HCI_REQ_TIMEOUT);
hci_close_dev(dd);
if (err < 0) {
err = -errno;
goto fail;
}
reply = dbus_message_new_method_return(msg);
dbus_message_append_args(reply,
DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &hash_ptr, 16,
DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &r_ptr, 16,
DBUS_TYPE_INVALID);
return reply;
fail:
return failed_strerror(msg, errno);
}
static DBusMessage *set_link_timeout(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct btd_adapter *adapter = data;
struct btd_device *device;
const char *path;
GSList *l;
uint32_t num_slots;
int dd, err;
if (!dbus_message_get_args(msg, NULL,
DBUS_TYPE_OBJECT_PATH, &path,
DBUS_TYPE_UINT32, &num_slots,
DBUS_TYPE_INVALID))
return invalid_args(msg);
l = g_slist_find_custom(adapter->devices,
path, (GCompareFunc) device_path_cmp);
if (!l)
return g_dbus_create_error(msg,
ERROR_INTERFACE ".DoesNotExist",
"Device does not exist");
device = l->data;
dd = hci_open_dev(adapter->dev_id);
if (dd < 0) {
err = -errno;
goto fail;
}
err = hci_write_link_supervision_timeout(dd,
htobs(device_get_handle(device)), htobs(num_slots), 1000);
hci_close_dev(dd);
if (err < 0) {
err = -errno;
goto fail;
}
return dbus_message_new_method_return(msg);
fail:
return failed_strerror(msg, errno);
}
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},
{ "CreatePairedDeviceOutOfBand", "sos", "o", create_paired_device_oob,
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 },
{ "ReadLocalOutOfBandData", "", "ayay", read_local_oob_data},
{ "RegisterAgent", "os", "", register_agent },
{ "RegisterAgent", "osb", "", register_agent_oob },
{ "UnregisterAgent", "o", "", unregister_agent },
{ "AddRfcommServiceRecord", "sttq", "u", add_rfcomm_service_record },
{ "RemoveServiceRecord", "u", "", remove_service_record },
{ "SetLinkTimeout", "ou", "", set_link_timeout },
{ "AddReservedServiceRecords", "au", "au", add_reserved_service_records },
{ "RemoveReservedServiceRecords", "au", "", remove_reserved_service_records },
{ }
};
static GDBusSignalTable adapter_signals[] = {
{ "PropertyChanged", "sv" },
{ "DeviceCreated", "o" },
{ "DeviceRemoved", "o" },
{ "DeviceFound", "sa{sv}" },
{ "DeviceDisappeared", "s" },
{ }
};
static inline uint8_t get_inquiry_mode(struct hci_dev *dev)
{
if (dev->features[6] & LMP_EXT_INQ)
return 2;
if (dev->features[3] & LMP_RSSI_INQ)
return 1;
if (dev->manufacturer == 11 &&
dev->hci_rev == 0x00 && dev->lmp_subver == 0x0757)
return 1;
if (dev->manufacturer == 15) {
if (dev->hci_rev == 0x03 && dev->lmp_subver == 0x6963)
return 1;
if (dev->hci_rev == 0x09 && dev->lmp_subver == 0x6963)
return 1;
if (dev->hci_rev == 0x00 && dev->lmp_subver == 0x6965)
return 1;
}
if (dev->manufacturer == 31 &&
dev->hci_rev == 0x2005 && dev->lmp_subver == 0x1805)
return 1;
return 0;
}
static int adapter_read_bdaddr(uint16_t dev_id, bdaddr_t *bdaddr)
{
int dd, err;
dd = hci_open_dev(dev_id);
if (dd < 0) {
err = -errno;
error("Can't open device hci%d: %s (%d)",
dev_id, strerror(errno), errno);
return err;
}
if (hci_read_bd_addr(dd, bdaddr, HCI_REQ_TIMEOUT) < 0) {
err = -errno;
error("Can't read address for hci%d: %s (%d)",
dev_id, strerror(errno), errno);
hci_close_dev(dd);
return err;
}
hci_close_dev(dd);
return 0;
}
static int adapter_setup(struct btd_adapter *adapter, const char *mode)
{
struct hci_dev *dev = &adapter->dev;
uint8_t events[8] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x00 };
uint8_t inqmode;
int err , dd;
char name[MAX_NAME_LENGTH + 1];
uint8_t cls[3];
dd = hci_open_dev(adapter->dev_id);
if (dd < 0) {
err = -errno;
error("Can't open device hci%d: %s (%d)", adapter->dev_id,
strerror(errno), errno);
return err;
}
if (dev->lmp_ver > 1) {
if (dev->features[5] & LMP_SNIFF_SUBR)
events[5] |= 0x20;
if (dev->features[5] & LMP_PAUSE_ENC)
events[5] |= 0x80;
if (dev->features[6] & LMP_EXT_INQ)
events[5] |= 0x40;
if (dev->features[6] & LMP_NFLUSH_PKTS)
events[7] |= 0x01;
if (dev->features[7] & LMP_LSTO)
events[6] |= 0x80;
if (dev->features[6] & LMP_SIMPLE_PAIR) {
events[6] |= 0x01; /* IO Capability Request */
events[6] |= 0x02; /* IO Capability Response */
events[6] |= 0x04; /* User Confirmation Request */
events[6] |= 0x08; /* User Passkey Request */
events[6] |= 0x10; /* Remote OOB Data Request */
events[6] |= 0x20; /* Simple Pairing Complete */
events[7] |= 0x04; /* User Passkey Notification */
events[7] |= 0x08; /* Keypress Notification */
events[7] |= 0x10; /* Remote Host Supported
* Features Notification */
}
hci_send_cmd(dd, OGF_HOST_CTL, OCF_SET_EVENT_MASK,
sizeof(events), events);
}
inqmode = get_inquiry_mode(dev);
if (inqmode < 1)
goto done;
if (hci_write_inquiry_mode(dd, inqmode, HCI_REQ_TIMEOUT) < 0) {
err = -errno;
error("Can't write inquiry mode for %s: %s (%d)",
adapter->path, strerror(errno), errno);
hci_close_dev(dd);
return err;
}
if (dev->features[7] & LMP_INQ_TX_PWR)
hci_send_cmd(dd, OGF_HOST_CTL,
OCF_READ_INQ_RESPONSE_TX_POWER_LEVEL, 0, NULL);
if (read_local_name(&adapter->bdaddr, name) < 0)
expand_name(name, MAX_NAME_LENGTH, main_opts.name,
adapter->dev_id);
adapter_ops->set_name(adapter->dev_id, name);
if (g_str_equal(mode, "off"))
strncpy((char *) adapter->dev.name, name, MAX_NAME_LENGTH);
/* Set device class */
if (adapter->initialized && adapter->wanted_cod) {
cls[1] = (adapter->wanted_cod >> 8) & 0xff;
cls[0] = adapter->wanted_cod & 0xff;
} else if (read_local_class(&adapter->bdaddr, cls) < 0) {
uint32_t class = htobl(main_opts.class);
if (class)
memcpy(cls, &class, 3);
else
goto done;
}
btd_adapter_set_class(adapter, cls[1], cls[0]);
done:
hci_close_dev(dd);
return 0;
}
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);
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);
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);
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);
}
static void clear_blocked(struct btd_adapter *adapter)
{
int dd;
dd = hci_open_dev(adapter->dev_id);
if (dd < 0) {
error("hci_open_dev(hci%d): %s (%d)", adapter->dev_id,
strerror(errno), errno);
return;
}
if (ioctl(dd, HCIUNBLOCKADDR, BDADDR_ANY) < 0)
error("ioctl(HCIUNBLOCKADDR): %s (%d)", strerror(errno), errno);
hci_close_dev(dd);
}
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)
{
struct hci_conn_list_req *cl = NULL;
struct hci_conn_info *ci;
int i, dd;
dd = hci_open_dev(adapter->dev_id);
if (dd < 0)
return;
cl = g_malloc0(10 * sizeof(*ci) + sizeof(*cl));
cl->dev_id = adapter->dev_id;
cl->conn_num = 10;