mirror of
https://git.kernel.org/pub/scm/bluetooth/bluez.git
synced 2024-12-15 06:55:30 +08:00
3882 lines
91 KiB
C
3882 lines
91 KiB
C
/*
|
|
*
|
|
* 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 <stdbool.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/file.h>
|
|
#include <sys/stat.h>
|
|
#include <dirent.h>
|
|
|
|
#include <bluetooth/bluetooth.h>
|
|
#include <bluetooth/uuid.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 "adapter.h"
|
|
#include "manager.h"
|
|
#include "device.h"
|
|
#include "profile.h"
|
|
#include "dbus-common.h"
|
|
#include "error.h"
|
|
#include "glib-helper.h"
|
|
#include "agent.h"
|
|
#include "storage.h"
|
|
#include "attrib/gattrib.h"
|
|
#include "attrib/att.h"
|
|
#include "attrib/gatt.h"
|
|
#include "attrib-server.h"
|
|
#include "eir.h"
|
|
#include "mgmt.h"
|
|
|
|
/* Flags Descriptions */
|
|
#define EIR_LIM_DISC 0x01 /* LE Limited Discoverable Mode */
|
|
#define EIR_GEN_DISC 0x02 /* LE General Discoverable Mode */
|
|
#define EIR_BREDR_UNSUP 0x04 /* BR/EDR Not Supported */
|
|
#define EIR_SIM_CONTROLLER 0x08 /* Simultaneous LE and BR/EDR to Same
|
|
Device Capable (Controller) */
|
|
#define EIR_SIM_HOST 0x10 /* Simultaneous LE and BR/EDR to Same
|
|
Device Capable (Host) */
|
|
|
|
#define IO_CAPABILITY_DISPLAYONLY 0x00
|
|
#define IO_CAPABILITY_DISPLAYYESNO 0x01
|
|
#define IO_CAPABILITY_KEYBOARDONLY 0x02
|
|
#define IO_CAPABILITY_NOINPUTNOOUTPUT 0x03
|
|
#define IO_CAPABILITY_KEYBOARDDISPLAY 0x04
|
|
#define IO_CAPABILITY_INVALID 0xFF
|
|
|
|
#define REMOVE_TEMP_TIMEOUT (3 * 60)
|
|
#define PENDING_FOUND_MAX 5
|
|
|
|
static GSList *adapter_drivers = NULL;
|
|
|
|
enum session_req_type {
|
|
SESSION_TYPE_MODE_GLOBAL = 0,
|
|
SESSION_TYPE_MODE_SESSION,
|
|
SESSION_TYPE_DISC_INTERLEAVED,
|
|
SESSION_TYPE_DISC_LE_SCAN
|
|
};
|
|
|
|
struct session_req {
|
|
struct btd_adapter *adapter;
|
|
enum session_req_type type;
|
|
DBusMessage *msg; /* Unreplied message ref */
|
|
GDBusPendingPropertySet prop_id; /* Pending Properties.Set() */
|
|
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 {
|
|
guint id;
|
|
service_auth_cb cb;
|
|
void *user_data;
|
|
const char *uuid;
|
|
struct btd_device *device;
|
|
struct btd_adapter *adapter;
|
|
struct agent *agent; /* NULL for queued auths */
|
|
};
|
|
|
|
struct discovery {
|
|
guint id;
|
|
GSList *found;
|
|
GSList *pending;
|
|
};
|
|
|
|
struct btd_adapter {
|
|
uint16_t dev_id;
|
|
gboolean up;
|
|
gboolean already_up;
|
|
char *path; /* adapter object path */
|
|
bdaddr_t bdaddr; /* adapter Bluetooth Address */
|
|
uint32_t dev_class; /* Class of Device */
|
|
char *name; /* adapter name */
|
|
gboolean allow_name_changes; /* whether the adapter name can be changed */
|
|
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;
|
|
struct agent *agent; /* For the new API */
|
|
guint auth_idle_id; /* Pending authorization dequeue */
|
|
GQueue *auths; /* Ongoing and pending auths */
|
|
GSList *connections; /* Connected devices */
|
|
GSList *devices; /* Devices structure pointers */
|
|
guint remove_temp; /* Remove devices timer */
|
|
GSList *mode_sessions; /* Request Mode sessions */
|
|
GSList *disc_sessions; /* Discovery sessions */
|
|
struct session_req *scanning_session;
|
|
GSList *connect_list; /* Devices to connect when found */
|
|
guint discov_id; /* Discovery timer */
|
|
struct discovery *discovery; /* Discovery active */
|
|
gboolean connecting; /* Connect active */
|
|
guint waiting_to_connect; /* # of devices waiting to connect */
|
|
gboolean discov_suspended; /* Discovery suspended */
|
|
guint auto_timeout_id; /* Automatic connections timeout */
|
|
sdp_list_t *services; /* Services associated to adapter */
|
|
|
|
gboolean discoverable; /* discoverable state */
|
|
gboolean pairable; /* pairable state */
|
|
gboolean initialized;
|
|
|
|
gboolean off_requested; /* DEVDOWN ioctl was called */
|
|
|
|
gint ref;
|
|
|
|
guint off_timer;
|
|
|
|
GSList *powered_callbacks;
|
|
GSList *pin_callbacks;
|
|
|
|
GSList *drivers;
|
|
GSList *profiles;
|
|
|
|
struct oob_handler *oob_handler;
|
|
};
|
|
|
|
static gboolean process_auth_queue(gpointer user_data);
|
|
|
|
int btd_adapter_set_class(struct btd_adapter *adapter, uint8_t major,
|
|
uint8_t minor)
|
|
{
|
|
return mgmt_set_dev_class(adapter->dev_id, major, minor);
|
|
}
|
|
|
|
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 char *mode)
|
|
{
|
|
if (strcasecmp("off", mode) == 0)
|
|
return MODE_OFF;
|
|
else if (strcasecmp("connectable", mode) == 0)
|
|
return MODE_CONNECTABLE;
|
|
else if (strcasecmp("discoverable", mode) == 0)
|
|
return MODE_DISCOVERABLE;
|
|
else
|
|
return MODE_UNKNOWN;
|
|
}
|
|
|
|
static void store_adapter_info(struct btd_adapter *adapter)
|
|
{
|
|
GKeyFile *key_file;
|
|
char filename[PATH_MAX + 1];
|
|
char address[18];
|
|
char *str;
|
|
gsize length = 0;
|
|
|
|
key_file = g_key_file_new();
|
|
|
|
g_key_file_set_string(key_file, "General", "Name", adapter->name);
|
|
|
|
g_key_file_set_boolean(key_file, "General", "Powered",
|
|
adapter->mode > MODE_OFF);
|
|
|
|
g_key_file_set_boolean(key_file, "General", "Pairable",
|
|
adapter->pairable);
|
|
|
|
if (adapter->pairable_timeout != main_opts.pairto)
|
|
g_key_file_set_integer(key_file, "General", "PairableTimeout",
|
|
adapter->pairable_timeout);
|
|
|
|
g_key_file_set_boolean(key_file, "General", "Discoverable",
|
|
adapter->discoverable);
|
|
|
|
if (adapter->discov_timeout != main_opts.discovto)
|
|
g_key_file_set_integer(key_file, "General",
|
|
"DiscoverableTimeout",
|
|
adapter->discov_timeout);
|
|
|
|
ba2str(&adapter->bdaddr, address);
|
|
snprintf(filename, PATH_MAX, STORAGEDIR "/%s/settings", address);
|
|
filename[PATH_MAX] = '\0';
|
|
|
|
create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
|
|
str = g_key_file_to_data(key_file, &length, NULL);
|
|
g_file_set_contents(filename, str, length, NULL);
|
|
g_free(str);
|
|
|
|
g_key_file_free(key_file);
|
|
}
|
|
|
|
void adapter_store_cached_name(const bdaddr_t *local, const bdaddr_t *peer,
|
|
const char *name)
|
|
{
|
|
char filename[PATH_MAX + 1];
|
|
char s_addr[18], d_addr[18];
|
|
GKeyFile *key_file;
|
|
char *data;
|
|
gsize length = 0;
|
|
|
|
ba2str(local, s_addr);
|
|
ba2str(peer, d_addr);
|
|
snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", s_addr, d_addr);
|
|
filename[PATH_MAX] = '\0';
|
|
create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
|
|
key_file = g_key_file_new();
|
|
g_key_file_load_from_file(key_file, filename, 0, NULL);
|
|
g_key_file_set_string(key_file, "General", "Name", name);
|
|
|
|
data = g_key_file_to_data(key_file, &length, NULL);
|
|
g_file_set_contents(filename, data, length, NULL);
|
|
g_free(data);
|
|
|
|
g_key_file_free(key_file);
|
|
}
|
|
|
|
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,
|
|
DBusMessage *msg, uint8_t mode,
|
|
enum session_req_type type,
|
|
GDBusWatchFunction cb)
|
|
{
|
|
const char *sender;
|
|
struct session_req *req;
|
|
|
|
req = g_new0(struct session_req, 1);
|
|
req->adapter = adapter;
|
|
req->mode = mode;
|
|
req->type = type;
|
|
|
|
if (msg == NULL)
|
|
return session_ref(req);
|
|
|
|
req->msg = dbus_message_ref(msg);
|
|
|
|
if (cb == NULL)
|
|
return session_ref(req);
|
|
|
|
sender = dbus_message_get_sender(msg);
|
|
req->owner = g_strdup(sender);
|
|
req->id = g_dbus_add_disconnect_watch(btd_get_dbus_connection(),
|
|
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 = mgmt_set_discoverable(adapter->dev_id, FALSE, 0);
|
|
else
|
|
err = mgmt_set_discoverable(adapter->dev_id, TRUE,
|
|
adapter->discov_timeout);
|
|
|
|
return err;
|
|
}
|
|
|
|
static struct session_req *find_session_by_msg(GSList *list, const DBusMessage *msg)
|
|
{
|
|
for (; list; list = list->next) {
|
|
struct session_req *req = list->data;
|
|
|
|
if (req->msg == msg)
|
|
return req;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int set_mode(struct btd_adapter *adapter, uint8_t new_mode)
|
|
{
|
|
int err;
|
|
|
|
if (adapter->pending_mode != NULL)
|
|
return -EALREADY;
|
|
|
|
if (!adapter->up && new_mode != MODE_OFF) {
|
|
err = mgmt_set_powered(adapter->dev_id, TRUE);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
goto done;
|
|
}
|
|
|
|
if (adapter->up && new_mode == MODE_OFF) {
|
|
err = mgmt_set_powered(adapter->dev_id, FALSE);
|
|
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:
|
|
store_adapter_info(adapter);
|
|
|
|
DBG("%s", mode2str(new_mode));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void set_session_pending_mode(struct btd_adapter *adapter,
|
|
uint8_t new_mode, DBusMessage *msg)
|
|
{
|
|
struct session_req *req;
|
|
|
|
/*
|
|
* Schedule the reply to be sent when a mode-change notification
|
|
* arrives. The reply will be sent by set_mode_complete().
|
|
*/
|
|
req = find_session_by_msg(adapter->mode_sessions, msg);
|
|
if (req) {
|
|
adapter->pending_mode = req;
|
|
session_ref(req);
|
|
} else
|
|
adapter->pending_mode = create_session(adapter, msg, new_mode,
|
|
SESSION_TYPE_MODE_SESSION, NULL);
|
|
}
|
|
|
|
static void set_discoverable(struct btd_adapter *adapter,
|
|
gboolean discoverable, GDBusPendingPropertySet id)
|
|
{
|
|
uint8_t mode;
|
|
int err;
|
|
|
|
mode = discoverable ? MODE_DISCOVERABLE : MODE_CONNECTABLE;
|
|
|
|
if (mode == adapter->mode) {
|
|
adapter->global_mode = mode;
|
|
return g_dbus_pending_property_success(id);
|
|
}
|
|
|
|
err = set_mode(adapter, mode);
|
|
if (err < 0)
|
|
return g_dbus_pending_property_error(id,
|
|
ERROR_INTERFACE ".Failed",
|
|
strerror(-err));
|
|
|
|
adapter->pending_mode = create_session(adapter, NULL, mode,
|
|
SESSION_TYPE_MODE_GLOBAL, NULL);
|
|
adapter->pending_mode->prop_id = id;
|
|
}
|
|
|
|
static void set_powered(struct btd_adapter *adapter, gboolean powered,
|
|
GDBusPendingPropertySet id)
|
|
{
|
|
uint8_t mode;
|
|
int err;
|
|
|
|
if (powered)
|
|
return set_discoverable(adapter, adapter->discoverable, id);
|
|
|
|
mode = MODE_OFF;
|
|
|
|
if (mode == adapter->mode) {
|
|
adapter->global_mode = mode;
|
|
return g_dbus_pending_property_success(id);
|
|
}
|
|
|
|
err = set_mode(adapter, mode);
|
|
if (err < 0)
|
|
return g_dbus_pending_property_error(id,
|
|
ERROR_INTERFACE ".Failed",
|
|
strerror(-err));
|
|
|
|
adapter->pending_mode = create_session(adapter, NULL, mode,
|
|
SESSION_TYPE_MODE_GLOBAL, NULL);
|
|
adapter->pending_mode->prop_id = id;
|
|
}
|
|
|
|
static void set_pairable(struct btd_adapter *adapter, gboolean pairable,
|
|
bool reply, GDBusPendingPropertySet id)
|
|
{
|
|
int err;
|
|
|
|
if (adapter->scan_mode == SCAN_DISABLED)
|
|
return g_dbus_pending_property_error(id,
|
|
ERROR_INTERFACE ".NotReady",
|
|
"Resource Not Ready");
|
|
|
|
if (pairable == adapter->pairable)
|
|
goto done;
|
|
|
|
if (!(adapter->scan_mode & SCAN_INQUIRY))
|
|
goto store;
|
|
|
|
err = set_mode(adapter, MODE_DISCOVERABLE);
|
|
if (err < 0) {
|
|
if (reply)
|
|
g_dbus_pending_property_error(id,
|
|
ERROR_INTERFACE ".Failed",
|
|
strerror(-err));
|
|
return;
|
|
}
|
|
|
|
store:
|
|
mgmt_set_pairable(adapter->dev_id, pairable);
|
|
|
|
done:
|
|
if (reply)
|
|
g_dbus_pending_property_success(id);
|
|
}
|
|
|
|
static gboolean pairable_timeout_handler(void *data)
|
|
{
|
|
set_pairable(data, FALSE, false, 0);
|
|
|
|
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);
|
|
}
|
|
|
|
void btd_adapter_pairable_changed(struct btd_adapter *adapter,
|
|
gboolean pairable)
|
|
{
|
|
adapter->pairable = pairable;
|
|
|
|
store_adapter_info(adapter);
|
|
|
|
g_dbus_emit_property_changed(btd_get_dbus_connection(), adapter->path,
|
|
ADAPTER_INTERFACE, "Pairable");
|
|
|
|
if (pairable && adapter->pairable_timeout)
|
|
adapter_set_pairable_timeout(adapter,
|
|
adapter->pairable_timeout);
|
|
}
|
|
|
|
static struct session_req *find_session(GSList *list, const char *sender)
|
|
{
|
|
for (; list; list = list->next) {
|
|
struct session_req *req = list->data;
|
|
|
|
/* req->owner may be NULL if the session has been added by the
|
|
* daemon itself, so we use g_strcmp0 instead of g_str_equal */
|
|
if (g_strcmp0(req->owner, sender) == 0)
|
|
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 send_devices_found(struct btd_adapter *adapter)
|
|
{
|
|
struct discovery *discovery = adapter->discovery;
|
|
DBusConnection *conn = btd_get_dbus_connection();
|
|
DBusMessageIter iter, props;
|
|
DBusMessage *signal;
|
|
|
|
if (!discovery || !discovery->pending)
|
|
return;
|
|
|
|
signal = dbus_message_new_signal(adapter->path, ADAPTER_INTERFACE,
|
|
"DevicesFound");
|
|
if (!signal) {
|
|
error("Unable to allocate DevicesFound signal");
|
|
g_slist_free(discovery->pending);
|
|
discovery->pending = NULL;
|
|
return;
|
|
}
|
|
|
|
dbus_message_iter_init_append(signal, &iter);
|
|
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{oa{sv}}",
|
|
&props);
|
|
|
|
while (discovery->pending) {
|
|
struct btd_device *dev = discovery->pending->data;
|
|
const char *path = device_get_path(dev);
|
|
DBusMessageIter entry;
|
|
|
|
dbus_message_iter_open_container(&props, DBUS_TYPE_DICT_ENTRY,
|
|
NULL, &entry);
|
|
dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH,
|
|
&path);
|
|
g_dbus_get_properties(conn, path, DEVICE_INTERFACE, &entry);
|
|
dbus_message_iter_close_container(&props, &entry);
|
|
|
|
discovery->pending = g_slist_remove(discovery->pending, dev);
|
|
}
|
|
|
|
dbus_message_iter_close_container(&iter, &props);
|
|
|
|
g_dbus_send_message(conn, signal);
|
|
}
|
|
|
|
static void invalidate_rssi(gpointer a)
|
|
{
|
|
struct btd_device *dev = a;
|
|
|
|
device_set_rssi(dev, 0);
|
|
}
|
|
|
|
static void discovery_cleanup(struct btd_adapter *adapter)
|
|
{
|
|
struct discovery *discovery = adapter->discovery;
|
|
|
|
if (!discovery)
|
|
return;
|
|
|
|
if (discovery->id > 0)
|
|
g_source_remove(discovery->id);
|
|
|
|
send_devices_found(adapter);
|
|
|
|
adapter->discovery = NULL;
|
|
|
|
g_slist_free_full(discovery->found, invalidate_rssi);
|
|
|
|
g_free(discovery);
|
|
}
|
|
|
|
/* Called when a session gets removed or the adapter is stopped */
|
|
static void stop_discovery(struct btd_adapter *adapter)
|
|
{
|
|
/* Reset if suspended, otherwise remove timer (software scheduler)
|
|
* or request inquiry to stop */
|
|
if (adapter->discov_suspended) {
|
|
adapter->discov_suspended = FALSE;
|
|
return;
|
|
}
|
|
|
|
if (adapter->discov_id > 0) {
|
|
g_source_remove(adapter->discov_id);
|
|
adapter->discov_id = 0;
|
|
return;
|
|
}
|
|
|
|
if (adapter->up)
|
|
mgmt_stop_discovery(adapter->dev_id);
|
|
else
|
|
discovery_cleanup(adapter);
|
|
}
|
|
|
|
static void session_remove(struct session_req *req)
|
|
{
|
|
struct btd_adapter *adapter = req->adapter;
|
|
|
|
/* Ignore global requests */
|
|
if (req->type == SESSION_TYPE_MODE_GLOBAL)
|
|
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);
|
|
} else {
|
|
adapter->disc_sessions = g_slist_remove(adapter->disc_sessions,
|
|
req);
|
|
|
|
if (adapter->disc_sessions)
|
|
return;
|
|
|
|
DBG("Stopping discovery");
|
|
|
|
stop_discovery(adapter);
|
|
}
|
|
}
|
|
|
|
static void session_free(void *data)
|
|
{
|
|
struct session_req *req = data;
|
|
|
|
if (req->id)
|
|
g_dbus_remove_watch(btd_get_dbus_connection(), req->id);
|
|
|
|
if (req->msg) {
|
|
dbus_message_unref(req->msg);
|
|
if (!req->got_reply && req->mode && req->adapter->agent)
|
|
agent_cancel(req->adapter->agent);
|
|
}
|
|
|
|
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_remove(req);
|
|
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_remove(req);
|
|
session_free(req);
|
|
}
|
|
|
|
static void confirm_mode_cb(struct agent *agent, DBusError *derr, void *data)
|
|
{
|
|
DBusConnection *conn = btd_get_dbus_connection();
|
|
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(conn, reply);
|
|
session_unref(req);
|
|
return;
|
|
}
|
|
|
|
err = set_mode(req->adapter, req->mode);
|
|
if (err >= 0 && req->adapter->mode != req->mode) {
|
|
set_session_pending_mode(req->adapter, req->mode, req->msg);
|
|
goto done;
|
|
}
|
|
|
|
if (err < 0)
|
|
reply = btd_error_failed(req->msg, strerror(-err));
|
|
else
|
|
reply = dbus_message_new_method_return(req->msg);
|
|
|
|
/*
|
|
* Send reply immediately only if there was an error changing mode, or
|
|
* change is not needed. Otherwise, reply is sent in
|
|
* set_mode_complete.
|
|
*/
|
|
g_dbus_send_message(conn, reply);
|
|
|
|
done:
|
|
session_unref(req);
|
|
}
|
|
|
|
static void set_discoverable_timeout(struct btd_adapter *adapter,
|
|
uint32_t timeout, GDBusPendingPropertySet id)
|
|
{
|
|
DBusConnection *conn = btd_get_dbus_connection();
|
|
|
|
if (adapter->discov_timeout == timeout && timeout == 0)
|
|
return g_dbus_pending_property_success(id);
|
|
|
|
if (adapter->scan_mode & SCAN_INQUIRY)
|
|
mgmt_set_discoverable(adapter->dev_id, TRUE, timeout);
|
|
|
|
adapter->discov_timeout = timeout;
|
|
|
|
store_adapter_info(adapter);
|
|
|
|
g_dbus_emit_property_changed(conn, adapter->path, ADAPTER_INTERFACE,
|
|
"DiscoverableTimeout");
|
|
g_dbus_pending_property_success(id);
|
|
}
|
|
|
|
static void set_pairable_timeout(struct btd_adapter *adapter,
|
|
uint32_t timeout, GDBusPendingPropertySet id)
|
|
{
|
|
DBusConnection *conn = btd_get_dbus_connection();
|
|
|
|
if (adapter->pairable_timeout == timeout && timeout == 0)
|
|
return g_dbus_pending_property_success(id);
|
|
|
|
if (adapter->pairable)
|
|
adapter_set_pairable_timeout(adapter, timeout);
|
|
|
|
adapter->pairable_timeout = timeout;
|
|
|
|
store_adapter_info(adapter);
|
|
|
|
g_dbus_emit_property_changed(conn, adapter->path, ADAPTER_INTERFACE,
|
|
"PairableTimeout");
|
|
g_dbus_pending_property_success(id);
|
|
}
|
|
|
|
void btd_adapter_class_changed(struct btd_adapter *adapter, uint8_t *new_class)
|
|
{
|
|
uint32_t class;
|
|
uint8_t cls[3];
|
|
|
|
class = new_class[0] | (new_class[1] << 8) | (new_class[2] << 16);
|
|
|
|
if (class == adapter->dev_class)
|
|
return;
|
|
|
|
adapter->dev_class = class;
|
|
|
|
memcpy(cls, new_class, sizeof(cls));
|
|
|
|
/* Removes service class */
|
|
cls[1] = cls[1] & 0x1f;
|
|
attrib_gap_set(adapter, GATT_CHARAC_APPEARANCE, cls, 2);
|
|
|
|
g_dbus_emit_property_changed(btd_get_dbus_connection(), adapter->path,
|
|
ADAPTER_INTERFACE, "Class");
|
|
}
|
|
|
|
void adapter_name_changed(struct btd_adapter *adapter, const char *name)
|
|
{
|
|
if (g_strcmp0(adapter->name, name) == 0)
|
|
return;
|
|
|
|
g_free(adapter->name);
|
|
adapter->name = g_strdup(name);
|
|
|
|
g_dbus_emit_property_changed(btd_get_dbus_connection(), adapter->path,
|
|
ADAPTER_INTERFACE, "Name");
|
|
|
|
attrib_gap_set(adapter, GATT_CHARAC_DEVICE_NAME,
|
|
(const uint8_t *) name, strlen(name));
|
|
}
|
|
|
|
int adapter_set_name(struct btd_adapter *adapter, const char *name)
|
|
{
|
|
char maxname[MAX_NAME_LENGTH + 1];
|
|
|
|
if (g_strcmp0(adapter->name, name) == 0)
|
|
return 0;
|
|
|
|
memset(maxname, 0, sizeof(maxname));
|
|
strncpy(maxname, name, MAX_NAME_LENGTH);
|
|
if (!g_utf8_validate(maxname, -1, NULL)) {
|
|
error("Name change failed: supplied name isn't valid UTF-8");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (adapter->up) {
|
|
int err = mgmt_set_name(adapter->dev_id, maxname);
|
|
if (err < 0)
|
|
return err;
|
|
} else {
|
|
g_free(adapter->name);
|
|
adapter->name = g_strdup(maxname);
|
|
}
|
|
|
|
store_adapter_info(adapter);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void set_name(struct btd_adapter *adapter, const char *name,
|
|
GDBusPendingPropertySet id)
|
|
{
|
|
int ret;
|
|
|
|
if (adapter->allow_name_changes == FALSE)
|
|
return g_dbus_pending_property_error(id,
|
|
ERROR_INTERFACE ".Failed",
|
|
strerror(EPERM));
|
|
|
|
ret = adapter_set_name(adapter, name);
|
|
if (ret >= 0)
|
|
return g_dbus_pending_property_success(id);
|
|
|
|
if (ret == -EINVAL)
|
|
g_dbus_pending_property_error(id,
|
|
ERROR_INTERFACE ".InvalidArguments",
|
|
"Invalid arguments in method call");
|
|
else
|
|
g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed",
|
|
strerror(-ret));
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static uint8_t get_uuid_mask(uuid_t *uuid)
|
|
{
|
|
if (uuid->type != SDP_UUID16)
|
|
return 0;
|
|
|
|
switch (uuid->value.uuid16) {
|
|
case DIALUP_NET_SVCLASS_ID:
|
|
case CIP_SVCLASS_ID:
|
|
return 0x42; /* Telephony & Networking */
|
|
case IRMC_SYNC_SVCLASS_ID:
|
|
case OBEX_OBJPUSH_SVCLASS_ID:
|
|
case OBEX_FILETRANS_SVCLASS_ID:
|
|
case IRMC_SYNC_CMD_SVCLASS_ID:
|
|
case PBAP_PSE_SVCLASS_ID:
|
|
return 0x10; /* Object Transfer */
|
|
case HEADSET_SVCLASS_ID:
|
|
case HANDSFREE_SVCLASS_ID:
|
|
return 0x20; /* Audio */
|
|
case CORDLESS_TELEPHONY_SVCLASS_ID:
|
|
case INTERCOM_SVCLASS_ID:
|
|
case FAX_SVCLASS_ID:
|
|
case SAP_SVCLASS_ID:
|
|
/*
|
|
* Setting the telephony bit for the handsfree audio gateway
|
|
* role is not required by the HFP specification, but the
|
|
* Nokia 616 carkit is just plain broken! It will refuse
|
|
* pairing without this bit set.
|
|
*/
|
|
case HANDSFREE_AGW_SVCLASS_ID:
|
|
return 0x40; /* Telephony */
|
|
case AUDIO_SOURCE_SVCLASS_ID:
|
|
case VIDEO_SOURCE_SVCLASS_ID:
|
|
return 0x08; /* Capturing */
|
|
case AUDIO_SINK_SVCLASS_ID:
|
|
case VIDEO_SINK_SVCLASS_ID:
|
|
return 0x04; /* Rendering */
|
|
case PANU_SVCLASS_ID:
|
|
case NAP_SVCLASS_ID:
|
|
case GN_SVCLASS_ID:
|
|
return 0x02; /* Networking */
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int uuid_cmp(const void *a, const void *b)
|
|
{
|
|
const sdp_record_t *rec = a;
|
|
const uuid_t *uuid = b;
|
|
|
|
return sdp_uuid_cmp(&rec->svclass, uuid);
|
|
}
|
|
|
|
void adapter_service_insert(struct btd_adapter *adapter, void *r)
|
|
{
|
|
sdp_record_t *rec = r;
|
|
gboolean new_uuid;
|
|
|
|
if (sdp_list_find(adapter->services, &rec->svclass, uuid_cmp) == NULL)
|
|
new_uuid = TRUE;
|
|
else
|
|
new_uuid = FALSE;
|
|
|
|
adapter->services = sdp_list_insert_sorted(adapter->services, rec,
|
|
record_sort);
|
|
|
|
if (new_uuid) {
|
|
uint8_t svc_hint = get_uuid_mask(&rec->svclass);
|
|
mgmt_add_uuid(adapter->dev_id, &rec->svclass, svc_hint);
|
|
}
|
|
|
|
if (adapter->initialized)
|
|
g_dbus_emit_property_changed(btd_get_dbus_connection(),
|
|
adapter->path, ADAPTER_INTERFACE, "UUIDs");
|
|
}
|
|
|
|
void adapter_service_remove(struct btd_adapter *adapter, void *r)
|
|
{
|
|
sdp_record_t *rec = r;
|
|
|
|
adapter->services = sdp_list_remove(adapter->services, rec);
|
|
|
|
if (sdp_list_find(adapter->services, &rec->svclass, uuid_cmp) == NULL)
|
|
mgmt_remove_uuid(adapter->dev_id, &rec->svclass);
|
|
|
|
if (adapter->initialized)
|
|
g_dbus_emit_property_changed(btd_get_dbus_connection(),
|
|
adapter->path, ADAPTER_INTERFACE, "UUIDs");
|
|
}
|
|
|
|
static struct btd_device *adapter_create_device(struct btd_adapter *adapter,
|
|
const char *address,
|
|
uint8_t bdaddr_type)
|
|
{
|
|
struct btd_device *device;
|
|
const char *path;
|
|
|
|
DBG("%s", address);
|
|
|
|
device = device_create(adapter, address, bdaddr_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(btd_get_dbus_connection(), adapter->path,
|
|
ADAPTER_INTERFACE, "DeviceCreated",
|
|
DBUS_TYPE_OBJECT_PATH, &path,
|
|
DBUS_TYPE_INVALID);
|
|
|
|
g_dbus_emit_property_changed(btd_get_dbus_connection(),
|
|
adapter->path, ADAPTER_INTERFACE, "Devices");
|
|
|
|
return device;
|
|
}
|
|
|
|
static void service_auth_cancel(struct service_auth *auth)
|
|
{
|
|
DBusError derr;
|
|
|
|
dbus_error_init(&derr);
|
|
dbus_set_error_const(&derr, "org.bluez.Error.Canceled", NULL);
|
|
|
|
auth->cb(&derr, auth->user_data);
|
|
|
|
dbus_error_free(&derr);
|
|
|
|
if (auth->agent != NULL)
|
|
agent_cancel(auth->agent);
|
|
|
|
g_free(auth);
|
|
}
|
|
|
|
void adapter_remove_device(struct btd_adapter *adapter,
|
|
struct btd_device *dev,
|
|
gboolean remove_storage)
|
|
{
|
|
const gchar *dev_path = device_get_path(dev);
|
|
struct discovery *discovery = adapter->discovery;
|
|
GList *l;
|
|
|
|
adapter->devices = g_slist_remove(adapter->devices, dev);
|
|
|
|
if (discovery) {
|
|
discovery->found = g_slist_remove(discovery->found, dev);
|
|
discovery->pending = g_slist_remove(discovery->pending, dev);
|
|
}
|
|
|
|
adapter->connections = g_slist_remove(adapter->connections, dev);
|
|
|
|
l = adapter->auths->head;
|
|
while (l != NULL) {
|
|
struct service_auth *auth = l->data;
|
|
GList *next = g_list_next(l);
|
|
|
|
if (auth->device != dev) {
|
|
l = next;
|
|
continue;
|
|
}
|
|
|
|
g_queue_delete_link(adapter->auths, l);
|
|
l = next;
|
|
|
|
service_auth_cancel(auth);
|
|
}
|
|
|
|
g_dbus_emit_property_changed(btd_get_dbus_connection(),
|
|
adapter->path, ADAPTER_INTERFACE, "Devices");
|
|
|
|
g_dbus_emit_signal(btd_get_dbus_connection(), adapter->path,
|
|
ADAPTER_INTERFACE, "DeviceRemoved",
|
|
DBUS_TYPE_OBJECT_PATH, &dev_path,
|
|
DBUS_TYPE_INVALID);
|
|
|
|
device_remove(dev, remove_storage);
|
|
}
|
|
|
|
struct btd_device *adapter_get_device(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(adapter, address, BDADDR_BREDR);
|
|
}
|
|
|
|
sdp_list_t *btd_adapter_get_services(struct btd_adapter *adapter)
|
|
{
|
|
return adapter->services;
|
|
}
|
|
|
|
static gboolean discovery_cb(gpointer user_data)
|
|
{
|
|
struct btd_adapter *adapter = user_data;
|
|
|
|
adapter->discov_id = 0;
|
|
|
|
if (adapter->scanning_session &&
|
|
g_slist_length(adapter->disc_sessions) == 1)
|
|
mgmt_start_le_scanning(adapter->dev_id);
|
|
else
|
|
mgmt_start_discovery(adapter->dev_id);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
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;
|
|
|
|
if (adapter->discov_suspended)
|
|
goto done;
|
|
|
|
err = mgmt_start_discovery(adapter->dev_id);
|
|
if (err < 0)
|
|
return btd_error_failed(msg, strerror(-err));
|
|
|
|
done:
|
|
req = create_session(adapter, msg, 0, SESSION_TYPE_DISC_INTERLEAVED,
|
|
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);
|
|
}
|
|
|
|
static gboolean adapter_property_get_address(
|
|
const GDBusPropertyTable *property,
|
|
DBusMessageIter *iter, void *data)
|
|
{
|
|
struct btd_adapter *adapter = data;
|
|
char srcaddr[18];
|
|
const char *ptr;
|
|
|
|
ba2str(&adapter->bdaddr, srcaddr);
|
|
ptr = srcaddr;
|
|
|
|
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ptr);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean adapter_property_get_name(const GDBusPropertyTable *property,
|
|
DBusMessageIter *iter, void *data)
|
|
{
|
|
struct btd_adapter *adapter = data;
|
|
const char *ptr;
|
|
|
|
ptr = adapter->name ?: "";
|
|
|
|
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ptr);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void adapter_property_set_name(const GDBusPropertyTable *property,
|
|
DBusMessageIter *value,
|
|
GDBusPendingPropertySet id, void *data)
|
|
{
|
|
const char *name;
|
|
|
|
if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_STRING)
|
|
return g_dbus_pending_property_error(id,
|
|
ERROR_INTERFACE ".InvalidArguments",
|
|
"Invalid arguments in method call");
|
|
|
|
dbus_message_iter_get_basic(value, &name);
|
|
|
|
set_name(data, name, id);
|
|
}
|
|
|
|
static gboolean adapter_property_get_class(const GDBusPropertyTable *property,
|
|
DBusMessageIter *iter, void *data)
|
|
{
|
|
struct btd_adapter *adapter = data;
|
|
|
|
dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32,
|
|
&adapter->dev_class);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean adapter_property_get_powered(
|
|
const GDBusPropertyTable *property,
|
|
DBusMessageIter *iter, void *data)
|
|
{
|
|
struct btd_adapter *adapter = data;
|
|
dbus_bool_t value;
|
|
|
|
value = (adapter->up && !adapter->off_requested) ? TRUE : FALSE;
|
|
dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void adapter_property_set_powered(
|
|
const GDBusPropertyTable *property,
|
|
DBusMessageIter *value,
|
|
GDBusPendingPropertySet id, void *data)
|
|
{
|
|
dbus_bool_t powered;
|
|
|
|
if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN)
|
|
return g_dbus_pending_property_error(id,
|
|
ERROR_INTERFACE ".InvalidArguments",
|
|
"Invalid arguments in method call");
|
|
|
|
dbus_message_iter_get_basic(value, &powered);
|
|
|
|
set_powered(data, powered, id);
|
|
}
|
|
|
|
static gboolean adapter_property_get_discoverable(
|
|
const GDBusPropertyTable *property,
|
|
DBusMessageIter *iter, void *data)
|
|
{
|
|
struct btd_adapter *adapter = data;
|
|
dbus_bool_t value;
|
|
|
|
value = adapter->scan_mode & SCAN_INQUIRY ? TRUE : FALSE;
|
|
dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void adapter_property_set_discoverable(
|
|
const GDBusPropertyTable *property,
|
|
DBusMessageIter *value,
|
|
GDBusPendingPropertySet id, void *data)
|
|
{
|
|
dbus_bool_t discoverable;
|
|
|
|
if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN)
|
|
return g_dbus_pending_property_error(id,
|
|
ERROR_INTERFACE ".InvalidArguments",
|
|
"Invalid arguments in method call");
|
|
|
|
dbus_message_iter_get_basic(value, &discoverable);
|
|
|
|
set_discoverable(data, discoverable, id);
|
|
}
|
|
|
|
static gboolean adapter_property_get_pairable(
|
|
const GDBusPropertyTable *property,
|
|
DBusMessageIter *iter, void *data)
|
|
{
|
|
struct btd_adapter *adapter = data;
|
|
|
|
dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN,
|
|
&adapter->pairable);
|
|
return TRUE;
|
|
}
|
|
|
|
static void adapter_property_set_pairable(const GDBusPropertyTable *property,
|
|
DBusMessageIter *value,
|
|
GDBusPendingPropertySet id, void *data)
|
|
{
|
|
dbus_bool_t pairable;
|
|
|
|
if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN)
|
|
return g_dbus_pending_property_error(id,
|
|
ERROR_INTERFACE ".InvalidArguments",
|
|
"Invalid arguments in method call");
|
|
|
|
dbus_message_iter_get_basic(value, &pairable);
|
|
|
|
set_pairable(data, pairable, true, id);
|
|
}
|
|
|
|
static gboolean adapter_property_get_discoverable_timeout(
|
|
const GDBusPropertyTable *property,
|
|
DBusMessageIter *iter, void *data)
|
|
{
|
|
struct btd_adapter *adapter = data;
|
|
|
|
dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32,
|
|
&adapter->discov_timeout);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static void adapter_property_set_discoverable_timeout(
|
|
const GDBusPropertyTable *property, DBusMessageIter *value,
|
|
GDBusPendingPropertySet id, void *data)
|
|
{
|
|
uint32_t timeout;
|
|
|
|
if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_UINT32)
|
|
return g_dbus_pending_property_error(id,
|
|
ERROR_INTERFACE ".InvalidArguments",
|
|
"Invalid arguments in method call");
|
|
|
|
dbus_message_iter_get_basic(value, &timeout);
|
|
set_discoverable_timeout(data, timeout, id);
|
|
}
|
|
|
|
static gboolean adapter_property_get_pairable_timeout(
|
|
const GDBusPropertyTable *property,
|
|
DBusMessageIter *iter, void *data)
|
|
{
|
|
struct btd_adapter *adapter = data;
|
|
|
|
dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32,
|
|
&adapter->pairable_timeout);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void adapter_property_set_pairable_timeout(
|
|
const GDBusPropertyTable *property, DBusMessageIter *value,
|
|
GDBusPendingPropertySet id, void *data)
|
|
{
|
|
uint32_t timeout;
|
|
|
|
if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_UINT32)
|
|
return g_dbus_pending_property_error(id,
|
|
ERROR_INTERFACE ".InvalidArguments",
|
|
"Invalid arguments in method call");
|
|
|
|
dbus_message_iter_get_basic(value, &timeout);
|
|
set_pairable_timeout(data, timeout, id);
|
|
}
|
|
|
|
static gboolean adapter_property_get_discovering(
|
|
const GDBusPropertyTable *property,
|
|
DBusMessageIter *iter, void *data)
|
|
{
|
|
struct btd_adapter *adapter = data;
|
|
dbus_bool_t discovering = adapter->discovery ? TRUE : FALSE;
|
|
|
|
dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &discovering);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean adapter_property_get_devices(
|
|
const GDBusPropertyTable *property,
|
|
DBusMessageIter *iter, void *data)
|
|
{
|
|
struct btd_adapter *adapter = data;
|
|
DBusMessageIter entry;
|
|
GSList *l;
|
|
|
|
dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
|
|
DBUS_TYPE_OBJECT_PATH_AS_STRING, &entry);
|
|
|
|
for (l = adapter->devices; l != NULL; l = l->next) {
|
|
const char *path = device_get_path(l->data);
|
|
|
|
dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH,
|
|
&path);
|
|
}
|
|
|
|
dbus_message_iter_close_container(iter, &entry);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean adapter_property_get_uuids(const GDBusPropertyTable *property,
|
|
DBusMessageIter *iter, void *data)
|
|
{
|
|
struct btd_adapter *adapter = data;
|
|
DBusMessageIter entry;
|
|
sdp_list_t *l;
|
|
|
|
dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
|
|
DBUS_TYPE_STRING_AS_STRING, &entry);
|
|
|
|
for (l = adapter->services; l != NULL; l = l->next) {
|
|
sdp_record_t *rec = l->data;
|
|
char *uuid;
|
|
|
|
uuid = bt_uuid2string(&rec->svclass);
|
|
if (uuid == NULL)
|
|
continue;
|
|
|
|
dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING,
|
|
&uuid);
|
|
g_free(uuid);
|
|
}
|
|
|
|
dbus_message_iter_close_container(iter, &entry);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
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;
|
|
|
|
if (adapter->discoverable)
|
|
new_mode = MODE_DISCOVERABLE;
|
|
else
|
|
new_mode = MODE_CONNECTABLE;
|
|
|
|
req = find_session(adapter->mode_sessions, sender);
|
|
if (req) {
|
|
session_ref(req);
|
|
return dbus_message_new_method_return(msg);
|
|
} else {
|
|
req = create_session(adapter, msg, new_mode,
|
|
SESSION_TYPE_MODE_SESSION, 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 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;
|
|
if (g_str_equal(capability, "KeyboardDisplay"))
|
|
return IO_CAPABILITY_KEYBOARDDISPLAY;
|
|
return IO_CAPABILITY_INVALID;
|
|
}
|
|
|
|
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;
|
|
|
|
device_set_temporary(device, TRUE);
|
|
|
|
if (!device_is_connected(device)) {
|
|
adapter_remove_device(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)
|
|
{
|
|
mgmt_set_io_capability(adapter->dev_id, IO_CAPABILITY_NOINPUTNOOUTPUT);
|
|
|
|
adapter->agent = NULL;
|
|
}
|
|
|
|
static DBusMessage *register_agent(DBusConnection *conn, DBusMessage *msg,
|
|
void *data)
|
|
{
|
|
const char *path, *name, *capability;
|
|
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 btd_error_invalid_args(msg);
|
|
|
|
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);
|
|
|
|
adapter->agent = agent_create(adapter, name, path, cap,
|
|
(agent_remove_cb) agent_removed, adapter);
|
|
|
|
DBG("Agent registered for hci%d at %s:%s", adapter->dev_id, name,
|
|
path);
|
|
|
|
mgmt_set_io_capability(adapter->dev_id, cap);
|
|
|
|
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 btd_error_invalid_args(msg);
|
|
|
|
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 const GDBusMethodTable adapter_methods[] = {
|
|
{ GDBUS_ASYNC_METHOD("RequestSession", NULL, NULL,
|
|
request_session) },
|
|
{ GDBUS_METHOD("ReleaseSession", NULL, NULL,
|
|
release_session) },
|
|
{ GDBUS_METHOD("StartDiscovery", NULL, NULL,
|
|
adapter_start_discovery) },
|
|
{ GDBUS_ASYNC_METHOD("StopDiscovery", NULL, NULL,
|
|
adapter_stop_discovery) },
|
|
{ GDBUS_ASYNC_METHOD("RemoveDevice",
|
|
GDBUS_ARGS({ "device", "o" }), NULL,
|
|
remove_device) },
|
|
{ GDBUS_METHOD("FindDevice",
|
|
GDBUS_ARGS({ "address", "s" }),
|
|
GDBUS_ARGS({ "device", "o" }),
|
|
find_device) },
|
|
{ GDBUS_METHOD("RegisterAgent",
|
|
GDBUS_ARGS({ "agent", "o" },
|
|
{ "capability", "s" }), NULL,
|
|
register_agent) },
|
|
{ GDBUS_METHOD("UnregisterAgent",
|
|
GDBUS_ARGS({ "agent", "o" }), NULL,
|
|
unregister_agent) },
|
|
{ }
|
|
};
|
|
|
|
static const GDBusSignalTable adapter_signals[] = {
|
|
{ GDBUS_SIGNAL("DeviceCreated",
|
|
GDBUS_ARGS({ "device", "o" })) },
|
|
{ GDBUS_SIGNAL("DeviceRemoved",
|
|
GDBUS_ARGS({ "device", "o" })) },
|
|
{ GDBUS_SIGNAL("DevicesFound",
|
|
GDBUS_ARGS({ "devices", "a{oa{sv}}" })) },
|
|
{ }
|
|
};
|
|
|
|
static const GDBusPropertyTable adapter_properties[] = {
|
|
{ "Address", "s", adapter_property_get_address },
|
|
{ "Name", "s", adapter_property_get_name, adapter_property_set_name },
|
|
{ "Class", "u", adapter_property_get_class },
|
|
{ "Powered", "b", adapter_property_get_powered,
|
|
adapter_property_set_powered },
|
|
{ "Discoverable", "b", adapter_property_get_discoverable,
|
|
adapter_property_set_discoverable },
|
|
{ "Pairable", "b", adapter_property_get_pairable,
|
|
adapter_property_set_pairable },
|
|
{ "DiscoverableTimeout", "u",
|
|
adapter_property_get_discoverable_timeout,
|
|
adapter_property_set_discoverable_timeout },
|
|
{ "PairableTimeout", "u", adapter_property_get_pairable_timeout,
|
|
adapter_property_set_pairable_timeout },
|
|
{ "Discovering", "b", adapter_property_get_discovering },
|
|
{ "Devices", "ao", adapter_property_get_devices },
|
|
{ "UUIDs", "as", adapter_property_get_uuids },
|
|
{ }
|
|
};
|
|
|
|
static void create_stored_device_from_profiles(char *key, char *value,
|
|
void *user_data)
|
|
{
|
|
char address[18];
|
|
uint8_t bdaddr_type;
|
|
struct btd_adapter *adapter = user_data;
|
|
GSList *list, *uuids = bt_string2list(value);
|
|
struct btd_device *device;
|
|
|
|
if (sscanf(key, "%17s#%hhu", address, &bdaddr_type) < 2)
|
|
bdaddr_type = BDADDR_BREDR;
|
|
|
|
if (g_slist_find_custom(adapter->devices,
|
|
address, (GCompareFunc) device_address_cmp))
|
|
return;
|
|
|
|
device = device_create(adapter, address, bdaddr_type);
|
|
if (!device)
|
|
return;
|
|
|
|
device_set_temporary(device, FALSE);
|
|
adapter->devices = g_slist_append(adapter->devices, device);
|
|
|
|
list = device_services_from_record(device, uuids);
|
|
if (list)
|
|
device_register_services(device, list, ATT_PSM);
|
|
|
|
device_probe_profiles(device, uuids);
|
|
|
|
g_slist_free_full(uuids, g_free);
|
|
}
|
|
|
|
struct adapter_keys {
|
|
struct btd_adapter *adapter;
|
|
GSList *keys;
|
|
};
|
|
|
|
static int str2buf(const char *str, uint8_t *buf, size_t blen)
|
|
{
|
|
int i, dlen;
|
|
|
|
if (str == NULL)
|
|
return -EINVAL;
|
|
|
|
memset(buf, 0, blen);
|
|
|
|
dlen = MIN((strlen(str) / 2), blen);
|
|
|
|
for (i = 0; i < dlen; i++)
|
|
sscanf(str + (i * 2), "%02hhX", &buf[i]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct link_key_info *get_key_info(GKeyFile *key_file, const char *peer)
|
|
{
|
|
struct link_key_info *info = NULL;
|
|
char *str;
|
|
|
|
str = g_key_file_get_string(key_file, "LinkKey", "Key", NULL);
|
|
if (!str || strlen(str) != 34)
|
|
goto failed;
|
|
|
|
info = g_new0(struct link_key_info, 1);
|
|
|
|
str2ba(peer, &info->bdaddr);
|
|
str2buf(&str[2], info->key, sizeof(info->key));
|
|
|
|
info->type = g_key_file_get_integer(key_file, "LinkKey", "Type", NULL);
|
|
info->pin_len = g_key_file_get_integer(key_file, "LinkKey", "PINLength",
|
|
NULL);
|
|
|
|
failed:
|
|
g_free(str);
|
|
|
|
return info;
|
|
}
|
|
|
|
static struct smp_ltk_info *get_ltk_info(GKeyFile *key_file, const char *peer)
|
|
{
|
|
struct smp_ltk_info *ltk = NULL;
|
|
char *key;
|
|
char *rand = NULL;
|
|
char *type = NULL;
|
|
uint8_t bdaddr_type;
|
|
|
|
key = g_key_file_get_string(key_file, "LongTermKey", "Key", NULL);
|
|
if (!key || strlen(key) != 34)
|
|
goto failed;
|
|
|
|
rand = g_key_file_get_string(key_file, "LongTermKey", "Rand", NULL);
|
|
if (!rand || strlen(rand) != 18)
|
|
goto failed;
|
|
|
|
type = g_key_file_get_string(key_file, "General", "AddressType", NULL);
|
|
if (!type)
|
|
goto failed;
|
|
|
|
if (g_str_equal(type, "public"))
|
|
bdaddr_type = BDADDR_LE_PUBLIC;
|
|
else if (g_str_equal(type, "static"))
|
|
bdaddr_type = BDADDR_LE_RANDOM;
|
|
else
|
|
goto failed;
|
|
|
|
ltk = g_new0(struct smp_ltk_info, 1);
|
|
|
|
str2ba(peer, <k->bdaddr);
|
|
ltk->bdaddr_type = bdaddr_type;
|
|
str2buf(&key[2], ltk->val, sizeof(ltk->val));
|
|
str2buf(&rand[2], ltk->rand, sizeof(ltk->rand));
|
|
|
|
ltk->authenticated = g_key_file_get_integer(key_file, "LongTermKey",
|
|
"Authenticated", NULL);
|
|
ltk->master = g_key_file_get_integer(key_file, "LongTermKey", "Master",
|
|
NULL);
|
|
ltk->enc_size = g_key_file_get_integer(key_file, "LongTermKey",
|
|
"EncSize", NULL);
|
|
ltk->ediv = g_key_file_get_integer(key_file, "LongTermKey", "EDiv",
|
|
NULL);
|
|
|
|
failed:
|
|
g_free(key);
|
|
g_free(rand);
|
|
g_free(type);
|
|
|
|
return ltk;
|
|
}
|
|
|
|
static GSList *string_to_primary_list(char *str)
|
|
{
|
|
GSList *l = NULL;
|
|
char **services;
|
|
int i;
|
|
|
|
if (str == NULL)
|
|
return NULL;
|
|
|
|
services = g_strsplit(str, " ", 0);
|
|
if (services == NULL)
|
|
return NULL;
|
|
|
|
for (i = 0; services[i]; i++) {
|
|
struct gatt_primary *prim;
|
|
int ret;
|
|
|
|
prim = g_new0(struct gatt_primary, 1);
|
|
|
|
ret = sscanf(services[i], "%04hX#%04hX#%s", &prim->range.start,
|
|
&prim->range.end, prim->uuid);
|
|
|
|
if (ret < 3) {
|
|
g_free(prim);
|
|
continue;
|
|
}
|
|
|
|
l = g_slist_append(l, prim);
|
|
}
|
|
|
|
g_strfreev(services);
|
|
|
|
return l;
|
|
}
|
|
|
|
static void create_stored_device_from_primaries(char *key, char *value,
|
|
void *user_data)
|
|
{
|
|
struct btd_adapter *adapter = user_data;
|
|
struct btd_device *device;
|
|
GSList *services, *uuids, *l;
|
|
char address[18];
|
|
uint8_t bdaddr_type;
|
|
|
|
if (sscanf(key, "%17s#%hhu", address, &bdaddr_type) < 2)
|
|
return;
|
|
|
|
if (g_slist_find_custom(adapter->devices,
|
|
address, (GCompareFunc) device_address_cmp))
|
|
return;
|
|
|
|
device = device_create(adapter, address, bdaddr_type);
|
|
if (!device)
|
|
return;
|
|
|
|
device_set_temporary(device, FALSE);
|
|
adapter->devices = g_slist_append(adapter->devices, device);
|
|
|
|
services = string_to_primary_list(value);
|
|
if (services == NULL)
|
|
return;
|
|
|
|
for (l = services, uuids = NULL; l; l = l->next) {
|
|
struct gatt_primary *prim = l->data;
|
|
uuids = g_slist_append(uuids, prim->uuid);
|
|
}
|
|
|
|
device_register_services(device, services, -1);
|
|
|
|
device_probe_profiles(device, uuids);
|
|
|
|
g_slist_free(uuids);
|
|
}
|
|
|
|
static void load_devices(struct btd_adapter *adapter)
|
|
{
|
|
char filename[PATH_MAX + 1];
|
|
char srcaddr[18];
|
|
struct adapter_keys keys = { adapter, NULL };
|
|
struct adapter_keys ltks = { adapter, NULL };
|
|
int err;
|
|
DIR *dir;
|
|
struct dirent *entry;
|
|
|
|
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, "primaries");
|
|
textfile_foreach(filename, create_stored_device_from_primaries,
|
|
adapter);
|
|
|
|
snprintf(filename, PATH_MAX, STORAGEDIR "/%s", srcaddr);
|
|
filename[PATH_MAX] = '\0';
|
|
|
|
dir = opendir(filename);
|
|
if (!dir) {
|
|
error("Unable to open adapter storage directory: %s", filename);
|
|
return;
|
|
}
|
|
|
|
while ((entry = readdir(dir)) != NULL) {
|
|
struct btd_device *device;
|
|
char filename[PATH_MAX + 1];
|
|
GKeyFile *key_file;
|
|
struct link_key_info *key_info;
|
|
struct smp_ltk_info *ltk_info;
|
|
GSList *l;
|
|
|
|
if (entry->d_type != DT_DIR || bachk(entry->d_name) < 0)
|
|
continue;
|
|
|
|
snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", srcaddr,
|
|
entry->d_name);
|
|
|
|
key_file = g_key_file_new();
|
|
g_key_file_load_from_file(key_file, filename, 0, NULL);
|
|
|
|
key_info = get_key_info(key_file, entry->d_name);
|
|
if (key_info)
|
|
keys.keys = g_slist_append(keys.keys, key_info);
|
|
|
|
ltk_info = get_ltk_info(key_file, entry->d_name);
|
|
if (ltk_info)
|
|
ltks.keys = g_slist_append(ltks.keys, ltk_info);
|
|
|
|
g_key_file_free(key_file);
|
|
|
|
l = g_slist_find_custom(adapter->devices, entry->d_name,
|
|
(GCompareFunc) device_address_cmp);
|
|
if (l) {
|
|
device = l->data;
|
|
goto device_exist;
|
|
}
|
|
|
|
device = device_create(adapter, entry->d_name, BDADDR_BREDR);
|
|
if (!device)
|
|
continue;
|
|
|
|
device_set_temporary(device, FALSE);
|
|
adapter->devices = g_slist_append(adapter->devices, device);
|
|
|
|
device_exist:
|
|
if (key_info || ltk_info) {
|
|
device_set_paired(device, TRUE);
|
|
device_set_bonded(device, TRUE);
|
|
}
|
|
}
|
|
|
|
closedir(dir);
|
|
|
|
err = mgmt_load_link_keys(adapter->dev_id, keys.keys,
|
|
main_opts.debug_keys);
|
|
if (err < 0)
|
|
error("Unable to load link keys: %s (%d)",
|
|
strerror(-err), -err);
|
|
|
|
g_slist_free_full(keys.keys, g_free);
|
|
|
|
err = mgmt_load_ltks(adapter->dev_id, ltks.keys);
|
|
if (err < 0)
|
|
error("Unable to load ltks: %s (%d)", strerror(-err), -err);
|
|
|
|
g_slist_free_full(ltks.keys, g_free);
|
|
}
|
|
|
|
int btd_adapter_block_address(struct btd_adapter *adapter,
|
|
const bdaddr_t *bdaddr, uint8_t bdaddr_type)
|
|
{
|
|
return mgmt_block_device(adapter->dev_id, bdaddr, bdaddr_type);
|
|
}
|
|
|
|
int btd_adapter_unblock_address(struct btd_adapter *adapter,
|
|
const bdaddr_t *bdaddr, uint8_t bdaddr_type)
|
|
{
|
|
return mgmt_unblock_device(adapter->dev_id, bdaddr,
|
|
bdaddr_type);
|
|
}
|
|
|
|
static void clear_blocked(struct btd_adapter *adapter)
|
|
{
|
|
int err;
|
|
|
|
err = mgmt_unblock_device(adapter->dev_id, BDADDR_ANY, 0);
|
|
if (err < 0)
|
|
error("Clearing blocked list failed: %s (%d)",
|
|
strerror(-err), -err);
|
|
}
|
|
|
|
static void probe_driver(struct btd_adapter *adapter, gpointer user_data)
|
|
{
|
|
struct btd_adapter_driver *driver = user_data;
|
|
int err;
|
|
|
|
if (driver->probe == NULL)
|
|
return;
|
|
|
|
err = driver->probe(adapter);
|
|
if (err < 0) {
|
|
error("%s: %s (%d)", driver->name, strerror(-err), -err);
|
|
return;
|
|
}
|
|
|
|
adapter->drivers = g_slist_prepend(adapter->drivers, driver);
|
|
}
|
|
|
|
static void load_drivers(struct btd_adapter *adapter)
|
|
{
|
|
GSList *l;
|
|
|
|
for (l = adapter_drivers; l; l = l->next)
|
|
probe_driver(adapter, l->data);
|
|
}
|
|
|
|
static void probe_profile(struct btd_profile *profile, void *data)
|
|
{
|
|
struct btd_adapter *adapter = data;
|
|
int err;
|
|
|
|
if (profile->adapter_probe == NULL)
|
|
return;
|
|
|
|
err = profile->adapter_probe(profile, adapter);
|
|
if (err < 0) {
|
|
error("%s: %s (%d)", profile->name, strerror(-err), -err);
|
|
return;
|
|
}
|
|
|
|
adapter->profiles = g_slist_prepend(adapter->profiles, profile);
|
|
}
|
|
|
|
void adapter_add_profile(struct btd_adapter *adapter, gpointer p)
|
|
{
|
|
struct btd_profile *profile = p;
|
|
|
|
if (!adapter->initialized)
|
|
return;
|
|
|
|
probe_profile(profile, adapter);
|
|
|
|
g_slist_foreach(adapter->devices, device_probe_profile, profile);
|
|
}
|
|
|
|
void adapter_remove_profile(struct btd_adapter *adapter, gpointer p)
|
|
{
|
|
struct btd_profile *profile = p;
|
|
|
|
if (!adapter->initialized)
|
|
return;
|
|
|
|
if (profile->device_remove)
|
|
g_slist_foreach(adapter->devices, device_remove_profile, p);
|
|
|
|
adapter->profiles = g_slist_remove(adapter->profiles, profile);
|
|
|
|
if (profile->adapter_remove)
|
|
profile->adapter_remove(profile, adapter);
|
|
}
|
|
|
|
static void load_connections(struct btd_adapter *adapter)
|
|
{
|
|
GSList *l, *conns;
|
|
int err;
|
|
|
|
err = mgmt_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)) {
|
|
bdaddr_t *bdaddr = l->data;
|
|
struct btd_device *device;
|
|
char address[18];
|
|
|
|
ba2str(bdaddr, address);
|
|
DBG("Adding existing connection to %s", address);
|
|
|
|
device = adapter_get_device(adapter, address);
|
|
if (device)
|
|
adapter_add_connection(adapter, device);
|
|
}
|
|
|
|
g_slist_free_full(conns, g_free);
|
|
}
|
|
|
|
static void set_auto_connect(gpointer data, gpointer user_data)
|
|
{
|
|
struct btd_device *device = data;
|
|
gboolean *enable = user_data;
|
|
|
|
device_set_auto_connect(device, *enable);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
g_slist_foreach(adapter->devices, set_auto_connect, &powered);
|
|
}
|
|
|
|
void btd_adapter_get_mode(struct btd_adapter *adapter, uint8_t *mode,
|
|
uint16_t *discoverable_timeout,
|
|
gboolean *pairable)
|
|
{
|
|
if (mode)
|
|
*mode = adapter->mode;
|
|
|
|
if (discoverable_timeout)
|
|
*discoverable_timeout = adapter->discov_timeout;
|
|
|
|
if (pairable)
|
|
*pairable = adapter->pairable;
|
|
}
|
|
|
|
void btd_adapter_get_major_minor(struct btd_adapter *adapter, uint8_t *major,
|
|
uint8_t *minor)
|
|
{
|
|
*major = (adapter->dev_class >> 8) & 0xFF;
|
|
*minor = adapter->dev_class & 0xFF;
|
|
}
|
|
|
|
uint32_t btd_adapter_get_class(struct btd_adapter *adapter)
|
|
{
|
|
return adapter->dev_class;
|
|
}
|
|
|
|
const char *btd_adapter_get_name(struct btd_adapter *adapter)
|
|
{
|
|
return adapter->name;
|
|
}
|
|
|
|
void adapter_connect_list_add(struct btd_adapter *adapter,
|
|
struct btd_device *device)
|
|
{
|
|
struct session_req *req;
|
|
|
|
if (g_slist_find(adapter->connect_list, device)) {
|
|
DBG("ignoring already added device %s",
|
|
device_get_path(device));
|
|
return;
|
|
}
|
|
|
|
adapter->connect_list = g_slist_append(adapter->connect_list,
|
|
btd_device_ref(device));
|
|
DBG("%s added to %s's connect_list", device_get_path(device),
|
|
adapter->name);
|
|
|
|
if (!adapter->up)
|
|
return;
|
|
|
|
if (adapter->off_requested)
|
|
return;
|
|
|
|
if (adapter->scanning_session)
|
|
return;
|
|
|
|
if (adapter->disc_sessions == NULL)
|
|
adapter->discov_id = g_idle_add(discovery_cb, adapter);
|
|
|
|
req = create_session(adapter, NULL, 0, SESSION_TYPE_DISC_LE_SCAN,
|
|
NULL);
|
|
adapter->disc_sessions = g_slist_append(adapter->disc_sessions, req);
|
|
adapter->scanning_session = req;
|
|
}
|
|
|
|
void adapter_connect_list_remove(struct btd_adapter *adapter,
|
|
struct btd_device *device)
|
|
{
|
|
if (!g_slist_find(adapter->connect_list, device)) {
|
|
DBG("device %s is not on the list, ignoring",
|
|
device_get_path(device));
|
|
return;
|
|
}
|
|
|
|
adapter->connect_list = g_slist_remove(adapter->connect_list, device);
|
|
DBG("%s removed from %s's connect_list", device_get_path(device),
|
|
adapter->name);
|
|
btd_device_unref(device);
|
|
}
|
|
|
|
void btd_adapter_start(struct btd_adapter *adapter)
|
|
{
|
|
struct session_req *req;
|
|
|
|
adapter->off_requested = FALSE;
|
|
adapter->up = TRUE;
|
|
adapter->off_timer = 0;
|
|
|
|
if (adapter->scan_mode & SCAN_INQUIRY) {
|
|
adapter->mode = MODE_DISCOVERABLE;
|
|
adapter->discoverable = TRUE;
|
|
} else {
|
|
adapter->mode = MODE_CONNECTABLE;
|
|
adapter->discoverable = FALSE;
|
|
}
|
|
|
|
g_dbus_emit_property_changed(btd_get_dbus_connection(), adapter->path,
|
|
ADAPTER_INTERFACE, "Powered");
|
|
|
|
call_adapter_powered_callbacks(adapter, TRUE);
|
|
|
|
info("Adapter %s has been enabled", adapter->path);
|
|
|
|
if (g_slist_length(adapter->connect_list) == 0 ||
|
|
adapter->disc_sessions != NULL)
|
|
return;
|
|
|
|
req = create_session(adapter, NULL, 0, SESSION_TYPE_DISC_LE_SCAN,
|
|
NULL);
|
|
adapter->disc_sessions = g_slist_append(adapter->disc_sessions, req);
|
|
adapter->scanning_session = req;
|
|
|
|
adapter->discov_id = g_idle_add(discovery_cb, adapter);
|
|
}
|
|
|
|
static void reply_pending_requests(struct btd_adapter *adapter)
|
|
{
|
|
GSList *l;
|
|
|
|
if (!adapter)
|
|
return;
|
|
|
|
/* pending bonding */
|
|
for (l = adapter->devices; l; l = l->next) {
|
|
struct btd_device *device = l->data;
|
|
|
|
if (device_is_bonding(device, NULL))
|
|
device_cancel_bonding(device,
|
|
HCI_OE_USER_ENDED_CONNECTION);
|
|
}
|
|
}
|
|
|
|
static void remove_driver(gpointer data, gpointer user_data)
|
|
{
|
|
struct btd_adapter_driver *driver = data;
|
|
struct btd_adapter *adapter = user_data;
|
|
|
|
if (driver->remove)
|
|
driver->remove(adapter);
|
|
}
|
|
|
|
static void remove_profile(gpointer data, gpointer user_data)
|
|
{
|
|
struct btd_profile *profile = data;
|
|
struct btd_adapter *adapter = user_data;
|
|
|
|
if (profile->adapter_remove)
|
|
profile->adapter_remove(profile, adapter);
|
|
}
|
|
|
|
static void unload_drivers(struct btd_adapter *adapter)
|
|
{
|
|
g_slist_foreach(adapter->drivers, remove_driver, adapter);
|
|
g_slist_free(adapter->drivers);
|
|
adapter->drivers = NULL;
|
|
|
|
g_slist_foreach(adapter->profiles, remove_profile, adapter);
|
|
g_slist_free(adapter->profiles);
|
|
adapter->profiles = NULL;
|
|
}
|
|
|
|
static void set_mode_complete(struct btd_adapter *adapter)
|
|
{
|
|
DBusConnection *conn = btd_get_dbus_connection();
|
|
struct session_req *pending;
|
|
int err;
|
|
|
|
store_adapter_info(adapter);
|
|
|
|
DBG("%s", mode2str(adapter->mode));
|
|
|
|
if (adapter->mode == MODE_OFF) {
|
|
g_slist_free_full(adapter->mode_sessions, session_free);
|
|
adapter->mode_sessions = NULL;
|
|
}
|
|
|
|
if (adapter->pending_mode == NULL)
|
|
return;
|
|
|
|
pending = adapter->pending_mode;
|
|
adapter->pending_mode = NULL;
|
|
|
|
err = (pending->mode != adapter->mode) ? -EINVAL : 0;
|
|
|
|
if (pending->type == SESSION_TYPE_MODE_GLOBAL) {
|
|
if (err < 0)
|
|
g_dbus_pending_property_error(pending->prop_id,
|
|
ERROR_INTERFACE ".Failed",
|
|
strerror(-err));
|
|
else {
|
|
adapter->global_mode = adapter->mode;
|
|
g_dbus_pending_property_success(pending->prop_id);
|
|
}
|
|
} else 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(conn, reply);
|
|
}
|
|
|
|
if (err != 0)
|
|
error("unable to set mode: %s", mode2str(pending->mode));
|
|
|
|
session_unref(pending);
|
|
}
|
|
|
|
int btd_adapter_stop(struct btd_adapter *adapter)
|
|
{
|
|
DBusConnection *conn = btd_get_dbus_connection();
|
|
bool emit_discoverable = false, emit_pairable = false;
|
|
bool emit_discovering = false;
|
|
|
|
/* check pending requests */
|
|
reply_pending_requests(adapter);
|
|
|
|
adapter->up = FALSE;
|
|
|
|
if (adapter->discovery) {
|
|
emit_discovering = true;
|
|
stop_discovery(adapter);
|
|
}
|
|
|
|
if (adapter->disc_sessions) {
|
|
g_slist_free_full(adapter->disc_sessions, session_free);
|
|
adapter->disc_sessions = NULL;
|
|
}
|
|
|
|
while (adapter->connections) {
|
|
struct btd_device *device = adapter->connections->data;
|
|
adapter_remove_connection(adapter, device);
|
|
}
|
|
|
|
if (adapter->scan_mode == (SCAN_PAGE | SCAN_INQUIRY))
|
|
emit_discoverable = true;
|
|
|
|
if ((adapter->scan_mode & SCAN_PAGE) && adapter->pairable == TRUE)
|
|
emit_pairable = true;
|
|
|
|
adapter->scan_mode = SCAN_DISABLED;
|
|
adapter->mode = MODE_OFF;
|
|
adapter->off_requested = FALSE;
|
|
|
|
if (emit_discoverable)
|
|
g_dbus_emit_property_changed(conn, adapter->path,
|
|
ADAPTER_INTERFACE, "Discoverable");
|
|
if (emit_pairable)
|
|
g_dbus_emit_property_changed(conn, adapter->path,
|
|
ADAPTER_INTERFACE, "Pairable");
|
|
|
|
if (emit_discovering)
|
|
g_dbus_emit_property_changed(conn, adapter->path,
|
|
ADAPTER_INTERFACE, "Discovering");
|
|
|
|
g_dbus_emit_property_changed(conn, adapter->path, ADAPTER_INTERFACE,
|
|
"Powered");
|
|
|
|
call_adapter_powered_callbacks(adapter, FALSE);
|
|
|
|
info("Adapter %s has been disabled", adapter->path);
|
|
|
|
set_mode_complete(adapter);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void off_timer_remove(struct btd_adapter *adapter)
|
|
{
|
|
g_source_remove(adapter->off_timer);
|
|
adapter->off_timer = 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);
|
|
|
|
g_queue_free_full(adapter->auths, g_free);
|
|
|
|
if (adapter->off_timer)
|
|
off_timer_remove(adapter);
|
|
|
|
sdp_list_free(adapter->services, NULL);
|
|
|
|
g_slist_free(adapter->connections);
|
|
|
|
g_free(adapter->path);
|
|
g_free(adapter->name);
|
|
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(btd_get_dbus_connection(),
|
|
path, ADAPTER_INTERFACE);
|
|
|
|
g_free(path);
|
|
}
|
|
|
|
static void convert_names_entry(char *key, char *value, void *user_data)
|
|
{
|
|
char *address = user_data;
|
|
char *str = key;
|
|
bdaddr_t local, peer;
|
|
|
|
if (strchr(key, '#'))
|
|
str[17] = '\0';
|
|
|
|
if (bachk(str) != 0)
|
|
return;
|
|
|
|
str2ba(address, &local);
|
|
str2ba(str, &peer);
|
|
adapter_store_cached_name(&local, &peer, value);
|
|
}
|
|
|
|
struct device_converter {
|
|
char *address;
|
|
void (*cb)(GKeyFile *key_file, void *value);
|
|
gboolean force;
|
|
};
|
|
|
|
static void set_device_type(GKeyFile *key_file, char type)
|
|
{
|
|
char *techno;
|
|
char *addr_type = NULL;
|
|
char *str;
|
|
|
|
switch (type) {
|
|
case BDADDR_BREDR:
|
|
techno = "BR/EDR";
|
|
break;
|
|
case BDADDR_LE_PUBLIC:
|
|
techno = "LE";
|
|
addr_type = "public";
|
|
break;
|
|
case BDADDR_LE_RANDOM:
|
|
techno = "LE";
|
|
addr_type = "static";
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
str = g_key_file_get_string(key_file, "General",
|
|
"SupportedTechnologies", NULL);
|
|
if (!str)
|
|
g_key_file_set_string(key_file, "General",
|
|
"SupportedTechnologies", techno);
|
|
else if (!strstr(str, techno))
|
|
g_key_file_set_string(key_file, "General",
|
|
"SupportedTechnologies", "BR/EDR;LE");
|
|
|
|
g_free(str);
|
|
|
|
if (addr_type)
|
|
g_key_file_set_string(key_file, "General", "AddressType",
|
|
addr_type);
|
|
}
|
|
|
|
static void convert_aliases_entry(GKeyFile *key_file, void *value)
|
|
{
|
|
g_key_file_set_string(key_file, "General", "Alias", value);
|
|
}
|
|
|
|
static void convert_trusts_entry(GKeyFile *key_file, void *value)
|
|
{
|
|
g_key_file_set_boolean(key_file, "General", "Trusted", TRUE);
|
|
}
|
|
|
|
static void convert_classes_entry(GKeyFile *key_file, void *value)
|
|
{
|
|
g_key_file_set_string(key_file, "General", "Class", value);
|
|
}
|
|
|
|
static void convert_blocked_entry(GKeyFile *key_file, void *value)
|
|
{
|
|
g_key_file_set_boolean(key_file, "General", "Blocked", TRUE);
|
|
}
|
|
|
|
static void convert_did_entry(GKeyFile *key_file, void *value)
|
|
{
|
|
char *vendor_str, *product_str, *version_str;
|
|
uint16_t val;
|
|
|
|
vendor_str = strchr(value, ' ');
|
|
if (!vendor_str)
|
|
return;
|
|
|
|
*(vendor_str++) = 0;
|
|
|
|
if (g_str_equal(value, "FFFF"))
|
|
return;
|
|
|
|
product_str = strchr(vendor_str, ' ');
|
|
if (!product_str)
|
|
return;
|
|
|
|
*(product_str++) = 0;
|
|
|
|
version_str = strchr(product_str, ' ');
|
|
if (!version_str)
|
|
return;
|
|
|
|
*(version_str++) = 0;
|
|
|
|
val = (uint16_t) strtol(value, NULL, 16);
|
|
g_key_file_set_integer(key_file, "DeviceID", "Source", val);
|
|
|
|
val = (uint16_t) strtol(vendor_str, NULL, 16);
|
|
g_key_file_set_integer(key_file, "DeviceID", "Vendor", val);
|
|
|
|
val = (uint16_t) strtol(product_str, NULL, 16);
|
|
g_key_file_set_integer(key_file, "DeviceID", "Product", val);
|
|
|
|
val = (uint16_t) strtol(version_str, NULL, 16);
|
|
g_key_file_set_integer(key_file, "DeviceID", "Version", val);
|
|
}
|
|
|
|
static void convert_linkkey_entry(GKeyFile *key_file, void *value)
|
|
{
|
|
char *type_str, *length_str, *str;
|
|
gint val;
|
|
|
|
type_str = strchr(value, ' ');
|
|
if (!type_str)
|
|
return;
|
|
|
|
*(type_str++) = 0;
|
|
|
|
length_str = strchr(type_str, ' ');
|
|
if (!length_str)
|
|
return;
|
|
|
|
*(length_str++) = 0;
|
|
|
|
str = g_strconcat("0x", value, NULL);
|
|
g_key_file_set_string(key_file, "LinkKey", "Key", str);
|
|
g_free(str);
|
|
|
|
val = strtol(type_str, NULL, 16);
|
|
g_key_file_set_integer(key_file, "LinkKey", "Type", val);
|
|
|
|
val = strtol(length_str, NULL, 16);
|
|
g_key_file_set_integer(key_file, "LinkKey", "PINLength", val);
|
|
}
|
|
|
|
static void convert_ltk_entry(GKeyFile *key_file, void *value)
|
|
{
|
|
char *auth_str, *rand_str, *str;
|
|
int i, ret;
|
|
unsigned char auth, master, enc_size;
|
|
unsigned short ediv;
|
|
|
|
auth_str = strchr(value, ' ');
|
|
if (!auth_str)
|
|
return;
|
|
|
|
*(auth_str++) = 0;
|
|
|
|
for (i = 0, rand_str = auth_str; i < 4; i++) {
|
|
rand_str = strchr(rand_str, ' ');
|
|
if (!rand_str || rand_str[1] == '\0')
|
|
return;
|
|
|
|
rand_str++;
|
|
}
|
|
|
|
ret = sscanf(auth_str, " %hhd %hhd %hhd %hd", &auth, &master,
|
|
&enc_size, &ediv);
|
|
if (ret < 4)
|
|
return;
|
|
|
|
str = g_strconcat("0x", value, NULL);
|
|
g_key_file_set_string(key_file, "LongTermKey", "Key", str);
|
|
g_free(str);
|
|
|
|
g_key_file_set_integer(key_file, "LongTermKey", "Authenticated", auth);
|
|
g_key_file_set_integer(key_file, "LongTermKey", "Master", master);
|
|
g_key_file_set_integer(key_file, "LongTermKey", "EncSize", enc_size);
|
|
g_key_file_set_integer(key_file, "LongTermKey", "EDiv", ediv);
|
|
|
|
str = g_strconcat("0x", rand_str, NULL);
|
|
g_key_file_set_string(key_file, "LongTermKey", "Rand", str);
|
|
g_free(str);
|
|
}
|
|
|
|
static void convert_entry(char *key, char *value, void *user_data)
|
|
{
|
|
struct device_converter *converter = user_data;
|
|
char device_type = -1;
|
|
char filename[PATH_MAX + 1];
|
|
GKeyFile *key_file;
|
|
char *data;
|
|
gsize length = 0;
|
|
|
|
if (key[17] == '#') {
|
|
key[17] = '\0';
|
|
device_type = key[18] - '0';
|
|
}
|
|
|
|
if (bachk(key) != 0)
|
|
return;
|
|
|
|
if (converter->force == FALSE) {
|
|
struct stat st;
|
|
int err;
|
|
|
|
snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s",
|
|
converter->address, key);
|
|
filename[PATH_MAX] = '\0';
|
|
|
|
err = stat(filename, &st);
|
|
if (err || !S_ISDIR(st.st_mode))
|
|
return;
|
|
}
|
|
|
|
snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info",
|
|
converter->address, key);
|
|
filename[PATH_MAX] = '\0';
|
|
|
|
key_file = g_key_file_new();
|
|
g_key_file_load_from_file(key_file, filename, 0, NULL);
|
|
|
|
if (device_type >= 0)
|
|
set_device_type(key_file, device_type);
|
|
|
|
converter->cb(key_file, value);
|
|
|
|
data = g_key_file_to_data(key_file, &length, NULL);
|
|
if (length > 0) {
|
|
create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
g_file_set_contents(filename, data, length, NULL);
|
|
}
|
|
|
|
g_free(data);
|
|
|
|
g_key_file_free(key_file);
|
|
}
|
|
|
|
static void convert_file(char *file, char *address,
|
|
void (*cb)(GKeyFile *key_file, void *value),
|
|
gboolean force)
|
|
{
|
|
char filename[PATH_MAX + 1];
|
|
struct device_converter converter;
|
|
char *str;
|
|
|
|
snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s", address, file);
|
|
filename[PATH_MAX] = '\0';
|
|
|
|
str = textfile_get(filename, "converted");
|
|
if (str && strcmp(str, "yes") == 0) {
|
|
DBG("Legacy file %s already converted", filename);
|
|
} else {
|
|
converter.address = address;
|
|
converter.cb = cb;
|
|
converter.force = force;
|
|
|
|
textfile_foreach(filename, convert_entry, &converter);
|
|
textfile_put(filename, "converted", "yes");
|
|
}
|
|
free(str);
|
|
}
|
|
|
|
static void convert_device_storage(struct btd_adapter *adapter)
|
|
{
|
|
char filename[PATH_MAX + 1];
|
|
char address[18];
|
|
char *str;
|
|
|
|
ba2str(&adapter->bdaddr, address);
|
|
|
|
/* Convert device's name cache */
|
|
snprintf(filename, PATH_MAX, STORAGEDIR "/%s/names", address);
|
|
filename[PATH_MAX] = '\0';
|
|
|
|
str = textfile_get(filename, "converted");
|
|
if (str && strcmp(str, "yes") == 0) {
|
|
DBG("Legacy names file already converted");
|
|
} else {
|
|
textfile_foreach(filename, convert_names_entry, address);
|
|
textfile_put(filename, "converted", "yes");
|
|
}
|
|
free(str);
|
|
|
|
/* Convert aliases */
|
|
convert_file("aliases", address, convert_aliases_entry, TRUE);
|
|
|
|
/* Convert trusts */
|
|
convert_file("trusts", address, convert_trusts_entry, TRUE);
|
|
|
|
/* Convert blocked */
|
|
convert_file("blocked", address, convert_blocked_entry, TRUE);
|
|
|
|
/* Convert linkkeys */
|
|
convert_file("linkkeys", address, convert_linkkey_entry, TRUE);
|
|
|
|
/* Convert longtermkeys */
|
|
convert_file("longtermkeys", address, convert_ltk_entry, TRUE);
|
|
|
|
/* Convert classes */
|
|
convert_file("classes", address, convert_classes_entry, FALSE);
|
|
|
|
/* Convert device ids */
|
|
convert_file("did", address, convert_did_entry, FALSE);
|
|
}
|
|
|
|
static void convert_config(struct btd_adapter *adapter, const char *filename,
|
|
GKeyFile *key_file)
|
|
{
|
|
char address[18];
|
|
char str[MAX_NAME_LENGTH + 1];
|
|
char config_path[PATH_MAX + 1];
|
|
char *converted;
|
|
gboolean flag;
|
|
int timeout;
|
|
uint8_t mode;
|
|
char *data;
|
|
gsize length = 0;
|
|
|
|
ba2str(&adapter->bdaddr, address);
|
|
snprintf(config_path, PATH_MAX, STORAGEDIR "/%s/config", address);
|
|
config_path[PATH_MAX] = '\0';
|
|
|
|
converted = textfile_get(config_path, "converted");
|
|
if (converted) {
|
|
if (strcmp(converted, "yes") == 0) {
|
|
DBG("Legacy config file already converted");
|
|
free(converted);
|
|
return;
|
|
}
|
|
|
|
free(converted);
|
|
}
|
|
|
|
if (read_local_name(&adapter->bdaddr, str) == 0)
|
|
g_key_file_set_string(key_file, "General", "Name", str);
|
|
|
|
if (read_device_pairable(&adapter->bdaddr, &flag) == 0)
|
|
g_key_file_set_boolean(key_file, "General", "Pairable", flag);
|
|
|
|
if (read_pairable_timeout(address, &timeout) == 0)
|
|
g_key_file_set_integer(key_file, "General",
|
|
"PairableTimeout", timeout);
|
|
|
|
if (read_discoverable_timeout(address, &timeout) == 0)
|
|
g_key_file_set_integer(key_file, "General",
|
|
"DiscoverableTimeout", timeout);
|
|
|
|
if (read_device_mode(address, str, sizeof(str)) == 0) {
|
|
mode = get_mode(str);
|
|
g_key_file_set_boolean(key_file, "General", "Powered",
|
|
mode > MODE_OFF);
|
|
}
|
|
|
|
if (read_on_mode(address, str, sizeof(str)) == 0) {
|
|
mode = get_mode(str);
|
|
g_key_file_set_boolean(key_file, "General", "Discoverable",
|
|
mode == MODE_DISCOVERABLE);
|
|
}
|
|
|
|
create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
|
|
data = g_key_file_to_data(key_file, &length, NULL);
|
|
g_file_set_contents(filename, data, length, NULL);
|
|
g_free(data);
|
|
|
|
textfile_put(config_path, "converted", "yes");
|
|
}
|
|
|
|
static void load_config(struct btd_adapter *adapter)
|
|
{
|
|
GKeyFile *key_file;
|
|
char filename[PATH_MAX + 1];
|
|
char address[18];
|
|
gboolean powered;
|
|
GError *gerr = NULL;
|
|
|
|
ba2str(&adapter->bdaddr, address);
|
|
|
|
key_file = g_key_file_new();
|
|
|
|
snprintf(filename, PATH_MAX, STORAGEDIR "/%s/settings", address);
|
|
filename[PATH_MAX] = '\0';
|
|
|
|
if (!g_key_file_load_from_file(key_file, filename, 0, NULL))
|
|
convert_config(adapter, filename, key_file);
|
|
|
|
/* Get name */
|
|
adapter->name = g_key_file_get_string(key_file, "General",
|
|
"Name", NULL);
|
|
|
|
/* Set class */
|
|
adapter->dev_class = main_opts.class;
|
|
|
|
/* Get pairable mode */
|
|
adapter->pairable = g_key_file_get_boolean(key_file, "General",
|
|
"Pairable", &gerr);
|
|
if (gerr) {
|
|
adapter->pairable = TRUE;
|
|
g_error_free(gerr);
|
|
gerr = NULL;
|
|
}
|
|
|
|
/* Get pairable timeout */
|
|
adapter->pairable_timeout = g_key_file_get_integer(key_file, "General",
|
|
"PairableTimeout", &gerr);
|
|
if (gerr) {
|
|
adapter->pairable_timeout = main_opts.pairto;
|
|
g_error_free(gerr);
|
|
gerr = NULL;
|
|
}
|
|
|
|
/* Get discoverable mode */
|
|
adapter->discoverable = g_key_file_get_boolean(key_file, "General",
|
|
"Discoverable", &gerr);
|
|
if (gerr) {
|
|
adapter->discoverable = (main_opts.mode == MODE_DISCOVERABLE);
|
|
g_error_free(gerr);
|
|
gerr = NULL;
|
|
}
|
|
|
|
/* Get discoverable timeout */
|
|
adapter->discov_timeout = g_key_file_get_integer(key_file, "General",
|
|
"DiscoverableTimeout", &gerr);
|
|
if (gerr) {
|
|
adapter->discov_timeout = main_opts.discovto;
|
|
g_error_free(gerr);
|
|
gerr = NULL;
|
|
}
|
|
|
|
/* Get powered mode */
|
|
powered = g_key_file_get_boolean(key_file, "General", "Powered",
|
|
&gerr);
|
|
if (gerr) {
|
|
adapter->mode = main_opts.mode;
|
|
g_error_free(gerr);
|
|
gerr = NULL;
|
|
} else if (powered) {
|
|
adapter->mode = adapter->discoverable ? MODE_DISCOVERABLE :
|
|
MODE_CONNECTABLE;
|
|
} else
|
|
adapter->mode = MODE_OFF;
|
|
|
|
mgmt_set_connectable(adapter->dev_id, TRUE);
|
|
mgmt_set_discoverable(adapter->dev_id, adapter->discoverable,
|
|
adapter->discov_timeout);
|
|
|
|
g_key_file_free(key_file);
|
|
}
|
|
|
|
gboolean adapter_init(struct btd_adapter *adapter, gboolean up)
|
|
{
|
|
adapter->up = up;
|
|
adapter->already_up = up;
|
|
|
|
adapter->allow_name_changes = TRUE;
|
|
|
|
mgmt_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;
|
|
}
|
|
|
|
sdp_init_services_list(&adapter->bdaddr);
|
|
|
|
btd_adapter_gatt_server_start(adapter);
|
|
|
|
load_config(adapter);
|
|
convert_device_storage(adapter);
|
|
load_drivers(adapter);
|
|
btd_profile_foreach(probe_profile, adapter);
|
|
clear_blocked(adapter);
|
|
load_devices(adapter);
|
|
|
|
/* retrieve the active connections: address the scenario where
|
|
* the are active connections before the daemon've started */
|
|
load_connections(adapter);
|
|
|
|
adapter->initialized = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
struct btd_adapter *adapter_create(int id)
|
|
{
|
|
char path[MAX_PATH_LENGTH];
|
|
struct btd_adapter *adapter;
|
|
const char *base_path = manager_get_base_path();
|
|
|
|
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;
|
|
adapter->auths = g_queue_new();
|
|
|
|
snprintf(path, sizeof(path), "%s/hci%d", base_path, id);
|
|
adapter->path = g_strdup(path);
|
|
|
|
if (!g_dbus_register_interface(btd_get_dbus_connection(),
|
|
path, ADAPTER_INTERFACE,
|
|
adapter_methods, adapter_signals,
|
|
adapter_properties, 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);
|
|
|
|
if (adapter->remove_temp > 0) {
|
|
g_source_remove(adapter->remove_temp);
|
|
adapter->remove_temp = 0;
|
|
}
|
|
|
|
discovery_cleanup(adapter);
|
|
|
|
for (l = adapter->devices; l; l = l->next)
|
|
device_remove(l->data, FALSE);
|
|
g_slist_free(adapter->devices);
|
|
|
|
unload_drivers(adapter);
|
|
btd_adapter_gatt_server_stop(adapter);
|
|
|
|
g_slist_free(adapter->pin_callbacks);
|
|
|
|
/* Return adapter to down state if it was not up on init */
|
|
if (!adapter->already_up && adapter->up)
|
|
mgmt_set_powered(adapter->dev_id, FALSE);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
const bdaddr_t *adapter_get_address(struct btd_adapter *adapter)
|
|
{
|
|
return &adapter->bdaddr;
|
|
}
|
|
|
|
void adapter_set_allow_name_changes(struct btd_adapter *adapter,
|
|
gboolean allow_name_changes)
|
|
{
|
|
adapter->allow_name_changes = allow_name_changes;
|
|
}
|
|
|
|
static gboolean send_found(gpointer user_data)
|
|
{
|
|
struct btd_adapter *adapter = user_data;
|
|
struct discovery *discovery = adapter->discovery;
|
|
|
|
if (!discovery)
|
|
return FALSE;
|
|
|
|
discovery->id = 0;
|
|
|
|
if (!discovery->pending)
|
|
return FALSE;
|
|
|
|
send_devices_found(adapter);
|
|
|
|
discovery->id = g_timeout_add_seconds(1, send_found, adapter);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean adapter_remove_temp(gpointer data)
|
|
{
|
|
struct btd_adapter *adapter = data;
|
|
GSList *l, *next;
|
|
|
|
DBG("%s", adapter->path);
|
|
|
|
adapter->remove_temp = 0;
|
|
|
|
for (l = adapter->devices; l != NULL; l = next) {
|
|
struct btd_device *dev = l->data;
|
|
|
|
next = g_slist_next(l);
|
|
|
|
if (device_is_temporary(dev))
|
|
adapter_remove_device(adapter, dev, TRUE);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void adapter_set_discovering(struct btd_adapter *adapter,
|
|
gboolean discovering)
|
|
{
|
|
struct discovery *discovery;
|
|
guint connect_list_len;
|
|
|
|
if (discovering && !adapter->discovery)
|
|
adapter->discovery = g_new0(struct discovery, 1);
|
|
|
|
discovery = adapter->discovery;
|
|
|
|
g_dbus_emit_property_changed(btd_get_dbus_connection(), adapter->path,
|
|
ADAPTER_INTERFACE, "Discovering");
|
|
|
|
if (discovering) {
|
|
if (adapter->remove_temp > 0) {
|
|
g_source_remove(adapter->remove_temp);
|
|
adapter->remove_temp = 0;
|
|
}
|
|
discovery->id = g_timeout_add_seconds(1, send_found, adapter);
|
|
return;
|
|
}
|
|
|
|
discovery_cleanup(adapter);
|
|
|
|
adapter->remove_temp = g_timeout_add_seconds(REMOVE_TEMP_TIMEOUT,
|
|
adapter_remove_temp,
|
|
adapter);
|
|
|
|
if (adapter->discov_suspended)
|
|
return;
|
|
|
|
connect_list_len = g_slist_length(adapter->connect_list);
|
|
|
|
if (connect_list_len == 0 && adapter->scanning_session) {
|
|
session_unref(adapter->scanning_session);
|
|
adapter->scanning_session = NULL;
|
|
}
|
|
|
|
if (adapter_has_discov_sessions(adapter)) {
|
|
adapter->discov_id = g_idle_add(discovery_cb, adapter);
|
|
|
|
DBG("hci%u restarting discovery: disc_sessions %u",
|
|
adapter->dev_id,
|
|
g_slist_length(adapter->disc_sessions));
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void suspend_discovery(struct btd_adapter *adapter)
|
|
{
|
|
if (adapter->disc_sessions == NULL || adapter->discov_suspended)
|
|
return;
|
|
|
|
DBG("Suspending discovery");
|
|
|
|
adapter->discov_suspended = TRUE;
|
|
|
|
if (adapter->discov_id > 0) {
|
|
g_source_remove(adapter->discov_id);
|
|
adapter->discov_id = 0;
|
|
} else
|
|
mgmt_stop_discovery(adapter->dev_id);
|
|
}
|
|
|
|
static gboolean clean_connecting_state(GIOChannel *io, GIOCondition cond,
|
|
gpointer user_data)
|
|
{
|
|
struct btd_device *device = user_data;
|
|
struct btd_adapter *adapter = device_get_adapter(device);
|
|
|
|
adapter->connecting = FALSE;
|
|
|
|
if (adapter->waiting_to_connect == 0 &&
|
|
g_slist_length(adapter->connect_list) > 0)
|
|
adapter->discov_id = g_idle_add(discovery_cb, adapter);
|
|
|
|
btd_device_unref(device);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean connect_pending_cb(gpointer user_data)
|
|
{
|
|
struct btd_device *device = user_data;
|
|
struct btd_adapter *adapter = device_get_adapter(device);
|
|
GIOChannel *io;
|
|
|
|
/* in the future we may want to check here if the controller supports
|
|
* scanning and connecting at the same time */
|
|
if (adapter->discovery)
|
|
return TRUE;
|
|
|
|
if (adapter->connecting)
|
|
return TRUE;
|
|
|
|
adapter->connecting = TRUE;
|
|
adapter->waiting_to_connect--;
|
|
|
|
io = device_att_connect(device);
|
|
if (io != NULL) {
|
|
g_io_add_watch(io, G_IO_OUT | G_IO_ERR, clean_connecting_state,
|
|
btd_device_ref(device));
|
|
g_io_channel_unref(io);
|
|
}
|
|
|
|
btd_device_unref(device);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void adapter_update_found_devices(struct btd_adapter *adapter,
|
|
const bdaddr_t *bdaddr,
|
|
uint8_t bdaddr_type, int8_t rssi,
|
|
bool confirm_name, bool legacy,
|
|
uint8_t *data, uint8_t data_len)
|
|
{
|
|
struct discovery *discovery = adapter->discovery;
|
|
struct btd_device *dev;
|
|
struct eir_data eir_data;
|
|
char addr[18];
|
|
int err;
|
|
GSList *l;
|
|
|
|
if (!discovery) {
|
|
error("Device found event while no discovery in progress");
|
|
return;
|
|
}
|
|
|
|
memset(&eir_data, 0, sizeof(eir_data));
|
|
err = eir_parse(&eir_data, data, data_len);
|
|
if (err < 0) {
|
|
error("Error parsing EIR data: %s (%d)", strerror(-err), -err);
|
|
return;
|
|
}
|
|
|
|
if (eir_data.appearance != 0)
|
|
write_remote_appearance(&adapter->bdaddr, bdaddr, bdaddr_type,
|
|
eir_data.appearance);
|
|
|
|
if (eir_data.name != NULL && eir_data.name_complete)
|
|
adapter_store_cached_name(&adapter->bdaddr, bdaddr,
|
|
eir_data.name);
|
|
|
|
/* Avoid creating LE device if it's not discoverable */
|
|
if (bdaddr_type != BDADDR_BREDR &&
|
|
!(eir_data.flags & (EIR_LIM_DISC | EIR_GEN_DISC))) {
|
|
eir_data_free(&eir_data);
|
|
return;
|
|
}
|
|
|
|
ba2str(bdaddr, addr);
|
|
|
|
l = g_slist_find_custom(adapter->devices, bdaddr,
|
|
(GCompareFunc) device_bdaddr_cmp);
|
|
if (l)
|
|
dev = l->data;
|
|
else
|
|
dev = adapter_create_device(adapter, addr, bdaddr_type);
|
|
|
|
device_set_legacy(dev, legacy);
|
|
device_set_rssi(dev, rssi);
|
|
|
|
if (eir_data.name)
|
|
device_set_name(dev, eir_data.name);
|
|
|
|
if (eir_data.class != 0)
|
|
device_set_class(dev, eir_data.class);
|
|
|
|
device_add_eir_uuids(dev, eir_data.services);
|
|
|
|
eir_data_free(&eir_data);
|
|
|
|
if (!g_slist_find(discovery->pending, dev)) {
|
|
guint pending_count;
|
|
|
|
discovery->pending = g_slist_prepend(discovery->pending, dev);
|
|
|
|
pending_count = g_slist_length(discovery->pending);
|
|
|
|
if (discovery->id == 0) {
|
|
discovery->id = g_idle_add(send_found, adapter);
|
|
} else if (pending_count > PENDING_FOUND_MAX) {
|
|
g_source_remove(discovery->id);
|
|
discovery->id = g_idle_add(send_found, adapter);
|
|
}
|
|
}
|
|
|
|
if (g_slist_find(discovery->found, dev))
|
|
return;
|
|
|
|
if (confirm_name)
|
|
mgmt_confirm_name(adapter->dev_id, bdaddr, bdaddr_type,
|
|
device_name_known(dev));
|
|
|
|
discovery->found = g_slist_prepend(discovery->found, dev);
|
|
|
|
if (device_is_le(dev) && g_slist_find(adapter->connect_list, dev)) {
|
|
adapter_connect_list_remove(adapter, dev);
|
|
g_idle_add(connect_pending_cb, btd_device_ref(dev));
|
|
stop_discovery(adapter);
|
|
adapter->waiting_to_connect++;
|
|
}
|
|
}
|
|
|
|
void adapter_mode_changed(struct btd_adapter *adapter, uint8_t scan_mode)
|
|
{
|
|
bool emit_pairable = false;
|
|
|
|
DBG("old 0x%02x new 0x%02x", adapter->scan_mode, scan_mode);
|
|
|
|
if (adapter->scan_mode == scan_mode)
|
|
return;
|
|
|
|
switch (scan_mode) {
|
|
case SCAN_DISABLED:
|
|
adapter->mode = MODE_OFF;
|
|
break;
|
|
case SCAN_PAGE:
|
|
adapter->mode = MODE_CONNECTABLE;
|
|
adapter->discoverable = FALSE;
|
|
break;
|
|
case (SCAN_PAGE | SCAN_INQUIRY):
|
|
adapter->mode = MODE_DISCOVERABLE;
|
|
adapter->discoverable = TRUE;
|
|
break;
|
|
default:
|
|
/* ignore, reserved */
|
|
return;
|
|
}
|
|
|
|
/* If page scanning gets toggled emit the Pairable property */
|
|
if ((adapter->scan_mode & SCAN_PAGE) != (scan_mode & SCAN_PAGE))
|
|
emit_pairable = true;
|
|
|
|
adapter->scan_mode = scan_mode;
|
|
|
|
if (emit_pairable)
|
|
g_dbus_emit_property_changed(btd_get_dbus_connection(),
|
|
adapter->path, ADAPTER_INTERFACE, "Pairable");
|
|
|
|
g_dbus_emit_property_changed(btd_get_dbus_connection(), adapter->path,
|
|
ADAPTER_INTERFACE, "Discoverable");
|
|
|
|
set_mode_complete(adapter);
|
|
}
|
|
|
|
struct agent *adapter_get_agent(struct btd_adapter *adapter)
|
|
{
|
|
if (!adapter)
|
|
return NULL;
|
|
|
|
return adapter->agent;
|
|
}
|
|
|
|
void adapter_add_connection(struct btd_adapter *adapter,
|
|
struct btd_device *device)
|
|
{
|
|
if (g_slist_find(adapter->connections, device)) {
|
|
error("Device is already marked as connected");
|
|
return;
|
|
}
|
|
|
|
device_add_connection(device);
|
|
|
|
adapter->connections = g_slist_append(adapter->connections, device);
|
|
}
|
|
|
|
void adapter_remove_connection(struct btd_adapter *adapter,
|
|
struct btd_device *device)
|
|
{
|
|
DBG("");
|
|
|
|
if (!g_slist_find(adapter->connections, device)) {
|
|
error("No matching connection for device");
|
|
return;
|
|
}
|
|
|
|
device_remove_connection(device);
|
|
|
|
adapter->connections = g_slist_remove(adapter->connections, device);
|
|
|
|
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(adapter, device, TRUE);
|
|
}
|
|
}
|
|
|
|
gboolean adapter_has_discov_sessions(struct btd_adapter *adapter)
|
|
{
|
|
if (!adapter || !adapter->disc_sessions)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
int btd_register_adapter_driver(struct btd_adapter_driver *driver)
|
|
{
|
|
adapter_drivers = g_slist_append(adapter_drivers, driver);
|
|
|
|
if (driver->probe == NULL)
|
|
return 0;
|
|
|
|
manager_foreach_adapter(probe_driver, driver);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void unload_driver(struct btd_adapter *adapter, gpointer data)
|
|
{
|
|
struct btd_adapter_driver *driver = data;
|
|
|
|
if (driver->remove)
|
|
driver->remove(adapter);
|
|
|
|
adapter->drivers = g_slist_remove(adapter->drivers, data);
|
|
}
|
|
|
|
void btd_unregister_adapter_driver(struct btd_adapter_driver *driver)
|
|
{
|
|
adapter_drivers = g_slist_remove(adapter_drivers, driver);
|
|
|
|
manager_foreach_adapter(unload_driver, driver);
|
|
}
|
|
|
|
static void agent_auth_cb(struct agent *agent, DBusError *derr,
|
|
void *user_data)
|
|
{
|
|
struct btd_adapter *adapter = user_data;
|
|
struct service_auth *auth = adapter->auths->head->data;
|
|
|
|
g_queue_pop_head(adapter->auths);
|
|
|
|
auth->cb(derr, auth->user_data);
|
|
|
|
g_free(auth);
|
|
|
|
adapter->auth_idle_id = g_idle_add(process_auth_queue, adapter);
|
|
}
|
|
|
|
static gboolean process_auth_queue(gpointer user_data)
|
|
{
|
|
struct btd_adapter *adapter = user_data;
|
|
DBusError err;
|
|
|
|
adapter->auth_idle_id = 0;
|
|
|
|
dbus_error_init(&err);
|
|
dbus_set_error_const(&err, "org.bluez.Error.Rejected", NULL);
|
|
|
|
while (!g_queue_is_empty(adapter->auths)) {
|
|
struct service_auth *auth = adapter->auths->head->data;
|
|
struct btd_device *device = auth->device;
|
|
const gchar *dev_path;
|
|
|
|
if (device_is_trusted(device) == TRUE) {
|
|
auth->cb(NULL, auth->user_data);
|
|
goto next;
|
|
}
|
|
|
|
auth->agent = device_get_agent(device);
|
|
if (auth->agent == NULL) {
|
|
warn("Can't find device agent");
|
|
auth->cb(&err, auth->user_data);
|
|
goto next;
|
|
}
|
|
|
|
dev_path = device_get_path(device);
|
|
|
|
if (agent_authorize_service(auth->agent, dev_path, auth->uuid,
|
|
agent_auth_cb, adapter, NULL) < 0) {
|
|
auth->cb(&err, auth->user_data);
|
|
goto next;
|
|
}
|
|
|
|
break;
|
|
|
|
next:
|
|
g_free(auth);
|
|
g_queue_pop_head(adapter->auths);
|
|
}
|
|
|
|
dbus_error_free(&err);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static int 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;
|
|
char address[18];
|
|
static guint id = 0;
|
|
|
|
ba2str(dst, address);
|
|
device = adapter_find_device(adapter, address);
|
|
if (!device)
|
|
return 0;
|
|
|
|
/* Device connected? */
|
|
if (!g_slist_find(adapter->connections, device))
|
|
error("Authorization request for non-connected device!?");
|
|
|
|
auth = g_try_new0(struct service_auth, 1);
|
|
if (!auth)
|
|
return 0;
|
|
|
|
auth->cb = cb;
|
|
auth->user_data = user_data;
|
|
auth->uuid = uuid;
|
|
auth->device = device;
|
|
auth->adapter = adapter;
|
|
auth->id = ++id;
|
|
|
|
g_queue_push_tail(adapter->auths, auth);
|
|
|
|
if (adapter->auths->length != 1)
|
|
return auth->id;
|
|
|
|
if (adapter->auth_idle_id != 0)
|
|
return auth->id;
|
|
|
|
adapter->auth_idle_id = g_idle_add(process_auth_queue, adapter);
|
|
|
|
return auth->id;
|
|
}
|
|
|
|
guint btd_request_authorization(const bdaddr_t *src, const bdaddr_t *dst,
|
|
const char *uuid, service_auth_cb cb,
|
|
void *user_data)
|
|
{
|
|
struct btd_adapter *adapter;
|
|
GSList *l;
|
|
|
|
if (bacmp(src, BDADDR_ANY) != 0) {
|
|
adapter = manager_find_adapter(src);
|
|
if (!adapter)
|
|
return 0;
|
|
|
|
return adapter_authorize(adapter, dst, uuid, cb, user_data);
|
|
}
|
|
|
|
for (l = manager_get_adapters(); l != NULL; l = g_slist_next(l)) {
|
|
guint id;
|
|
|
|
adapter = l->data;
|
|
|
|
id = adapter_authorize(adapter, dst, uuid, cb, user_data);
|
|
if (id != 0)
|
|
return id;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct service_auth *find_authorization(guint id)
|
|
{
|
|
GSList *l;
|
|
GList *l2;
|
|
|
|
for (l = manager_get_adapters(); l != NULL; l = g_slist_next(l)) {
|
|
struct btd_adapter *adapter = l->data;
|
|
|
|
for (l2 = adapter->auths->head; l2 != NULL; l2 = l2->next) {
|
|
struct service_auth *auth = l2->data;
|
|
|
|
if (auth->id == id)
|
|
return auth;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int btd_cancel_authorization(guint id)
|
|
{
|
|
struct service_auth *auth;
|
|
|
|
auth = find_authorization(id);
|
|
if (auth == NULL)
|
|
return -EPERM;
|
|
|
|
g_queue_remove(auth->adapter->auths, auth);
|
|
|
|
if (auth->agent != NULL)
|
|
agent_cancel(auth->agent);
|
|
|
|
g_free(auth);
|
|
|
|
return 0;
|
|
}
|
|
|
|
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());
|
|
|
|
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_powering_down(struct btd_adapter *adapter)
|
|
{
|
|
return adapter->off_requested;
|
|
}
|
|
|
|
int btd_adapter_restore_powered(struct btd_adapter *adapter)
|
|
{
|
|
if (!main_opts.remember_powered)
|
|
return -EINVAL;
|
|
|
|
if (adapter->up)
|
|
return 0;
|
|
|
|
if (adapter->mode == MODE_OFF)
|
|
return 0;
|
|
|
|
return mgmt_set_powered(adapter->dev_id, TRUE);
|
|
}
|
|
|
|
static gboolean disable_auto(gpointer user_data)
|
|
{
|
|
struct btd_adapter *adapter = user_data;
|
|
GSList *l;
|
|
|
|
for (l = adapter->devices; l; l = l->next) {
|
|
struct btd_device *device = l->data;
|
|
|
|
device_set_auto_connect(device, FALSE);
|
|
}
|
|
|
|
adapter->auto_timeout_id = 0;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void btd_adapter_enable_auto_connect(struct btd_adapter *adapter)
|
|
{
|
|
gboolean enable = TRUE;
|
|
|
|
if (!adapter->up)
|
|
return;
|
|
|
|
DBG("Enabling automatic connections");
|
|
|
|
if (adapter->auto_timeout_id)
|
|
return;
|
|
|
|
g_slist_foreach(adapter->devices, set_auto_connect, &enable);
|
|
|
|
adapter->auto_timeout_id = g_timeout_add_seconds(main_opts.autoto,
|
|
disable_auto, adapter);
|
|
}
|
|
|
|
void btd_adapter_register_pin_cb(struct btd_adapter *adapter,
|
|
btd_adapter_pin_cb_t cb)
|
|
{
|
|
adapter->pin_callbacks = g_slist_prepend(adapter->pin_callbacks, cb);
|
|
}
|
|
|
|
void btd_adapter_unregister_pin_cb(struct btd_adapter *adapter,
|
|
btd_adapter_pin_cb_t cb)
|
|
{
|
|
adapter->pin_callbacks = g_slist_remove(adapter->pin_callbacks, cb);
|
|
}
|
|
|
|
ssize_t btd_adapter_get_pin(struct btd_adapter *adapter, struct btd_device *dev,
|
|
char *pin_buf, gboolean *display)
|
|
{
|
|
GSList *l;
|
|
btd_adapter_pin_cb_t cb;
|
|
ssize_t ret;
|
|
|
|
for (l = adapter->pin_callbacks; l != NULL; l = g_slist_next(l)) {
|
|
cb = l->data;
|
|
ret = cb(adapter, dev, pin_buf, display);
|
|
if (ret > 0)
|
|
return ret;
|
|
}
|
|
|
|
return read_pin_code(&adapter->bdaddr, device_get_address(dev),
|
|
pin_buf);
|
|
}
|
|
|
|
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->up)
|
|
return -EINVAL;
|
|
|
|
return mgmt_set_fast_connectable(adapter->dev_id, enable);
|
|
}
|
|
|
|
int btd_adapter_read_clock(struct btd_adapter *adapter, const bdaddr_t *bdaddr,
|
|
int which, int timeout, uint32_t *clock,
|
|
uint16_t *accuracy)
|
|
{
|
|
if (!adapter->up)
|
|
return -EINVAL;
|
|
|
|
return mgmt_read_clock(adapter->dev_id, bdaddr, which,
|
|
timeout, clock, accuracy);
|
|
}
|
|
|
|
int btd_adapter_disconnect_device(struct btd_adapter *adapter,
|
|
const bdaddr_t *bdaddr,
|
|
uint8_t bdaddr_type)
|
|
|
|
{
|
|
return mgmt_disconnect(adapter->dev_id, bdaddr, bdaddr_type);
|
|
}
|
|
|
|
int btd_adapter_remove_bonding(struct btd_adapter *adapter,
|
|
const bdaddr_t *bdaddr, uint8_t bdaddr_type)
|
|
{
|
|
return mgmt_unpair_device(adapter->dev_id, bdaddr, bdaddr_type);
|
|
}
|
|
|
|
int btd_adapter_pincode_reply(struct btd_adapter *adapter,
|
|
const bdaddr_t *bdaddr,
|
|
const char *pin, size_t pin_len)
|
|
{
|
|
return mgmt_pincode_reply(adapter->dev_id, bdaddr, pin, pin_len);
|
|
}
|
|
|
|
int btd_adapter_confirm_reply(struct btd_adapter *adapter,
|
|
const bdaddr_t *bdaddr, uint8_t bdaddr_type,
|
|
gboolean success)
|
|
{
|
|
return mgmt_confirm_reply(adapter->dev_id, bdaddr, bdaddr_type,
|
|
success);
|
|
}
|
|
|
|
int btd_adapter_passkey_reply(struct btd_adapter *adapter,
|
|
const bdaddr_t *bdaddr, uint8_t bdaddr_type,
|
|
uint32_t passkey)
|
|
{
|
|
return mgmt_passkey_reply(adapter->dev_id, bdaddr, bdaddr_type,
|
|
passkey);
|
|
}
|
|
|
|
int btd_adapter_set_did(struct btd_adapter *adapter, uint16_t vendor,
|
|
uint16_t product, uint16_t version,
|
|
uint16_t source)
|
|
{
|
|
return mgmt_set_did(adapter->dev_id, vendor, product, version, source);
|
|
}
|
|
|
|
int adapter_create_bonding(struct btd_adapter *adapter, const bdaddr_t *bdaddr,
|
|
uint8_t addr_type, uint8_t io_cap)
|
|
{
|
|
suspend_discovery(adapter);
|
|
return mgmt_create_bonding(adapter->dev_id, bdaddr, addr_type, io_cap);
|
|
}
|
|
|
|
int adapter_cancel_bonding(struct btd_adapter *adapter, const bdaddr_t *bdaddr)
|
|
{
|
|
return mgmt_cancel_bonding(adapter->dev_id, bdaddr);
|
|
}
|
|
|
|
static void check_oob_bonding_complete(struct btd_adapter *adapter,
|
|
const bdaddr_t *bdaddr, uint8_t status)
|
|
{
|
|
if (!adapter->oob_handler || !adapter->oob_handler->bonding_cb)
|
|
return;
|
|
|
|
if (bacmp(bdaddr, &adapter->oob_handler->remote_addr) != 0)
|
|
return;
|
|
|
|
adapter->oob_handler->bonding_cb(adapter, bdaddr, status,
|
|
adapter->oob_handler->user_data);
|
|
|
|
g_free(adapter->oob_handler);
|
|
adapter->oob_handler = NULL;
|
|
}
|
|
|
|
void adapter_bonding_complete(struct btd_adapter *adapter,
|
|
const bdaddr_t *bdaddr, uint8_t status)
|
|
{
|
|
struct btd_device *device;
|
|
char addr[18];
|
|
|
|
ba2str(bdaddr, addr);
|
|
if (status == 0)
|
|
device = adapter_get_device(adapter, addr);
|
|
else
|
|
device = adapter_find_device(adapter, addr);
|
|
|
|
if (device != NULL)
|
|
device_bonding_complete(device, status);
|
|
|
|
if (adapter->discov_suspended) {
|
|
adapter->discov_suspended = FALSE;
|
|
mgmt_start_discovery(adapter->dev_id);
|
|
}
|
|
|
|
check_oob_bonding_complete(adapter, bdaddr, status);
|
|
}
|
|
|
|
int btd_adapter_read_local_oob_data(struct btd_adapter *adapter)
|
|
{
|
|
return mgmt_read_local_oob_data(adapter->dev_id);
|
|
}
|
|
|
|
int btd_adapter_add_remote_oob_data(struct btd_adapter *adapter,
|
|
const bdaddr_t *bdaddr,
|
|
uint8_t *hash, uint8_t *randomizer)
|
|
{
|
|
return mgmt_add_remote_oob_data(adapter->dev_id, bdaddr, hash,
|
|
randomizer);
|
|
}
|
|
|
|
int btd_adapter_remove_remote_oob_data(struct btd_adapter *adapter,
|
|
const bdaddr_t *bdaddr)
|
|
{
|
|
return mgmt_remove_remote_oob_data(adapter->dev_id, bdaddr);
|
|
}
|
|
|
|
int btd_adapter_ssp_enabled(struct btd_adapter *adapter)
|
|
{
|
|
return mgmt_ssp_enabled(adapter->dev_id);
|
|
}
|
|
|
|
void btd_adapter_set_oob_handler(struct btd_adapter *adapter,
|
|
struct oob_handler *handler)
|
|
{
|
|
adapter->oob_handler = handler;
|
|
}
|
|
|
|
gboolean btd_adapter_check_oob_handler(struct btd_adapter *adapter)
|
|
{
|
|
return adapter->oob_handler != NULL;
|
|
}
|
|
|
|
void adapter_read_local_oob_data_complete(struct btd_adapter *adapter,
|
|
uint8_t *hash, uint8_t *randomizer)
|
|
{
|
|
if (!adapter->oob_handler || !adapter->oob_handler->read_local_cb)
|
|
return;
|
|
|
|
adapter->oob_handler->read_local_cb(adapter, hash, randomizer,
|
|
adapter->oob_handler->user_data);
|
|
|
|
g_free(adapter->oob_handler);
|
|
adapter->oob_handler = NULL;
|
|
}
|