C++程序  |  4149行  |  98.5 KB

/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2006-2010  Nokia Corporation
 *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
 *
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>

#ifdef ANDROID_EXPAND_NAME
#include <cutils/properties.h>
#endif

#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 "dbus-common.h"
#include "event.h"
#include "error.h"
#include "glib-helper.h"
#include "agent.h"
#include "storage.h"
#include "attrib-server.h"
#include "att.h"
#include "eir.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_INVALID		0xFF

#define check_address(address) bachk(address)

static DBusConnection *connection = NULL;
static GSList *adapter_drivers = NULL;

static GSList *ops_candidates = NULL;

const struct btd_adapter_ops *adapter_ops = NULL;

struct session_req {
	struct btd_adapter	*adapter;
	DBusConnection		*conn;		/* Connection reference */
	DBusMessage		*msg;		/* Unreplied message ref */
	char			*owner;		/* Bus name of the owner */
	guint			id;		/* Listener id */
	uint8_t			mode;		/* Requested mode */
	int			refcount;	/* Session refcount */
	gboolean		got_reply;	/* Agent reply received */
};

struct service_auth {
	service_auth_cb cb;
	void *user_data;
	struct btd_device *device;
	struct btd_adapter *adapter;
};

struct btd_adapter {
	uint16_t dev_id;
	int up;
	char *path;			/* adapter object path */
	bdaddr_t bdaddr;		/* adapter Bluetooth Address */
	uint32_t dev_class;		/* Class of Device */
	char name[MAX_NAME_LENGTH + 1]; /* adapter name */
	guint discov_timeout_id;	/* discoverable timeout id */
	guint stop_discov_id;		/* stop inquiry/scanning id */
	uint32_t discov_timeout;	/* discoverable time(sec) */
	guint pairable_timeout_id;	/* pairable timeout id */
	uint32_t pairable_timeout;	/* pairable time(sec) */
	uint8_t scan_mode;		/* scan mode: SCAN_DISABLED, SCAN_PAGE,
					 * SCAN_INQUIRY */
	uint8_t mode;			/* off, connectable, discoverable,
					 * limited */
	uint8_t global_mode;		/* last valid global mode */
	struct session_req *pending_mode;
	int state;			/* standard inq, periodic inq, name
					 * resolving, suspended discovery */
	GSList *found_devices;
	GSList *oor_devices;		/* out of range device list */
	struct agent *agent;		/* For the new API */
	guint auth_idle_id;		/* Ongoing authorization */
	GSList *connections;		/* Connected devices */
	GSList *devices;		/* Devices structure pointers */
	GSList *mode_sessions;		/* Request Mode sessions */
	GSList *disc_sessions;		/* Discovery sessions */
	guint scheduler_id;		/* Scheduler handle */
	sdp_list_t *services;		/* Services associated to adapter */

	uint8_t  features[8];

	gboolean pairable;		/* pairable state */
	gboolean initialized;

	gboolean off_requested;		/* DEVDOWN ioctl was called */

	gint ref;

	GSList *powered_callbacks;

	gboolean name_stored;

	GSList *loaded_drivers;
};

static void adapter_set_pairable_timeout(struct btd_adapter *adapter,
					guint interval);
static DBusMessage *set_discoverable(DBusConnection *conn, DBusMessage *msg,
				gboolean discoverable, void *data);

static int found_device_cmp(const struct remote_dev_info *d1,
			const struct remote_dev_info *d2)
{
	int ret;

	if (bacmp(&d2->bdaddr, BDADDR_ANY)) {
		ret = bacmp(&d1->bdaddr, &d2->bdaddr);
		if (ret)
			return ret;
	}

	if (d2->name_status != NAME_ANY) {
		ret = (d1->name_status - d2->name_status);
		if (ret)
			return ret;
	}

	return 0;
}

static void dev_info_free(struct remote_dev_info *dev)
{
	g_free(dev->name);
	g_free(dev->alias);
	g_slist_foreach(dev->services, (GFunc) g_free, NULL);
	g_slist_free(dev->services);
	g_strfreev(dev->uuids);
	g_free(dev);
}

/*
 * Device name expansion
 *   %d - device id
 */
static char *expand_name(char *dst, int size, char *str, int dev_id)
{
	register int sp, np, olen;
	char *opt, buf[10];

#ifdef ANDROID_EXPAND_NAME
	char value[PROPERTY_VALUE_MAX];
#endif

	if (!str || !dst)
		return NULL;

	sp = np = 0;
	while (np < size - 1 && str[sp]) {
		switch (str[sp]) {
		case '%':
			opt = NULL;

			switch (str[sp+1]) {
			case 'd':
				sprintf(buf, "%d", dev_id);
				opt = buf;
				break;

			case 'h':
				opt = main_opts.host_name;
				break;

#ifdef ANDROID_EXPAND_NAME
			case 'b':
				property_get("ro.product.brand", value, "");
				opt = value;
			break;

			case 'm':
				property_get("ro.product.model", value, "");
				opt = value;
			break;

			case 'n':
				property_get("ro.product.name", value, "");
				opt = value;
			break;
#endif

			case '%':
				dst[np++] = str[sp++];
				/* fall through */
			default:
				sp++;
				continue;
			}

			if (opt) {
				/* substitute */
				olen = strlen(opt);
				if (np + olen < size - 1)
					memcpy(dst + np, opt, olen);
				np += olen;
			}
			sp += 2;
			continue;

		case '\\':
			sp++;
			/* fall through */
		default:
			dst[np++] = str[sp++];
			break;
		}
	}
	dst[np] = '\0';
	return dst;
}

int btd_adapter_set_class(struct btd_adapter *adapter, uint8_t major,
								uint8_t minor)
{
	return adapter_ops->set_dev_class(adapter->dev_id, major, minor);
}

static int pending_remote_name_cancel(struct btd_adapter *adapter)
{
	struct remote_dev_info *dev, match;
	int err;

	/* find the pending remote name request */
	memset(&match, 0, sizeof(struct remote_dev_info));
	bacpy(&match.bdaddr, BDADDR_ANY);
	match.name_status = NAME_REQUESTED;

	dev = adapter_search_found_devices(adapter, &match);
	if (!dev) /* no pending request */
		return -ENODATA;

	err = adapter_ops->cancel_resolve_name(adapter->dev_id, &dev->bdaddr);
	if (err < 0)
		error("Remote name cancel failed: %s(%d)",
						strerror(errno), errno);

	adapter_set_state(adapter, STATE_IDLE);

	return err;
}

int adapter_resolve_names(struct btd_adapter *adapter)
{
	struct remote_dev_info *dev, match;
	int err;

	/* Do not attempt to resolve more names if on suspended state */
	if (adapter->state == STATE_SUSPENDED)
		return 0;

	memset(&match, 0, sizeof(struct remote_dev_info));
	bacpy(&match.bdaddr, BDADDR_ANY);
	match.name_status = NAME_REQUIRED;

	dev = adapter_search_found_devices(adapter, &match);
	if (!dev)
		return -ENODATA;

	/* send at least one request or return failed if the list is empty */
	do {
		/* flag to indicate the current remote name requested */
		dev->name_status = NAME_REQUESTED;

		err = adapter_ops->resolve_name(adapter->dev_id, &dev->bdaddr);

		if (!err)
			break;

		error("Unable to send HCI remote name req: %s (%d)",
						strerror(errno), errno);

		/* if failed, request the next element */
		/* remove the element from the list */
		adapter_remove_found_device(adapter, &dev->bdaddr);

		/* get the next element */
		dev = adapter_search_found_devices(adapter, &match);
	} while (dev);

	return err;
}

static const char *mode2str(uint8_t mode)
{
	switch(mode) {
	case MODE_OFF:
		return "off";
	case MODE_CONNECTABLE:
		return "connectable";
	case MODE_DISCOVERABLE:
		return "discoverable";
	default:
		return "unknown";
	}
}

static uint8_t get_mode(const bdaddr_t *bdaddr, const char *mode)
{
	if (strcasecmp("off", mode) == 0)
		return MODE_OFF;
	else if (strcasecmp("connectable", mode) == 0)
		return MODE_CONNECTABLE;
	else if (strcasecmp("discoverable", mode) == 0)
		return MODE_DISCOVERABLE;
	else if (strcasecmp("on", mode) == 0) {
		char onmode[14], srcaddr[18];

		ba2str(bdaddr, srcaddr);
		if (read_on_mode(srcaddr, onmode, sizeof(onmode)) < 0)
			return MODE_CONNECTABLE;

		return get_mode(bdaddr, onmode);
	} else
		return MODE_UNKNOWN;
}

static void adapter_set_limited_discoverable(struct btd_adapter *adapter,
							gboolean limited)
{
	DBG("%s", limited ? "TRUE" : "FALSE");

	adapter_ops->set_limited_discoverable(adapter->dev_id, limited);
}

static void adapter_remove_discov_timeout(struct btd_adapter *adapter)
{
	if (!adapter)
		return;

	if (adapter->discov_timeout_id == 0)
		return;

	g_source_remove(adapter->discov_timeout_id);
	adapter->discov_timeout_id = 0;
}

static gboolean discov_timeout_handler(gpointer user_data)
{
	struct btd_adapter *adapter = user_data;

	adapter->discov_timeout_id = 0;

	set_discoverable(NULL, NULL, FALSE, user_data);

	return FALSE;
}

static void adapter_set_discov_timeout(struct btd_adapter *adapter,
					guint interval)
{
	if (adapter->discov_timeout_id) {
		g_source_remove(adapter->discov_timeout_id);
		adapter->discov_timeout_id = 0;
	}

	if (interval == 0) {
		adapter_set_limited_discoverable(adapter, FALSE);
		return;
	}

	/* Set limited discoverable if pairable and interval between 0 to 60
	   sec */
	if (adapter->pairable && interval <= 60)
		adapter_set_limited_discoverable(adapter, TRUE);
	else
		adapter_set_limited_discoverable(adapter, FALSE);

	adapter->discov_timeout_id = g_timeout_add_seconds(interval,
							discov_timeout_handler,
							adapter);
}

static struct session_req *session_ref(struct session_req *req)
{
	req->refcount++;

	DBG("%p: ref=%d", req, req->refcount);

	return req;
}

static struct session_req *create_session(struct btd_adapter *adapter,
					DBusConnection *conn, DBusMessage *msg,
					uint8_t mode, GDBusWatchFunction cb)
{
	const char *sender = dbus_message_get_sender(msg);
	struct session_req *req;

	req = g_new0(struct session_req, 1);
	req->adapter = adapter;
	req->conn = dbus_connection_ref(conn);
	req->msg = dbus_message_ref(msg);
	req->mode = mode;

	if (cb == NULL)
		return session_ref(req);

	req->owner = g_strdup(sender);
	req->id = g_dbus_add_disconnect_watch(conn, sender, cb, req, NULL);

	info("%s session %p with %s activated",
		req->mode ? "Mode" : "Discovery", req, sender);

	return session_ref(req);
}

static int adapter_set_mode(struct btd_adapter *adapter, uint8_t mode)
{
	int err;

	if (mode == MODE_CONNECTABLE)
		err = adapter_ops->set_discoverable(adapter->dev_id, FALSE);
	else
		err = adapter_ops->set_discoverable(adapter->dev_id, TRUE);

	if (err < 0)
		return err;

	if (mode == MODE_CONNECTABLE)
		return 0;

	adapter_remove_discov_timeout(adapter);

	if (adapter->discov_timeout)
		adapter_set_discov_timeout(adapter, adapter->discov_timeout);

	return 0;
}

static 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,
			DBusMessage *msg)
{
	int err;
	const char *modestr;

	if (adapter->pending_mode != NULL)
		return -EALREADY;

	if (!adapter->up && new_mode != MODE_OFF) {
		err = adapter_ops->set_powered(adapter->dev_id, TRUE);
		if (err < 0)
			return err;

		goto done;
	}

	if (adapter->up && new_mode == MODE_OFF) {
		err = adapter_ops->set_powered(adapter->dev_id, FALSE);
		if (err < 0)
			return err;

		adapter->off_requested = TRUE;

		goto done;
	}

	if (new_mode == adapter->mode)
		return 0;

	err = adapter_set_mode(adapter, new_mode);

	if (err < 0)
		return err;

done:
	modestr = mode2str(new_mode);
	write_device_mode(&adapter->bdaddr, modestr);

	DBG("%s", modestr);

	if (msg != NULL) {
		struct session_req *req;

		req = find_session_by_msg(adapter->mode_sessions, msg);
		if (req) {
			adapter->pending_mode = req;
			session_ref(req);
		} else
			/* Wait for mode change to reply */
			adapter->pending_mode = create_session(adapter,
					connection, msg, new_mode, NULL);
	} else
		/* Nothing to reply just write the new mode */
		adapter->mode = new_mode;

	return 0;
}

static DBusMessage *set_discoverable(DBusConnection *conn, DBusMessage *msg,
				gboolean discoverable, void *data)
{
	struct btd_adapter *adapter = data;
	uint8_t mode;
	int err;

	mode = discoverable ? MODE_DISCOVERABLE : MODE_CONNECTABLE;

	if (mode == adapter->mode) {
		adapter->global_mode = mode;
		return dbus_message_new_method_return(msg);
	}

	err = set_mode(adapter, mode, msg);
	if (err < 0)
		return btd_error_failed(msg, strerror(-err));

	return NULL;
}

static DBusMessage *set_powered(DBusConnection *conn, DBusMessage *msg,
				gboolean powered, void *data)
{
	struct btd_adapter *adapter = data;
	uint8_t mode;
	int err;

	if (powered) {
		mode = get_mode(&adapter->bdaddr, "on");
		return set_discoverable(conn, msg, mode == MODE_DISCOVERABLE,
									data);
	}

	mode = MODE_OFF;

	if (mode == adapter->mode) {
		adapter->global_mode = mode;
		return dbus_message_new_method_return(msg);
	}

	err = set_mode(adapter, mode, msg);
	if (err < 0)
		return btd_error_failed(msg, strerror(-err));

	return NULL;
}

void btd_adapter_pairable_changed(struct btd_adapter *adapter,
							gboolean pairable)
{
	adapter->pairable = pairable;

	write_device_pairable(&adapter->bdaddr, pairable);

	emit_property_changed(connection, adapter->path,
				ADAPTER_INTERFACE, "Pairable",
				DBUS_TYPE_BOOLEAN, &pairable);

	if (pairable && adapter->pairable_timeout)
		adapter_set_pairable_timeout(adapter,
						adapter->pairable_timeout);
}

static DBusMessage *set_pairable(DBusConnection *conn, DBusMessage *msg,
				gboolean pairable, void *data)
{
	struct btd_adapter *adapter = data;
	int err;

	if (adapter->scan_mode == SCAN_DISABLED)
		return btd_error_not_ready(msg);

	if (pairable == adapter->pairable)
		goto done;

	if (!(adapter->scan_mode & SCAN_INQUIRY))
		goto store;

	err = set_mode(adapter, MODE_DISCOVERABLE, NULL);
	if (err < 0 && msg)
		return btd_error_failed(msg, strerror(-err));

store:
	adapter_ops->set_pairable(adapter->dev_id, pairable);

done:
	return msg ? dbus_message_new_method_return(msg) : NULL;
}

static gboolean pairable_timeout_handler(void *data)
{
	set_pairable(NULL, NULL, FALSE, data);

	return FALSE;
}

static void adapter_set_pairable_timeout(struct btd_adapter *adapter,
					guint interval)
{
	if (adapter->pairable_timeout_id) {
		g_source_remove(adapter->pairable_timeout_id);
		adapter->pairable_timeout_id = 0;
	}

	if (interval == 0)
		return;

	adapter->pairable_timeout_id = g_timeout_add_seconds(interval,
						pairable_timeout_handler,
						adapter);
}

static struct session_req *find_session(GSList *list, const char *sender)
{
	for (; list; list = list->next) {
		struct session_req *req = list->data;

		if (g_str_equal(req->owner, sender))
			return req;
	}

	return NULL;
}

static uint8_t get_needed_mode(struct btd_adapter *adapter, uint8_t mode)
{
	GSList *l;

	if (adapter->global_mode > mode)
		mode = adapter->global_mode;

	for (l = adapter->mode_sessions; l; l = l->next) {
		struct session_req *req = l->data;

		if (req->mode > mode)
			mode = req->mode;
	}

	return mode;
}

static GSList *remove_bredr(GSList *all)
{
	GSList *l, *le;

	for (l = all, le = NULL; l; l = l->next) {
		struct remote_dev_info *dev = l->data;
		if (dev->le == FALSE) {
			dev_info_free(dev);
			continue;
		}

		le = g_slist_append(le, dev);
	}

	g_slist_free(all);

	return le;
}

static void stop_discovery(struct btd_adapter *adapter)
{
	pending_remote_name_cancel(adapter);

	adapter->found_devices = remove_bredr(adapter->found_devices);

	if (adapter->oor_devices) {
		g_slist_free(adapter->oor_devices);
		adapter->oor_devices = NULL;
	}

	/* Reset if suspended, otherwise remove timer (software scheduler)
	   or request inquiry to stop */
	if (adapter->state == STATE_SUSPENDED) {
		adapter_set_state(adapter, STATE_IDLE);
		return;
	}

	if (adapter->scheduler_id) {
		g_source_remove(adapter->scheduler_id);
		adapter->scheduler_id = 0;
		return;
	}

	adapter_ops->stop_discovery(adapter->dev_id);
}

static void session_remove(struct session_req *req)
{
	struct btd_adapter *adapter = req->adapter;

	/* Ignore set_mode session */
	if (req->owner == NULL)
		return;

	DBG("%s session %p with %s deactivated",
		req->mode ? "Mode" : "Discovery", req, req->owner);

	if (req->mode) {
		uint8_t mode;

		adapter->mode_sessions = g_slist_remove(adapter->mode_sessions,
							req);

		mode = get_needed_mode(adapter, adapter->global_mode);

		if (mode == adapter->mode)
			return;

		DBG("Switching to '%s' mode", mode2str(mode));

		set_mode(adapter, mode, NULL);
	} else {
		adapter->disc_sessions = g_slist_remove(adapter->disc_sessions,
							req);

		if (adapter->disc_sessions)
			return;

		DBG("Stopping discovery");

		stop_discovery(adapter);
	}
}

static void session_free(struct session_req *req)
{
	if (req->id)
		g_dbus_remove_watch(req->conn, req->id);

	session_remove(req);

	if (req->msg) {
		dbus_message_unref(req->msg);
		if (!req->got_reply && req->mode && req->adapter->agent)
			agent_cancel(req->adapter->agent);
	}

	if (req->conn)
		dbus_connection_unref(req->conn);
	g_free(req->owner);
	g_free(req);
}

static void session_owner_exit(DBusConnection *conn, void *user_data)
{
	struct session_req *req = user_data;

	req->id = 0;

	session_free(req);
}

static void session_unref(struct session_req *req)
{
	req->refcount--;

	DBG("%p: ref=%d", req, req->refcount);

	if (req->refcount)
		return;

	session_free(req);
}

static void confirm_mode_cb(struct agent *agent, DBusError *derr, void *data)
{
	struct session_req *req = data;
	int err;
	DBusMessage *reply;

	req->got_reply = TRUE;

	if (derr && dbus_error_is_set(derr)) {
		reply = dbus_message_new_error(req->msg, derr->name,
						derr->message);
		g_dbus_send_message(req->conn, reply);
		session_unref(req);
		return;
	}

	err = set_mode(req->adapter, req->mode, req->msg);
	if (err < 0)
		reply = btd_error_failed(req->msg, strerror(-err));
	else if (!req->adapter->pending_mode)
		reply = dbus_message_new_method_return(req->msg);
	else
		reply = NULL;

	if (reply) {
		/*
		 * 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(req->conn, reply);

		dbus_message_unref(req->msg);
		req->msg = NULL;
	}

	if (!find_session(req->adapter->mode_sessions, req->owner))
		session_unref(req);
}

static DBusMessage *set_discoverable_timeout(DBusConnection *conn,
							DBusMessage *msg,
							uint32_t timeout,
							void *data)
{
	struct btd_adapter *adapter = data;
	const char *path;

	if (adapter->discov_timeout == timeout && timeout == 0)
		return dbus_message_new_method_return(msg);

	if (adapter->scan_mode & SCAN_INQUIRY)
		adapter_set_discov_timeout(adapter, timeout);

	adapter->discov_timeout = timeout;

	write_discoverable_timeout(&adapter->bdaddr, timeout);

	path = dbus_message_get_path(msg);

	emit_property_changed(conn, path,
				ADAPTER_INTERFACE, "DiscoverableTimeout",
				DBUS_TYPE_UINT32, &timeout);

	return dbus_message_new_method_return(msg);
}

static DBusMessage *set_pairable_timeout(DBusConnection *conn,
						DBusMessage *msg,
						uint32_t timeout,
						void *data)
{
	struct btd_adapter *adapter = data;
	const char *path;

	if (adapter->pairable_timeout == timeout && timeout == 0)
		return dbus_message_new_method_return(msg);

	if (adapter->pairable)
		adapter_set_pairable_timeout(adapter, timeout);

	adapter->pairable_timeout = timeout;

	write_pairable_timeout(&adapter->bdaddr, timeout);

	path = dbus_message_get_path(msg);

	emit_property_changed(conn, path,
				ADAPTER_INTERFACE, "PairableTimeout",
				DBUS_TYPE_UINT32, &timeout);

	return dbus_message_new_method_return(msg);
}

void btd_adapter_class_changed(struct btd_adapter *adapter, uint32_t new_class)
{
	uint8_t class[3];

	class[2] = (new_class >> 16) & 0xff;
	class[1] = (new_class >> 8) & 0xff;
	class[0] = new_class & 0xff;

	write_local_class(&adapter->bdaddr, class);

	adapter->dev_class = new_class;

	if (main_opts.attrib_server) {
		/* Removes service class */
		class[1] = class[1] & 0x1f;
		attrib_gap_set(GATT_CHARAC_APPEARANCE, class, 2);
	}

	emit_property_changed(connection, adapter->path,
				ADAPTER_INTERFACE, "Class",
				DBUS_TYPE_UINT32, &new_class);
}

void adapter_update_local_name(struct btd_adapter *adapter, const char *name)
{
	if (strncmp(name, adapter->name, MAX_NAME_LENGTH) == 0)
		return;

	strncpy(adapter->name, name, MAX_NAME_LENGTH);

	if (main_opts.attrib_server)
		attrib_gap_set(GATT_CHARAC_DEVICE_NAME,
			(const uint8_t *) adapter->name, strlen(adapter->name));

	if (!adapter->name_stored) {
		char *name_ptr = adapter->name;

		write_local_name(&adapter->bdaddr, adapter->name);

		if (connection)
			emit_property_changed(connection, adapter->path,
						ADAPTER_INTERFACE, "Name",
						DBUS_TYPE_STRING, &name_ptr);
	}

	adapter->name_stored = FALSE;
}

static DBusMessage *set_name(DBusConnection *conn, DBusMessage *msg,
					const char *name, void *data)
{
	struct btd_adapter *adapter = data;
	char *name_ptr = adapter->name;

	if (!g_utf8_validate(name, -1, NULL)) {
		error("Name change failed: supplied name isn't valid UTF-8");
		return btd_error_invalid_args(msg);
	}

	if (strncmp(name, adapter->name, MAX_NAME_LENGTH) == 0)
		goto done;

	strncpy(adapter->name, name, MAX_NAME_LENGTH);
	write_local_name(&adapter->bdaddr, name);
	emit_property_changed(connection, adapter->path,
					ADAPTER_INTERFACE, "Name",
					DBUS_TYPE_STRING, &name_ptr);

	if (adapter->up) {
		int err = adapter_ops->set_name(adapter->dev_id, name);
		if (err < 0)
			return btd_error_failed(msg, strerror(-err));

		adapter->name_stored = TRUE;
	}

done:
	return dbus_message_new_method_return(msg);
}

struct btd_device *adapter_find_device(struct btd_adapter *adapter,
							const char *dest)
{
	struct btd_device *device;
	GSList *l;

	if (!adapter)
		return NULL;

	l = g_slist_find_custom(adapter->devices, dest,
					(GCompareFunc) device_address_cmp);
	if (!l)
		return NULL;

	device = l->data;

	return device;
}

static void adapter_update_devices(struct btd_adapter *adapter)
{
	char **devices;
	int i;
	GSList *l;

	/* Devices */
	devices = g_new0(char *, g_slist_length(adapter->devices) + 1);
	for (i = 0, l = adapter->devices; l; l = l->next, i++) {
		struct btd_device *dev = l->data;
		devices[i] = (char *) device_get_path(dev);
	}

	emit_array_property_changed(connection, adapter->path,
					ADAPTER_INTERFACE, "Devices",
					DBUS_TYPE_OBJECT_PATH, &devices, i);
	g_free(devices);
}

static void adapter_emit_uuids_updated(struct btd_adapter *adapter)
{
	char **uuids;
	int i;
	sdp_list_t *list;

	if (!adapter->initialized)
		return;

	uuids = g_new0(char *, sdp_list_len(adapter->services) + 1);

	for (i = 0, list = adapter->services; list; list = list->next) {
		char *uuid;
		sdp_record_t *rec = list->data;

		uuid = bt_uuid2string(&rec->svclass);
		if (uuid)
			uuids[i++] = uuid;
	}

	emit_array_property_changed(connection, adapter->path,
			ADAPTER_INTERFACE, "UUIDs", DBUS_TYPE_STRING, &uuids, i);

	g_strfreev(uuids);
}

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);
		adapter_ops->add_uuid(adapter->dev_id, &rec->svclass, svc_hint);
	}

	adapter_emit_uuids_updated(adapter);
}

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)
		adapter_ops->remove_uuid(adapter->dev_id, &rec->svclass);

	adapter_emit_uuids_updated(adapter);
}

static struct btd_device *adapter_create_device(DBusConnection *conn,
						struct btd_adapter *adapter,
						const char *address,
						device_type_t type)
{
	struct btd_device *device;
	const char *path;

	DBG("%s", address);

	device = device_create(conn, adapter, address, type);
	if (!device)
		return NULL;

	device_set_temporary(device, TRUE);

	adapter->devices = g_slist_append(adapter->devices, device);

	path = device_get_path(device);
	g_dbus_emit_signal(conn, adapter->path,
			ADAPTER_INTERFACE, "DeviceCreated",
			DBUS_TYPE_OBJECT_PATH, &path,
			DBUS_TYPE_INVALID);

	adapter_update_devices(adapter);

	return device;
}

void adapter_remove_device(DBusConnection *conn, struct btd_adapter *adapter,
						struct btd_device *device,
						gboolean remove_storage)
{
	const gchar *dev_path = device_get_path(device);
	struct agent *agent;

	adapter->devices = g_slist_remove(adapter->devices, device);
	adapter->connections = g_slist_remove(adapter->connections, device);

	adapter_update_devices(adapter);

	g_dbus_emit_signal(conn, adapter->path,
			ADAPTER_INTERFACE, "DeviceRemoved",
			DBUS_TYPE_OBJECT_PATH, &dev_path,
			DBUS_TYPE_INVALID);

	agent = device_get_agent(device);

	if (agent && device_is_authorizing(device))
		agent_cancel(agent);

	device_remove(device, remove_storage);
}

struct btd_device *adapter_get_device(DBusConnection *conn,
						struct btd_adapter *adapter,
						const gchar *address)
{
	struct btd_device *device;

	DBG("%s", address);

	if (!adapter)
		return NULL;

	device = adapter_find_device(adapter, address);
	if (device)
		return device;

	return adapter_create_device(conn, adapter, address,
						DEVICE_TYPE_BREDR);
}

static int start_discovery(struct btd_adapter *adapter)
{
	/* Do not start if suspended */
	if (adapter->state == STATE_SUSPENDED)
		return 0;

	/* Postpone discovery if still resolving names */
	if (adapter->state == STATE_RESOLVNAME)
		return -EINPROGRESS;

	pending_remote_name_cancel(adapter);

	return adapter_ops->start_discovery(adapter->dev_id);
}

static gboolean discovery_cb(gpointer user_data)
{
	struct btd_adapter *adapter = user_data;
	int err;

	err = start_discovery(adapter);
	if (err == -EINPROGRESS)
		return TRUE;
	else if (err < 0)
		error("start_discovery: %s (%d)", strerror(-err), -err);

	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;

	g_slist_foreach(adapter->found_devices, (GFunc) dev_info_free, NULL);
	g_slist_free(adapter->found_devices);
	adapter->found_devices = NULL;

	g_slist_free(adapter->oor_devices);
	adapter->oor_devices = NULL;

	err = start_discovery(adapter);
	if (err < 0 && err != -EINPROGRESS)
		return btd_error_failed(msg, strerror(-err));

done:
	req = create_session(adapter, conn, msg, 0,
				session_owner_exit);

	adapter->disc_sessions = g_slist_append(adapter->disc_sessions, req);

	return dbus_message_new_method_return(msg);
}

static DBusMessage *adapter_stop_discovery(DBusConnection *conn,
						DBusMessage *msg, void *data)
{
	struct btd_adapter *adapter = data;
	struct session_req *req;
	const char *sender = dbus_message_get_sender(msg);

	if (!adapter->up)
		return btd_error_not_ready(msg);

	req = find_session(adapter->disc_sessions, sender);
	if (!req)
		return btd_error_failed(msg, "Invalid discovery session");

	session_unref(req);
	info("Stopping discovery");
	return dbus_message_new_method_return(msg);
}

static DBusMessage *get_properties(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	struct btd_adapter *adapter = data;
	const char *property;
	DBusMessage *reply;
	DBusMessageIter iter;
	DBusMessageIter dict;
	char str[MAX_NAME_LENGTH + 1], srcaddr[18];
	gboolean value;
	char **devices, **uuids;
	int i;
	GSList *l;
	sdp_list_t *list;

	ba2str(&adapter->bdaddr, srcaddr);

	if (check_address(srcaddr) < 0)
		return btd_error_invalid_args(msg);

	reply = dbus_message_new_method_return(msg);
	if (!reply)
		return NULL;

	dbus_message_iter_init_append(reply, &iter);

	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);

	/* Address */
	property = srcaddr;
	dict_append_entry(&dict, "Address", DBUS_TYPE_STRING, &property);

	/* Name */
	memset(str, 0, sizeof(str));
	strncpy(str, (char *) adapter->name, MAX_NAME_LENGTH);
	property = str;

	dict_append_entry(&dict, "Name", DBUS_TYPE_STRING, &property);

	/* Class */
	dict_append_entry(&dict, "Class",
				DBUS_TYPE_UINT32, &adapter->dev_class);

	/* Powered */
	value = (adapter->up && !adapter->off_requested) ? TRUE : FALSE;
	dict_append_entry(&dict, "Powered", DBUS_TYPE_BOOLEAN, &value);

	/* Discoverable */
	value = adapter->scan_mode & SCAN_INQUIRY ? TRUE : FALSE;
	dict_append_entry(&dict, "Discoverable", DBUS_TYPE_BOOLEAN, &value);

	/* Pairable */
	dict_append_entry(&dict, "Pairable", DBUS_TYPE_BOOLEAN,
				&adapter->pairable);

	/* DiscoverableTimeout */
	dict_append_entry(&dict, "DiscoverableTimeout",
				DBUS_TYPE_UINT32, &adapter->discov_timeout);

	/* PairableTimeout */
	dict_append_entry(&dict, "PairableTimeout",
				DBUS_TYPE_UINT32, &adapter->pairable_timeout);


	if (adapter->state == STATE_DISCOV)
		value = TRUE;
	else
		value = FALSE;

	/* Discovering */
	dict_append_entry(&dict, "Discovering", DBUS_TYPE_BOOLEAN, &value);

	/* Devices */
	devices = g_new0(char *, g_slist_length(adapter->devices) + 1);
	for (i = 0, l = adapter->devices; l; l = l->next, i++) {
		struct btd_device *dev = l->data;
		devices[i] = (char *) device_get_path(dev);
	}
	dict_append_array(&dict, "Devices", DBUS_TYPE_OBJECT_PATH,
								&devices, i);
	g_free(devices);

	/* UUIDs */
	uuids = g_new0(char *, sdp_list_len(adapter->services) + 1);

	for (i = 0, list = adapter->services; list; list = list->next) {
		sdp_record_t *rec = list->data;
		char *uuid;

		uuid = bt_uuid2string(&rec->svclass);
		if (uuid)
			uuids[i++] = uuid;
	}

	dict_append_array(&dict, "UUIDs", DBUS_TYPE_STRING, &uuids, i);

	g_strfreev(uuids);

	dbus_message_iter_close_container(&iter, &dict);

	return reply;
}

static DBusMessage *set_property(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	struct btd_adapter *adapter = data;
	DBusMessageIter iter;
	DBusMessageIter sub;
	const char *property;
	char srcaddr[18];

	ba2str(&adapter->bdaddr, srcaddr);

	if (!dbus_message_iter_init(msg, &iter))
		return btd_error_invalid_args(msg);

	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
		return btd_error_invalid_args(msg);

	dbus_message_iter_get_basic(&iter, &property);
	dbus_message_iter_next(&iter);

	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
		return btd_error_invalid_args(msg);
	dbus_message_iter_recurse(&iter, &sub);

	if (g_str_equal("Name", property)) {
		const char *name;

		if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING)
			return btd_error_invalid_args(msg);
		dbus_message_iter_get_basic(&sub, &name);

		return set_name(conn, msg, name, data);
	} else if (g_str_equal("Powered", property)) {
		gboolean powered;

		if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
			return btd_error_invalid_args(msg);

		dbus_message_iter_get_basic(&sub, &powered);

		return set_powered(conn, msg, powered, data);
	} else if (g_str_equal("Discoverable", property)) {
		gboolean discoverable;

		if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
			return btd_error_invalid_args(msg);

		dbus_message_iter_get_basic(&sub, &discoverable);

		return set_discoverable(conn, msg, discoverable, data);
	} else if (g_str_equal("DiscoverableTimeout", property)) {
		uint32_t timeout;

		if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT32)
			return btd_error_invalid_args(msg);

		dbus_message_iter_get_basic(&sub, &timeout);

		return set_discoverable_timeout(conn, msg, timeout, data);
	} else if (g_str_equal("Pairable", property)) {
		gboolean pairable;

		if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
			return btd_error_invalid_args(msg);

		dbus_message_iter_get_basic(&sub, &pairable);

		return set_pairable(conn, msg, pairable, data);
	} else if (g_str_equal("PairableTimeout", property)) {
		uint32_t timeout;

		if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT32)
			return btd_error_invalid_args(msg);

		dbus_message_iter_get_basic(&sub, &timeout);

		return set_pairable_timeout(conn, msg, timeout, data);
	}

	return btd_error_invalid_args(msg);
}

static DBusMessage *request_session(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	struct btd_adapter *adapter = data;
	struct session_req *req;
	const char *sender = dbus_message_get_sender(msg);
	uint8_t new_mode;
	int err;

	if (!adapter->agent)
		return btd_error_agent_not_available(msg);

	if (!adapter->mode_sessions)
		adapter->global_mode = adapter->mode;

	new_mode = get_mode(&adapter->bdaddr, "on");

	req = find_session(adapter->mode_sessions, sender);
	if (req) {
		session_ref(req);
		return dbus_message_new_method_return(msg);
	} else {
		req = create_session(adapter, conn, msg, new_mode,
					session_owner_exit);
		adapter->mode_sessions = g_slist_append(adapter->mode_sessions,
							req);
	}

	/* No need to change mode */
	if (adapter->mode >= new_mode)
		return dbus_message_new_method_return(msg);

	err = agent_confirm_mode_change(adapter->agent, mode2str(new_mode),
					confirm_mode_cb, req, NULL);
	if (err < 0) {
		session_unref(req);
		return btd_error_failed(msg, strerror(-err));
	}

	return NULL;
}

static DBusMessage *release_session(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	struct btd_adapter *adapter = data;
	struct session_req *req;
	const char *sender = dbus_message_get_sender(msg);

	req = find_session(adapter->mode_sessions, sender);
	if (!req)
		return btd_error_failed(msg, "Invalid Session");

	session_unref(req);

	return dbus_message_new_method_return(msg);
}

static DBusMessage *list_devices(DBusConnection *conn,
						DBusMessage *msg, void *data)
{
	struct btd_adapter *adapter = data;
	DBusMessage *reply;
	GSList *l;
	DBusMessageIter iter;
	DBusMessageIter array_iter;
	const gchar *dev_path;

	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING))
		return btd_error_invalid_args(msg);

	reply = dbus_message_new_method_return(msg);
	if (!reply)
		return NULL;

	dbus_message_iter_init_append(reply, &iter);
	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
				DBUS_TYPE_OBJECT_PATH_AS_STRING, &array_iter);

	for (l = adapter->devices; l; l = l->next) {
		struct btd_device *device = l->data;

		dev_path = device_get_path(device);

		dbus_message_iter_append_basic(&array_iter,
				DBUS_TYPE_OBJECT_PATH, &dev_path);
	}

	dbus_message_iter_close_container(&iter, &array_iter);

	return reply;
}

static DBusMessage *cancel_device_creation(DBusConnection *conn,
						DBusMessage *msg, void *data)
{
	struct btd_adapter *adapter = data;
	const gchar *address, *sender = dbus_message_get_sender(msg);
	struct btd_device *device;

	if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address,
						DBUS_TYPE_INVALID) == FALSE)
		return btd_error_invalid_args(msg);

	if (check_address(address) < 0)
		return btd_error_invalid_args(msg);

	device = adapter_find_device(adapter, address);
	if (!device || !device_is_creating(device, NULL))
		return btd_error_does_not_exist(msg);

	if (!device_is_creating(device, sender))
		return btd_error_not_authorized(msg);

	device_set_temporary(device, TRUE);

	if (device_is_connected(device)) {
		device_request_disconnect(device, msg);
		return NULL;
	}

	adapter_remove_device(conn, adapter, device, TRUE);

	return dbus_message_new_method_return(msg);
}

static struct btd_device *create_device_internal(DBusConnection *conn,
						struct btd_adapter *adapter,
						const gchar *address, int *err)
{
	struct remote_dev_info *dev, match;
	struct btd_device *device;
	device_type_t type;

	memset(&match, 0, sizeof(struct remote_dev_info));
	str2ba(address, &match.bdaddr);
	match.name_status = NAME_ANY;

	dev = adapter_search_found_devices(adapter, &match);
	if (dev && dev->le)
		type = DEVICE_TYPE_LE;
	else
		type = DEVICE_TYPE_BREDR;

	device = adapter_create_device(conn, adapter, address, type);
	if (!device && err)
		*err = -ENOMEM;

	return device;
}

static DBusMessage *create_device(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	struct btd_adapter *adapter = data;
	struct btd_device *device;
	const gchar *address;
	DBusMessage *reply;
	int err;

	if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address,
						DBUS_TYPE_INVALID) == FALSE)
		return btd_error_invalid_args(msg);

	if (check_address(address) < 0)
		return btd_error_invalid_args(msg);

	if (!adapter->up)
		return btd_error_not_ready(msg);

	if (adapter_find_device(adapter, address))
		return btd_error_already_exists(msg);

	DBG("%s", address);

	device = create_device_internal(conn, adapter, address, &err);
	if (!device)
		goto failed;

	if (device_get_type(device) != DEVICE_TYPE_LE)
		err = device_browse_sdp(device, conn, msg, NULL, FALSE);
	else
		err = device_browse_primary(device, conn, msg, FALSE);

	if (err < 0) {
		adapter_remove_device(conn, adapter, device, TRUE);
		return btd_error_failed(msg, strerror(-err));
	}

	return NULL;

failed:
	if (err == -ENOTCONN) {
		/* Device is not connectable */
		const char *path = device_get_path(device);

		reply = dbus_message_new_method_return(msg);

		dbus_message_append_args(reply,
				DBUS_TYPE_OBJECT_PATH, &path,
				DBUS_TYPE_INVALID);
	} else
		reply = btd_error_failed(msg, strerror(-err));

	return reply;
}

static uint8_t parse_io_capability(const char *capability)
{
	if (g_str_equal(capability, ""))
		return IO_CAPABILITY_DISPLAYYESNO;
	if (g_str_equal(capability, "DisplayOnly"))
		return IO_CAPABILITY_DISPLAYONLY;
	if (g_str_equal(capability, "DisplayYesNo"))
		return IO_CAPABILITY_DISPLAYYESNO;
	if (g_str_equal(capability, "KeyboardOnly"))
		return IO_CAPABILITY_KEYBOARDONLY;
	if (g_str_equal(capability, "NoInputNoOutput"))
		return IO_CAPABILITY_NOINPUTNOOUTPUT;
	return IO_CAPABILITY_INVALID;
}

static DBusMessage *create_paired_device(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	struct btd_adapter *adapter = data;
	struct btd_device *device;
	const gchar *address, *agent_path, *capability, *sender;
	uint8_t cap;
	int err;

	if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address,
					DBUS_TYPE_OBJECT_PATH, &agent_path,
					DBUS_TYPE_STRING, &capability,
					DBUS_TYPE_INVALID) == FALSE)
		return btd_error_invalid_args(msg);

	if (check_address(address) < 0)
		return btd_error_invalid_args(msg);

	if (!adapter->up)
		return btd_error_not_ready(msg);

	sender = dbus_message_get_sender(msg);
	if (adapter->agent &&
			agent_matches(adapter->agent, sender, agent_path)) {
		error("Refusing adapter agent usage as device specific one");
		return btd_error_invalid_args(msg);
	}

	cap = parse_io_capability(capability);
	if (cap == IO_CAPABILITY_INVALID)
		return btd_error_invalid_args(msg);

	device = adapter_find_device(adapter, address);
	if (!device) {
		device = create_device_internal(conn, adapter, address, &err);
		if (!device)
			return btd_error_failed(msg, strerror(-err));
	}

	if (device_get_type(device) != DEVICE_TYPE_LE)
		return device_create_bonding(device, conn, msg,
							agent_path, cap);

	err = device_browse_primary(device, conn, msg, TRUE);
	if (err < 0)
		return btd_error_failed(msg, strerror(-err));

	return NULL;
}

static gint device_path_cmp(struct btd_device *device, const gchar *path)
{
	const gchar *dev_path = device_get_path(device);

	return strcasecmp(dev_path, path);
}

static DBusMessage *remove_device(DBusConnection *conn, DBusMessage *msg,
								void *data)
{
	struct btd_adapter *adapter = data;
	struct btd_device *device;
	const char *path;
	GSList *l;

	if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
						DBUS_TYPE_INVALID) == FALSE)
		return btd_error_invalid_args(msg);

	l = g_slist_find_custom(adapter->devices,
			path, (GCompareFunc) device_path_cmp);
	if (!l)
		return btd_error_does_not_exist(msg);

	device = l->data;

	if (device_is_temporary(device) || device_is_busy(device))
		return g_dbus_create_error(msg,
				ERROR_INTERFACE ".DoesNotExist",
				"Device creation in progress");

	device_set_temporary(device, TRUE);

	if (!device_is_connected(device)) {
		adapter_remove_device(conn, adapter, device, TRUE);
		return dbus_message_new_method_return(msg);
	}

	device_request_disconnect(device, msg);
	return NULL;
}

static DBusMessage *find_device(DBusConnection *conn, DBusMessage *msg,
								void *data)
{
	struct btd_adapter *adapter = data;
	struct btd_device *device;
	DBusMessage *reply;
	const gchar *address;
	GSList *l;
	const gchar *dev_path;

	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address,
						DBUS_TYPE_INVALID))
		return btd_error_invalid_args(msg);

	l = g_slist_find_custom(adapter->devices,
			address, (GCompareFunc) device_address_cmp);
	if (!l)
		return btd_error_does_not_exist(msg);

	device = l->data;

	reply = dbus_message_new_method_return(msg);
	if (!reply)
		return NULL;

	dev_path = device_get_path(device);

	dbus_message_append_args(reply,
				DBUS_TYPE_OBJECT_PATH, &dev_path,
				DBUS_TYPE_INVALID);

	return reply;
}

static void agent_removed(struct agent *agent, struct btd_adapter *adapter)
{
	adapter_ops->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 agent *agent;
	struct btd_adapter *adapter = data;
	uint8_t cap;

	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
			DBUS_TYPE_STRING, &capability, DBUS_TYPE_INVALID))
		return NULL;

	if (adapter->agent)
		return btd_error_already_exists(msg);

	cap = parse_io_capability(capability);
	if (cap == IO_CAPABILITY_INVALID)
		return btd_error_invalid_args(msg);

	name = dbus_message_get_sender(msg);

	agent = agent_create(adapter, name, path, cap,
				(agent_remove_cb) agent_removed, adapter);
	if (!agent)
		return btd_error_failed(msg, "Failed to create a new agent");

	adapter->agent = agent;

	DBG("Agent registered for hci%d at %s:%s", adapter->dev_id, name,
			path);

	adapter_ops->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 NULL;

	name = dbus_message_get_sender(msg);

	if (!adapter->agent || !agent_matches(adapter->agent, name, path))
		return btd_error_does_not_exist(msg);

	agent_free(adapter->agent);
	adapter->agent = NULL;

	return dbus_message_new_method_return(msg);
}

static sdp_record_t *create_rfcomm_record(struct btd_adapter *adapter,
					const char *name, uuid_t uuid, uint8_t channel)
{
	uuid_t root_uuid, l2cap_uuid, rfcomm_uuid;
	sdp_list_t *svclass, *root, *proto;
	sdp_record_t *record;

	record = sdp_record_alloc();
	if (!record)
		return NULL;

	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
	root = sdp_list_append(NULL, &root_uuid);
	sdp_set_browse_groups(record, root);

	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
	proto = sdp_list_append(NULL, sdp_list_append(NULL, &l2cap_uuid));

	sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
	proto = sdp_list_append(proto, sdp_list_append(
			sdp_list_append(NULL, &rfcomm_uuid),
			sdp_data_alloc(SDP_UINT8, &channel)));

	sdp_set_access_protos(record, sdp_list_append(NULL, proto));

	svclass = sdp_list_append(NULL, &uuid);
	sdp_set_service_classes(record, svclass);

	sdp_set_info_attr(record, name, NULL, NULL);

	return record;
}

static DBusMessage *add_rfcomm_service_record(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	uuid_t uuid;
	const char *name;
	uint8_t channel;
	uint32_t *uuid_p;
	uint32_t uuid_net[4];   // network order
	uint64_t uuid_host[2];  // host
	sdp_record_t *record;
	struct btd_adapter *adapter = data;

	DBusMessage *reply;

	if (!dbus_message_get_args(msg, NULL,
			DBUS_TYPE_STRING, &name,
			DBUS_TYPE_UINT64, &uuid_host[0],
			DBUS_TYPE_UINT64, &uuid_host[1],
			DBUS_TYPE_UINT16, &channel,
			DBUS_TYPE_INVALID))
		return btd_error_invalid_args(msg);

	uuid_p = (uint32_t *)uuid_host;
	uuid_net[1] = htonl(*uuid_p++);
	uuid_net[0] = htonl(*uuid_p++);
	uuid_net[3] = htonl(*uuid_p++);
	uuid_net[2] = htonl(*uuid_p++);

	sdp_uuid128_create(&uuid, (void *)uuid_net);

	record = create_rfcomm_record(adapter, name, uuid, channel);

	if (!record)
		return g_dbus_create_error(msg,
				ERROR_INTERFACE ".Failed",
				"Failed to create sdp record");

	if (add_record_to_server(&adapter->bdaddr, record))
		return g_dbus_create_error(msg,
				ERROR_INTERFACE ".Failed",
				"Failed to register sdp record");

	reply = dbus_message_new_method_return(msg);
	dbus_message_append_args(reply,
			DBUS_TYPE_UINT32, &record->handle,
			DBUS_TYPE_INVALID);

	return reply;
}

static DBusMessage *remove_service_record(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	struct btd_adapter *adapter = data;
	dbus_uint32_t handle;

	if (!dbus_message_get_args(msg, NULL,
			DBUS_TYPE_UINT32, &handle,
			DBUS_TYPE_INVALID))
		return btd_error_invalid_args(msg);

	if (remove_record_from_server(handle))
		return g_dbus_create_error(msg,
				ERROR_INTERFACE ".Failed",
				"Failed to remove sdp record");

	return dbus_message_new_method_return(msg);
}

static int add_headset_ag_record(struct btd_adapter* adapter)
{
	sdp_list_t *svclass_id, *pfseq, *apseq, *root;
	uuid_t root_uuid, svclass_uuid, ga_svclass_uuid, l2cap_uuid, rfcomm_uuid;
	sdp_profile_desc_t profile;
	sdp_list_t *aproto, *proto[2];
	sdp_record_t *record;
	uint8_t u8 = 11;
	sdp_data_t *channel;
	uint8_t netid = 0x01; // ???? profile document
	sdp_data_t *network = sdp_data_alloc(SDP_UINT8, &netid);
	int ret = 0;

	record = sdp_record_alloc();
	if (!record) return -1;

	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
	root = sdp_list_append(0, &root_uuid);
	sdp_set_browse_groups(record, root);

	sdp_uuid16_create(&svclass_uuid, HEADSET_AGW_SVCLASS_ID);
	svclass_id = sdp_list_append(0, &svclass_uuid);
	sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID);
	svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid);
	sdp_set_service_classes(record, svclass_id);

	sdp_uuid16_create(&profile.uuid, HEADSET_PROFILE_ID);
	profile.version = 0x0100;
	pfseq = sdp_list_append(0, &profile);
	sdp_set_profile_descs(record, pfseq);

	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
	proto[0] = sdp_list_append(0, &l2cap_uuid);
	apseq = sdp_list_append(0, proto[0]);

	sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
	proto[1] = sdp_list_append(0, &rfcomm_uuid);
	channel = sdp_data_alloc(SDP_UINT8, &u8);
	proto[1] = sdp_list_append(proto[1], channel);
	apseq = sdp_list_append(apseq, proto[1]);

	aproto = sdp_list_append(0, apseq);
	sdp_set_access_protos(record, aproto);

	sdp_set_info_attr(record, "Voice Gateway", 0, 0);

	if (add_record_to_server(&adapter->bdaddr, record) < 0)
		ret = -1;

	sdp_data_free(channel);
	sdp_list_free(proto[0], 0);
	sdp_list_free(proto[1], 0);
	sdp_list_free(apseq, 0);
	sdp_list_free(aproto, 0);

	if (!ret)
		return record->handle;
	return ret;
}

static int add_handsfree_ag_record(struct btd_adapter* adapter) {
	sdp_list_t *svclass_id, *pfseq, *apseq, *root;
	uuid_t root_uuid, svclass_uuid, ga_svclass_uuid, l2cap_uuid, rfcomm_uuid;
	sdp_profile_desc_t profile;
	sdp_list_t *aproto, *proto[2];
	sdp_record_t *record;
	uint8_t u8 = 10;
	uint16_t u16 = 0x17;
#ifdef ANDROID
	u16 = 0x07;
#endif
	sdp_data_t *channel, *features;
	uint8_t netid = 0x01; // ???? profile document
	sdp_data_t *network = sdp_data_alloc(SDP_UINT8, &netid);
	int ret = 0;

	record = sdp_record_alloc();
	if (!record) return -1;

	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
	root = sdp_list_append(0, &root_uuid);
	sdp_set_browse_groups(record, root);

	sdp_uuid16_create(&svclass_uuid, HANDSFREE_AGW_SVCLASS_ID);
	svclass_id = sdp_list_append(0, &svclass_uuid);
	sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID);
	svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid);
	sdp_set_service_classes(record, svclass_id);

	sdp_uuid16_create(&profile.uuid, HANDSFREE_PROFILE_ID);
	profile.version = 0x0105;
	pfseq = sdp_list_append(0, &profile);
	sdp_set_profile_descs(record, pfseq);

	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
	proto[0] = sdp_list_append(0, &l2cap_uuid);
	apseq = sdp_list_append(0, proto[0]);

	sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
	proto[1] = sdp_list_append(0, &rfcomm_uuid);
	channel = sdp_data_alloc(SDP_UINT8, &u8);
	proto[1] = sdp_list_append(proto[1], channel);
	apseq = sdp_list_append(apseq, proto[1]);

	features = sdp_data_alloc(SDP_UINT16, &u16);
	sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);

	aproto = sdp_list_append(0, apseq);
	sdp_set_access_protos(record, aproto);

	sdp_set_info_attr(record, "Voice Gateway", 0, 0);

	sdp_attr_add(record, SDP_ATTR_EXTERNAL_NETWORK, network);

	if (add_record_to_server(&adapter->bdaddr, record) < 0)
		ret = -1;

	sdp_data_free(channel);
	sdp_list_free(proto[0], 0);
	sdp_list_free(proto[1], 0);
	sdp_list_free(apseq, 0);
        sdp_list_free(aproto, 0);

	if (!ret)
		return record->handle;
	return ret;
}

static int add_pbap_pse_record(struct btd_adapter *adapter)
{
	sdp_list_t *svclass_id, *pfseq, *apseq, *root;
	uuid_t root_uuid, pbap_uuid, l2cap_uuid, rfcomm_uuid, obex_uuid;
	sdp_profile_desc_t profile[1];
	sdp_list_t *aproto, *proto[3];
	sdp_record_t *record;
	uint8_t u8 = 19;
	sdp_data_t *channel;
	uint8_t formats[] = {0x01};
	uint8_t dtd = SDP_UINT8;
	sdp_data_t *sflist;
	int ret = 0;

	record = sdp_record_alloc();
	if (!record) return -1;

	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
	root = sdp_list_append(0, &root_uuid);
	sdp_set_browse_groups(record, root);

	sdp_uuid16_create(&pbap_uuid, PBAP_PSE_SVCLASS_ID);
	svclass_id = sdp_list_append(0, &pbap_uuid);
	sdp_set_service_classes(record, svclass_id);

	sdp_uuid16_create(&profile[0].uuid, PBAP_PROFILE_ID);
	profile[0].version = 0x0100;
	pfseq = sdp_list_append(0, profile);
	sdp_set_profile_descs(record, pfseq);

	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
	proto[0] = sdp_list_append(0, &l2cap_uuid);
	apseq = sdp_list_append(0, proto[0]);

	sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
	proto[1] = sdp_list_append(0, &rfcomm_uuid);
	channel = sdp_data_alloc(SDP_UINT8, &u8);
	proto[1] = sdp_list_append(proto[1], channel);
	apseq = sdp_list_append(apseq, proto[1]);

	sdp_uuid16_create(&obex_uuid, OBEX_UUID);
	proto[2] = sdp_list_append(0, &obex_uuid);
	apseq = sdp_list_append(apseq, proto[2]);

	aproto = sdp_list_append(0, apseq);
	sdp_set_access_protos(record, aproto);

	sflist = sdp_data_alloc(dtd,formats);
	sdp_attr_add(record, SDP_ATTR_SUPPORTED_REPOSITORIES, sflist);

	sdp_set_info_attr(record, "OBEX Phonebook Access Server", 0, 0);

	if (add_record_to_server(&adapter->bdaddr, record) < 0)
		ret = -1;

	sdp_data_free(channel);
	sdp_list_free(proto[0], 0);
	sdp_list_free(proto[1], 0);
	sdp_list_free(proto[2], 0);
	sdp_list_free(apseq, 0);
	sdp_list_free(aproto, 0);

	if (!ret)
		return record->handle;
	return ret;
}

static int add_opush_record(struct btd_adapter *adapter)
{
	sdp_list_t *svclass_id, *pfseq, *apseq, *root;
	uuid_t root_uuid, opush_uuid, l2cap_uuid, rfcomm_uuid, obex_uuid;
	sdp_profile_desc_t profile[1];
	sdp_list_t *aproto, *proto[3];
	sdp_record_t *record;
	uint8_t u8 = 12;
	sdp_data_t *channel;
#ifdef ANDROID
	uint8_t formats[] = { 0x01, 0x02, 0xff };
#else
	uint8_t formats[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0xff };
#endif
	void *dtds[sizeof(formats)], *values[sizeof(formats)];
	unsigned int i;
	uint8_t dtd = SDP_UINT8;
	sdp_data_t *sflist;
	int ret = 0;

	record = sdp_record_alloc();
	if (!record) return -1;

	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
	root = sdp_list_append(0, &root_uuid);
	sdp_set_browse_groups(record, root);

	sdp_uuid16_create(&opush_uuid, OBEX_OBJPUSH_SVCLASS_ID);
	svclass_id = sdp_list_append(0, &opush_uuid);
	sdp_set_service_classes(record, svclass_id);

	sdp_uuid16_create(&profile[0].uuid, OBEX_OBJPUSH_PROFILE_ID);
	profile[0].version = 0x0100;
	pfseq = sdp_list_append(0, profile);
	sdp_set_profile_descs(record, pfseq);

	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
	proto[0] = sdp_list_append(0, &l2cap_uuid);
	apseq = sdp_list_append(0, proto[0]);

	sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
	proto[1] = sdp_list_append(0, &rfcomm_uuid);
	channel = sdp_data_alloc(SDP_UINT8, &u8);
	proto[1] = sdp_list_append(proto[1], channel);
	apseq = sdp_list_append(apseq, proto[1]);

	sdp_uuid16_create(&obex_uuid, OBEX_UUID);
	proto[2] = sdp_list_append(0, &obex_uuid);
	apseq = sdp_list_append(apseq, proto[2]);

	aproto = sdp_list_append(0, apseq);
	sdp_set_access_protos(record, aproto);

	for (i = 0; i < sizeof(formats); i++) {
		dtds[i] = &dtd;
		values[i] = &formats[i];
	}
	sflist = sdp_seq_alloc(dtds, values, sizeof(formats));
	sdp_attr_add(record, SDP_ATTR_SUPPORTED_FORMATS_LIST, sflist);

	sdp_set_info_attr(record, "OBEX Object Push", 0, 0);

	if (add_record_to_server(&adapter->bdaddr, record) < 0)
		ret = -1;

	sdp_data_free(channel);
	sdp_list_free(proto[0], 0);
	sdp_list_free(proto[1], 0);
	sdp_list_free(proto[2], 0);
	sdp_list_free(apseq, 0);
	sdp_list_free(aproto, 0);

	if (!ret)
		return record->handle;
	return ret;
}

static DBusMessage *add_reserved_service_records(DBusConnection *conn,
						DBusMessage *msg, void *data) {
	DBusMessage *reply;
	struct btd_adapter *adapter = data;
	uint32_t *svc_classes;
	uint32_t *handles;
	uint32_t len, i;
	int ret;

	if (dbus_message_get_args(msg, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32,
				&svc_classes, &len, DBUS_TYPE_INVALID) == FALSE)
		return btd_error_invalid_args(msg);

	handles = g_malloc0(sizeof(uint32_t) * len);

	for (i = 0; i < len; i++) {
		switch (svc_classes[i]) {
			case PBAP_PSE_SVCLASS_ID:
				ret = add_pbap_pse_record(adapter);
				break;
			case HEADSET_AGW_SVCLASS_ID:
				ret = add_headset_ag_record(adapter);
				break;
			case HANDSFREE_AGW_SVCLASS_ID:
				ret = add_handsfree_ag_record(adapter);
				break;
			case OBEX_OBJPUSH_SVCLASS_ID:
				ret = add_opush_record(adapter);
				break;
		}
		if (ret < 0) {
			g_free(handles);
			return g_dbus_create_error(msg,
				ERROR_INTERFACE ".Failed", "Failed to add sdp record");
		} else
			handles[i] = ret;
	}

	reply = dbus_message_new_method_return(msg);
	dbus_message_append_args(reply, DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32,
					&handles, len, DBUS_TYPE_INVALID);

	g_free(handles);
	return reply;
}

static DBusMessage *remove_reserved_service_records(DBusConnection *conn,
							DBusMessage *msg, void *data) {
	uint32_t *handles;
	uint32_t len, i;
	
	if (dbus_message_get_args(msg, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32,
				&handles, &len, DBUS_TYPE_INVALID) == FALSE)
		return btd_error_invalid_args(msg);

	for (i = 0; i < len; i++)
		if (remove_record_from_server(handles[i]))
			return g_dbus_create_error(msg,
					ERROR_INTERFACE ".Failed", "Failed to remove sdp record");

	return dbus_message_new_method_return(msg);
}

static DBusMessage *set_link_timeout(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	struct btd_adapter *adapter = data;
        struct btd_device *device;
	const char *path;
	GSList *l;
	uint32_t num_slots;
        int dd, err;
	bdaddr_t bdaddr;

	if (!dbus_message_get_args(msg, NULL,
			DBUS_TYPE_OBJECT_PATH, &path,
			DBUS_TYPE_UINT32, &num_slots,
			DBUS_TYPE_INVALID))
		return btd_error_invalid_args(msg);

        l = g_slist_find_custom(adapter->devices,
                        path, (GCompareFunc) device_path_cmp);
        if (!l)
                return g_dbus_create_error(msg,
                                ERROR_INTERFACE ".DoesNotExist",
                                "Device does not exist");
	device_get_address(l->data, &bdaddr);

	err = adapter_ops->set_link_timeout(adapter->dev_id, &bdaddr,
			num_slots);
	if (err < 0)
		return btd_error_failed(msg, strerror(-err));

	return dbus_message_new_method_return(msg);
}

static GDBusMethodTable adapter_methods[] = {
	{ "GetProperties",	"",	"a{sv}",get_properties		},
	{ "SetProperty",	"sv",	"",	set_property,
						G_DBUS_METHOD_FLAG_ASYNC},
	{ "RequestSession",	"",	"",	request_session,
						G_DBUS_METHOD_FLAG_ASYNC},
	{ "ReleaseSession",	"",	"",	release_session		},
	{ "StartDiscovery",	"",	"",	adapter_start_discovery },
	{ "StopDiscovery",	"",	"",	adapter_stop_discovery,
						G_DBUS_METHOD_FLAG_ASYNC},
	{ "ListDevices",	"",	"ao",	list_devices,
						G_DBUS_METHOD_FLAG_DEPRECATED},
	{ "CreateDevice",	"s",	"o",	create_device,
						G_DBUS_METHOD_FLAG_ASYNC},
	{ "CreatePairedDevice",	"sos",	"o",	create_paired_device,
						G_DBUS_METHOD_FLAG_ASYNC},
	{ "CancelDeviceCreation","s",	"",	cancel_device_creation,
						G_DBUS_METHOD_FLAG_ASYNC},
	{ "RemoveDevice",	"o",	"",	remove_device,
						G_DBUS_METHOD_FLAG_ASYNC},
	{ "FindDevice",		"s",	"o",	find_device		},
	{ "RegisterAgent",	"os",	"",	register_agent		},
	{ "UnregisterAgent",	"o",	"",	unregister_agent	},
	{ "AddRfcommServiceRecord",	"sttq",	"u",	add_rfcomm_service_record },
	{ "RemoveServiceRecord",	"u",	"",	remove_service_record },
	{ "SetLinkTimeout",	"ou",	"",	set_link_timeout	},
	{ "AddReservedServiceRecords",   "au",    "au",    add_reserved_service_records  },
	{ "RemoveReservedServiceRecords", "au",    "",	remove_reserved_service_records  },
	{ }
};

static GDBusSignalTable adapter_signals[] = {
	{ "PropertyChanged",		"sv"		},
	{ "DeviceCreated",		"o"		},
	{ "DeviceRemoved",		"o"		},
	{ "DeviceFound",		"sa{sv}"	},
	{ "DeviceDisappeared",		"s"		},
	{ }
};

static void create_stored_device_from_profiles(char *key, char *value,
						void *user_data)
{
	struct btd_adapter *adapter = user_data;
	GSList *list, *uuids = bt_string2list(value);
	struct btd_device *device;

	if (g_slist_find_custom(adapter->devices,
				key, (GCompareFunc) device_address_cmp))
		return;

	device = device_create(connection, adapter, key, DEVICE_TYPE_BREDR);
	if (!device)
		return;

	device_set_temporary(device, FALSE);
	adapter->devices = g_slist_append(adapter->devices, device);

	device_probe_drivers(device, uuids);
	list = device_services_from_record(device, uuids);
	if (list)
		device_register_services(connection, device, list, ATT_PSM);

	g_slist_foreach(uuids, (GFunc) g_free, NULL);
	g_slist_free(uuids);
}

struct adapter_keys {
	struct btd_adapter *adapter;
	GSList *keys;
};

static struct link_key_info *get_key_info(const char *addr, const char *value)
{
	struct link_key_info *info;
	char tmp[3];
	long int l;
	int i;

	if (strlen(value) < 36) {
		error("Unexpectedly short (%zu) link key line", strlen(value));
		return NULL;
	}

	info = g_new0(struct link_key_info, 1);

	str2ba(addr, &info->bdaddr);

	memset(tmp, 0, sizeof(tmp));

	for (i = 0; i < 16; i++) {
		memcpy(tmp, value + (i * 2), 2);
		info->key[i] = (uint8_t) strtol(tmp, NULL, 16);
	}

	memcpy(tmp, value + 33, 2);
	info->type = (uint8_t) strtol(tmp, NULL, 10);

	memcpy(tmp, value + 35, 2);
	l = strtol(tmp, NULL, 10);
	if (l < 0)
		l = 0;
	info->pin_len = l;

	return info;
}

static void create_stored_device_from_linkkeys(char *key, char *value,
							void *user_data)
{
	struct adapter_keys *keys = user_data;
	struct btd_adapter *adapter = keys->adapter;
	struct btd_device *device;
	struct link_key_info *info;

	info = get_key_info(key, value);
	if (info)
		keys->keys = g_slist_append(keys->keys, info);

	if (g_slist_find_custom(adapter->devices, key,
					(GCompareFunc) device_address_cmp))
		return;

	device = device_create(connection, adapter, key, DEVICE_TYPE_BREDR);
	if (device) {
		device_set_temporary(device, FALSE);
		adapter->devices = g_slist_append(adapter->devices, device);
	}
}

static void create_stored_device_from_blocked(char *key, char *value,
							void *user_data)
{
	struct btd_adapter *adapter = user_data;
	struct btd_device *device;

	if (g_slist_find_custom(adapter->devices,
				key, (GCompareFunc) device_address_cmp))
		return;

	device = device_create(connection, adapter, key, DEVICE_TYPE_BREDR);
	if (device) {
		device_set_temporary(device, FALSE);
		adapter->devices = g_slist_append(adapter->devices, device);
	}
}

static void create_stored_device_from_types(char *key, char *value,
							void *user_data)
{
	GSList *l;
	struct btd_adapter *adapter = user_data;
	struct btd_device *device;
	uint8_t type;

	type = strtol(value, NULL, 16);

	l = g_slist_find_custom(adapter->devices,
				key, (GCompareFunc) device_address_cmp);
	if (l) {
		device = l->data;
		device_set_type(device, type);
		return;
	}

	device = device_create(connection, adapter, key, type);
	if (device) {
		device_set_temporary(device, FALSE);
		adapter->devices = g_slist_append(adapter->devices, device);
	}
}

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 att_primary *prim;
		int ret;

		prim = g_new0(struct att_primary, 1);

		ret = sscanf(services[i], "%04hX#%04hX#%s", &prim->start,
							&prim->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_primary(char *key, char *value,
							void *user_data)
{
	struct btd_adapter *adapter = user_data;
	struct btd_device *device;
	GSList *services, *uuids, *l;

	if (g_slist_find_custom(adapter->devices,
			key, (GCompareFunc) device_address_cmp))
		return;

	device = device_create(connection, adapter, key, DEVICE_TYPE_LE);
	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 att_primary *prim = l->data;
		uuids = g_slist_append(uuids, prim->uuid);
	}

	device_probe_drivers(device, uuids);
	device_register_services(connection, device, services, -1);

	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 };
	int err;

	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, "primary");
	textfile_foreach(filename, create_stored_device_from_primary,
								adapter);

	create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "linkkeys");
	textfile_foreach(filename, create_stored_device_from_linkkeys, &keys);

	err = adapter_ops->load_keys(adapter->dev_id, keys.keys,
							main_opts.debug_keys);
	if (err < 0) {
		error("Unable to load keys to adapter_ops: %s (%d)",
							strerror(-err), -err);
		g_slist_foreach(keys.keys, (GFunc) g_free, NULL);
		g_slist_free(keys.keys);
	}

	create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "blocked");
	textfile_foreach(filename, create_stored_device_from_blocked, adapter);

	create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "types");
	textfile_foreach(filename, create_stored_device_from_types, adapter);
}

int btd_adapter_block_address(struct btd_adapter *adapter, bdaddr_t *bdaddr)
{
	return adapter_ops->block_device(adapter->dev_id, bdaddr);
}

int btd_adapter_unblock_address(struct btd_adapter *adapter, bdaddr_t *bdaddr)
{
	return adapter_ops->unblock_device(adapter->dev_id, bdaddr);
}

static void clear_blocked(struct btd_adapter *adapter)
{
	int err;

	err = adapter_ops->unblock_device(adapter->dev_id, BDADDR_ANY);
	if (err < 0)
		error("Clearing blocked list failed: %s (%d)",
						strerror(-err), -err);
}

static void probe_driver(struct btd_adapter *adapter, gpointer user_data)
{
	struct btd_adapter_driver *driver = user_data;
	int err;

	if (!adapter->up)
		return;

	if (driver->probe == NULL)
		return;

	err = driver->probe(adapter);
	if (err < 0) {
		error("%s: %s (%d)", driver->name, strerror(-err), -err);
		return;
	}

	adapter->loaded_drivers = g_slist_prepend(adapter->loaded_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 load_connections(struct btd_adapter *adapter)
{
	GSList *l, *conns;
	int err;

	err = adapter_ops->get_conn_list(adapter->dev_id, &conns);
	if (err < 0) {
		error("Unable to fetch existing connections: %s (%d)",
							strerror(-err), -err);
		return;
	}

	for (l = conns; l != NULL; l = g_slist_next(l)) {
		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(connection, adapter, address);
		if (device)
			adapter_add_connection(adapter, device);
	}

	g_slist_foreach(conns, (GFunc) g_free, NULL);
	g_slist_free(conns);
}

static int get_discoverable_timeout(const char *src)
{
	int timeout;

	if (read_discoverable_timeout(src, &timeout) == 0)
		return timeout;

	return main_opts.discovto;
}

static int get_pairable_timeout(const char *src)
{
	int timeout;

	if (read_pairable_timeout(src, &timeout) == 0)
		return timeout;

	return main_opts.pairto;
}

static void call_adapter_powered_callbacks(struct btd_adapter *adapter,
						gboolean powered)
{
	GSList *l;

	for (l = adapter->powered_callbacks; l; l = l->next) {
		btd_adapter_powered_cb cb = l->data;

		cb(adapter, powered);
       }
}

static void emit_device_disappeared(gpointer data, gpointer user_data)
{
	struct remote_dev_info *dev = data;
	struct btd_adapter *adapter = user_data;
	char address[18];
	const char *paddr = address;

	ba2str(&dev->bdaddr, address);

	g_dbus_emit_signal(connection, adapter->path,
			ADAPTER_INTERFACE, "DeviceDisappeared",
			DBUS_TYPE_STRING, &paddr,
			DBUS_TYPE_INVALID);

	adapter->found_devices = g_slist_remove(adapter->found_devices, dev);
}

static void update_oor_devices(struct btd_adapter *adapter)
{
	g_slist_foreach(adapter->oor_devices, emit_device_disappeared, adapter);
	g_slist_foreach(adapter->oor_devices, (GFunc) dev_info_free, NULL);
	g_slist_free(adapter->oor_devices);
	adapter->oor_devices =  g_slist_copy(adapter->found_devices);
}

void btd_adapter_get_mode(struct btd_adapter *adapter, uint8_t *mode,
					uint8_t *on_mode, gboolean *pairable)
{
	char str[14], address[18];

	ba2str(&adapter->bdaddr, address);

	if (mode) {
		if (main_opts.remember_powered == FALSE)
			*mode = main_opts.mode;
		else if (read_device_mode(address, str, sizeof(str)) == 0)
			*mode = get_mode(&adapter->bdaddr, str);
		else
			*mode = main_opts.mode;
	}

	if (on_mode) {
		if (main_opts.remember_powered == FALSE)
			*on_mode = get_mode(&adapter->bdaddr, "on");
		else if (read_on_mode(address, str, sizeof(str)) == 0)
			*on_mode = get_mode(&adapter->bdaddr, str);
		else
			*on_mode = main_opts.mode;
	}

	if (pairable)
		*pairable = adapter->pairable;
}

void btd_adapter_start(struct btd_adapter *adapter)
{
	char address[18];
	uint8_t cls[3];
	gboolean powered;

	ba2str(&adapter->bdaddr, address);

	adapter->dev_class = 0;
	adapter->off_requested = FALSE;
	adapter->up = TRUE;
	adapter->discov_timeout = get_discoverable_timeout(address);
	adapter->pairable_timeout = get_pairable_timeout(address);
	adapter->state = STATE_IDLE;
	adapter->mode = MODE_CONNECTABLE;

	if (main_opts.le)
		adapter_ops->enable_le(adapter->dev_id);

	adapter_ops->set_name(adapter->dev_id, adapter->name);

	if (read_local_class(&adapter->bdaddr, cls) < 0) {
		uint32_t class = htobl(main_opts.class);
		memcpy(cls, &class, 3);
	}

	btd_adapter_set_class(adapter, cls[1], cls[0]);

	powered = TRUE;
	emit_property_changed(connection, adapter->path,
					ADAPTER_INTERFACE, "Powered",
					DBUS_TYPE_BOOLEAN, &powered);

	call_adapter_powered_callbacks(adapter, TRUE);

	adapter_ops->disable_cod_cache(adapter->dev_id);

	info("Adapter %s has been enabled", adapter->path);
}

static void reply_pending_requests(struct btd_adapter *adapter)
{
	GSList *l;

	if (!adapter)
		return;

	/* pending bonding */
	for (l = adapter->devices; l; l = l->next) {
		struct btd_device *device = l->data;

		if (device_is_bonding(device, NULL))
			device_cancel_bonding(device,
						HCI_OE_USER_ENDED_CONNECTION);
	}
}

static void 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 unload_drivers(struct btd_adapter *adapter)
{
	g_slist_foreach(adapter->loaded_drivers, remove_driver, adapter);
	g_slist_free(adapter->loaded_drivers);
	adapter->loaded_drivers = NULL;
}

static void set_mode_complete(struct btd_adapter *adapter)
{
	struct session_req *pending;
	const char *modestr;
	int err;

	DBG("");

	/*
	 * g_slist_free is not called after g_slist_foreach because the list is
	 * updated using g_slist_remove in session_remove which is called by
         * session_free, which is called for each element by g_slist_foreach.
	 */
	if (adapter->mode == MODE_OFF)
		g_slist_foreach(adapter->mode_sessions, (GFunc) session_free,
									NULL);

	if (adapter->pending_mode == NULL)
		return;

	pending = adapter->pending_mode;
	adapter->pending_mode = NULL;

	err = (pending->mode != adapter->mode) ? -EINVAL : 0;

	if (pending->msg != NULL) {
		DBusMessage *msg = pending->msg;
		DBusMessage *reply;

		if (err < 0)
			reply = btd_error_failed(msg, strerror(-err));
		else {
			if (strcmp(dbus_message_get_member(msg),
						"SetProperty") == 0)
				adapter->global_mode = adapter->mode;
			reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
		}

		g_dbus_send_message(connection, reply);
	}

	modestr = mode2str(adapter->mode);

	DBG("%s", modestr);

	/* restore if the mode doesn't matches the pending */
	if (err != 0) {
		write_device_mode(&adapter->bdaddr, modestr);
		error("unable to set mode: %s", mode2str(pending->mode));
	}

	session_unref(pending);
}

int btd_adapter_stop(struct btd_adapter *adapter)
{
	gboolean powered, discoverable, pairable;

	/* cancel pending timeout */
	if (adapter->discov_timeout_id) {
		g_source_remove(adapter->discov_timeout_id);
		adapter->discov_timeout_id = 0;
	}

	/* check pending requests */
	reply_pending_requests(adapter);

	stop_discovery(adapter);

	if (adapter->disc_sessions) {
		g_slist_foreach(adapter->disc_sessions, (GFunc) session_free,
				NULL);
		g_slist_free(adapter->disc_sessions);
		adapter->disc_sessions = NULL;
	}

	while (adapter->connections) {
		struct btd_device *device = adapter->connections->data;
		adapter_remove_connection(adapter, device);
	}

	if (adapter->scan_mode == (SCAN_PAGE | SCAN_INQUIRY)) {
		discoverable = FALSE;
		emit_property_changed(connection, adapter->path,
					ADAPTER_INTERFACE, "Discoverable",
					DBUS_TYPE_BOOLEAN, &discoverable);
	}

	if ((adapter->scan_mode & SCAN_PAGE) && adapter->pairable == TRUE) {
		pairable = FALSE;
		emit_property_changed(connection, adapter->path,
					ADAPTER_INTERFACE, "Pairable",
					DBUS_TYPE_BOOLEAN, &pairable);
	}

	powered = FALSE;
	emit_property_changed(connection, adapter->path, ADAPTER_INTERFACE,
				"Powered", DBUS_TYPE_BOOLEAN, &powered);

	/* Duplicately publish the UUIDs to make sure the upper layers know */
	adapter_emit_uuids_updated(adapter);

	adapter->up = 0;
	adapter->scan_mode = SCAN_DISABLED;
	adapter->mode = MODE_OFF;
	adapter->state = STATE_IDLE;
	adapter->off_requested = FALSE;
	adapter->name_stored = FALSE;

	call_adapter_powered_callbacks(adapter, FALSE);

	info("Adapter %s has been disabled", adapter->path);

	set_mode_complete(adapter);

	return 0;
}

static void adapter_free(gpointer user_data)
{
	struct btd_adapter *adapter = user_data;

	agent_free(adapter->agent);
	adapter->agent = NULL;

	DBG("%p", adapter);

	if (adapter->auth_idle_id)
		g_source_remove(adapter->auth_idle_id);

	sdp_list_free(adapter->services, NULL);

	g_slist_foreach(adapter->found_devices, (GFunc) dev_info_free, NULL);
	g_slist_free(adapter->found_devices);

	g_slist_free(adapter->oor_devices);

	g_free(adapter->path);
	g_free(adapter);
}

struct btd_adapter *btd_adapter_ref(struct btd_adapter *adapter)
{
	adapter->ref++;

	DBG("%p: ref=%d", adapter, adapter->ref);

	return adapter;
}

void btd_adapter_unref(struct btd_adapter *adapter)
{
	gchar *path;

	adapter->ref--;

	DBG("%p: ref=%d", adapter, adapter->ref);

	if (adapter->ref > 0)
		return;

	path = g_strdup(adapter->path);

	g_dbus_unregister_interface(connection, path, ADAPTER_INTERFACE);

	g_free(path);
}

gboolean adapter_init(struct btd_adapter *adapter)
{
	int err;

	/* adapter_ops makes sure that newly registered adapters always
	 * start off as powered */
	adapter->up = TRUE;

	adapter_ops->read_bdaddr(adapter->dev_id, &adapter->bdaddr);

	if (bacmp(&adapter->bdaddr, BDADDR_ANY) == 0) {
		error("No address available for hci%d", adapter->dev_id);
		return FALSE;
	}

	err = adapter_ops->read_local_features(adapter->dev_id,
							adapter->features);
	if (err < 0) {
		error("Can't read features for hci%d: %s (%d)",
					adapter->dev_id, strerror(-err), -err);
		return FALSE;
	}

	if (read_local_name(&adapter->bdaddr, adapter->name) < 0)
		expand_name(adapter->name, MAX_NAME_LENGTH, main_opts.name,
							adapter->dev_id);

	if (main_opts.attrib_server)
		attrib_gap_set(GATT_CHARAC_DEVICE_NAME,
			(const uint8_t *) adapter->name, strlen(adapter->name));

	sdp_init_services_list(&adapter->bdaddr);
	load_drivers(adapter);
	clear_blocked(adapter);
	load_devices(adapter);

	/* Set pairable mode */
	if (read_device_pairable(&adapter->bdaddr, &adapter->pairable) < 0)
		adapter->pairable = TRUE;

	/* retrieve the active connections: address the scenario where
	 * the are active connections before the daemon've started */
	load_connections(adapter);

	adapter->initialized = TRUE;

	return TRUE;
}

struct btd_adapter *adapter_create(DBusConnection *conn, int id)
{
	char path[MAX_PATH_LENGTH];
	struct btd_adapter *adapter;
	const char *base_path = manager_get_base_path();

	if (!connection)
		connection = conn;

	adapter = g_try_new0(struct btd_adapter, 1);
	if (!adapter) {
		error("adapter_create: failed to alloc memory for hci%d", id);
		return NULL;
	}

	adapter->dev_id = id;

	snprintf(path, sizeof(path), "%s/hci%d", base_path, id);
	adapter->path = g_strdup(path);

	if (!g_dbus_register_interface(conn, path, ADAPTER_INTERFACE,
					adapter_methods, adapter_signals, NULL,
					adapter, adapter_free)) {
		error("Adapter interface init failed on path %s", path);
		adapter_free(adapter);
		return NULL;
	}

	return btd_adapter_ref(adapter);
}

void adapter_remove(struct btd_adapter *adapter)
{
	GSList *l;

	DBG("Removing adapter %s", adapter->path);

	for (l = adapter->devices; l; l = l->next)
		device_remove(l->data, FALSE);
	g_slist_free(adapter->devices);

	unload_drivers(adapter);

	/* Return adapter to down state if it was not up on init */
	adapter_ops->restore_powered(adapter->dev_id);

	btd_adapter_unref(adapter);
}

uint16_t adapter_get_dev_id(struct btd_adapter *adapter)
{
	return adapter->dev_id;
}

const gchar *adapter_get_path(struct btd_adapter *adapter)
{
	if (!adapter)
		return NULL;

	return adapter->path;
}

void adapter_get_address(struct btd_adapter *adapter, bdaddr_t *bdaddr)
{
	bacpy(bdaddr, &adapter->bdaddr);
}

static inline void suspend_discovery(struct btd_adapter *adapter)
{
	if (adapter->state != STATE_SUSPENDED)
		return;

	if (adapter->oor_devices) {
		g_slist_free(adapter->oor_devices);
		adapter->oor_devices = NULL;
	}

	if (adapter->scheduler_id) {
		g_source_remove(adapter->scheduler_id);
		adapter->scheduler_id = 0;
	}

	adapter_ops->stop_discovery(adapter->dev_id);
}

static inline void resolve_names(struct btd_adapter *adapter)
{
	int err;

	if (adapter->state != STATE_RESOLVNAME)
		return;

	err = adapter_resolve_names(adapter);
	if (err < 0)
		adapter_set_state(adapter, STATE_IDLE);
}

void adapter_set_state(struct btd_adapter *adapter, int state)
{
	const char *path = adapter->path;
	gboolean discov_active;

	if (adapter->state == state)
		return;

	adapter->state = state;

	DBG("hci%d: new state %d", adapter->dev_id, adapter->state);

	switch (adapter->state) {
	case STATE_IDLE:
		update_oor_devices(adapter);

		discov_active = FALSE;
		emit_property_changed(connection, path,
					ADAPTER_INTERFACE, "Discovering",
					DBUS_TYPE_BOOLEAN, &discov_active);

		if (adapter_has_discov_sessions(adapter)) {
			adapter->scheduler_id = g_timeout_add_seconds(
						main_opts.discov_interval,
						discovery_cb, adapter);
		}
		break;
	case STATE_DISCOV:
		discov_active = TRUE;
		emit_property_changed(connection, path,
					ADAPTER_INTERFACE, "Discovering",
					DBUS_TYPE_BOOLEAN, &discov_active);
		break;
	case STATE_RESOLVNAME:
		resolve_names(adapter);
		break;
	case STATE_SUSPENDED:
		suspend_discovery(adapter);
		break;
	}
}

int adapter_get_state(struct btd_adapter *adapter)
{
	return adapter->state;
}

struct remote_dev_info *adapter_search_found_devices(struct btd_adapter *adapter,
						struct remote_dev_info *match)
{
	GSList *l;

	l = g_slist_find_custom(adapter->found_devices, match,
					(GCompareFunc) found_device_cmp);
	if (l)
		return l->data;

	return NULL;
}

static int dev_rssi_cmp(struct remote_dev_info *d1, struct remote_dev_info *d2)
{
	int rssi1, rssi2;

	rssi1 = d1->rssi < 0 ? -d1->rssi : d1->rssi;
	rssi2 = d2->rssi < 0 ? -d2->rssi : d2->rssi;

	return rssi1 - rssi2;
}

static void append_dict_valist(DBusMessageIter *iter,
					const char *first_key,
					va_list var_args)
{
	DBusMessageIter dict;
	const char *key;
	int type;
	int n_elements;
	void *val;

	dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);

	key = first_key;
	while (key) {
		type = va_arg(var_args, int);
		val = va_arg(var_args, void *);
		if (type == DBUS_TYPE_ARRAY) {
			n_elements = va_arg(var_args, int);
			if (n_elements > 0)
				dict_append_array(&dict, key, DBUS_TYPE_STRING,
						val, n_elements);
		} else
			dict_append_entry(&dict, key, type, val);
		key = va_arg(var_args, char *);
	}

	dbus_message_iter_close_container(iter, &dict);
}

static void emit_device_found(const char *path, const char *address,
				const char *first_key, ...)
{
	DBusMessage *signal;
	DBusMessageIter iter;
	va_list var_args;

	signal = dbus_message_new_signal(path, ADAPTER_INTERFACE,
					"DeviceFound");
	if (!signal) {
		error("Unable to allocate new %s.DeviceFound signal",
				ADAPTER_INTERFACE);
		return;
	}
	dbus_message_iter_init_append(signal, &iter);
	dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &address);

	va_start(var_args, first_key);
	append_dict_valist(&iter, first_key, var_args);
	va_end(var_args);

	g_dbus_send_message(connection, signal);
}

static char **strlist2array(GSList *list)
{
	unsigned int i, n;
	char **array;

	if (list == NULL)
		return NULL;

	n = g_slist_length(list);
	array = g_new0(char *, n + 1);

	for (i = 0; list; list = list->next, i++)
		array[i] = g_strdup((const gchar *) list->data);

	return array;
}

void adapter_emit_device_found(struct btd_adapter *adapter,
						struct remote_dev_info *dev)
{
	struct btd_device *device;
	char peer_addr[18], local_addr[18];
	const char *icon, *paddr = peer_addr;
	dbus_bool_t paired = FALSE;
	dbus_int16_t rssi = dev->rssi;
	char *alias;
	size_t uuid_count;

	ba2str(&dev->bdaddr, peer_addr);
	ba2str(&adapter->bdaddr, local_addr);

	device = adapter_find_device(adapter, paddr);
	if (device)
		paired = device_is_paired(device);

	/* The uuids string array is updated only if necessary */
	uuid_count = g_slist_length(dev->services);
	if (dev->services && dev->uuid_count != uuid_count) {
		g_strfreev(dev->uuids);
		dev->uuids = strlist2array(dev->services);
		dev->uuid_count = uuid_count;
	}

	if (dev->le) {
		gboolean broadcaster;

		if (dev->flags & (EIR_LIM_DISC | EIR_GEN_DISC))
			broadcaster = FALSE;
		else
			broadcaster = TRUE;

		emit_device_found(adapter->path, paddr,
				"Address", DBUS_TYPE_STRING, &paddr,
				"RSSI", DBUS_TYPE_INT16, &rssi,
				"Name", DBUS_TYPE_STRING, &dev->name,
				"Paired", DBUS_TYPE_BOOLEAN, &paired,
				"Broadcaster", DBUS_TYPE_BOOLEAN, &broadcaster,
				"UUIDs", DBUS_TYPE_ARRAY, &dev->uuids, uuid_count,
				NULL);
		return;
	}

	icon = class_to_icon(dev->class);

	if (!dev->alias) {
#ifdef ANDROID
		/* Android doesn't fallback to name or address if there is no alias.
		   It's safe to set alias to NULL because dict_append_entry() will
		   silently return and not set the property when value is NULL. */
		alias = NULL;
#else
		if (!dev->name) {
			alias = g_strdup(peer_addr);
			g_strdelimit(alias, ":", '-');
		} else
			alias = g_strdup(dev->name);
#endif
	} else
		alias = g_strdup(dev->alias);

	emit_device_found(adapter->path, paddr,
			"Address", DBUS_TYPE_STRING, &paddr,
			"Class", DBUS_TYPE_UINT32, &dev->class,
			"Icon", DBUS_TYPE_STRING, &icon,
			"RSSI", DBUS_TYPE_INT16, &rssi,
			"Name", DBUS_TYPE_STRING, &dev->name,
			"Alias", DBUS_TYPE_STRING, &alias,
			"LegacyPairing", DBUS_TYPE_BOOLEAN, &dev->legacy,
			"Paired", DBUS_TYPE_BOOLEAN, &paired,
			"UUIDs", DBUS_TYPE_ARRAY, &dev->uuids, uuid_count,
			NULL);

	g_free(alias);
}

static struct remote_dev_info *found_device_new(const bdaddr_t *bdaddr,
					gboolean le, const char *name,
					const char *alias, uint32_t class,
					gboolean legacy, name_status_t status,
					int flags)
{
	struct remote_dev_info *dev;

	dev = g_new0(struct remote_dev_info, 1);
	bacpy(&dev->bdaddr, bdaddr);
	dev->le = le;
	dev->name = g_strdup(name);
	dev->alias = g_strdup(alias);
	dev->class = class;
	dev->legacy = legacy;
	dev->name_status = status;
	if (flags >= 0)
		dev->flags = flags;

	return dev;
}

static void remove_same_uuid(gpointer data, gpointer user_data)
{
	struct remote_dev_info *dev = user_data;
	GSList *l;

	for (l = dev->services; l; l = l->next) {
		char *current_uuid = l->data;
		char *new_uuid = data;

		if (strcmp(current_uuid, new_uuid) == 0) {
			g_free(current_uuid);
			dev->services = g_slist_delete_link(dev->services, l);
			break;
		}
	}
}

static void dev_prepend_uuid(gpointer data, gpointer user_data)
{
	struct remote_dev_info *dev = user_data;
	char *new_uuid = data;

	dev->services = g_slist_prepend(dev->services, g_strdup(new_uuid));
}

static gboolean pairing_is_legacy(bdaddr_t *local, bdaddr_t *peer,
					const uint8_t *eir, const char *name)
{
	unsigned char features[8];

	if (eir)
		return FALSE;

	if (name == NULL)
		return TRUE;

	if (read_remote_features(local, peer, NULL, features) < 0)
		return TRUE;

	if (features[0] & 0x01)
		return FALSE;
	else
		return TRUE;
}

static char *read_stored_data(bdaddr_t *local, bdaddr_t *peer, const char *file)
{
	char local_addr[18], peer_addr[18], filename[PATH_MAX + 1];

	ba2str(local, local_addr);
	ba2str(peer, peer_addr);

	create_name(filename, PATH_MAX, STORAGEDIR, local_addr, file);

	return textfile_get(filename, peer_addr);
}

void adapter_update_found_devices(struct btd_adapter *adapter, bdaddr_t *bdaddr,
						uint32_t class, int8_t rssi,
						uint8_t *data)
{
	struct remote_dev_info *dev, match;
	struct eir_data eir_data;
	char *alias, *name;
	gboolean legacy, le;
	name_status_t name_status;
	int err;

	memset(&eir_data, 0, sizeof(eir_data));
	err = eir_parse(&eir_data, data);
	if (err < 0) {
		error("Error parsing EIR data: %s (%d)", strerror(-err), -err);
		return;
	}

	if (eir_data.name != NULL && eir_data.name_complete)
		write_device_name(&adapter->bdaddr, bdaddr, eir_data.name);

	/* Device already seen in the discovery session ? */
	memset(&match, 0, sizeof(struct remote_dev_info));
	bacpy(&match.bdaddr, bdaddr);
	match.name_status = NAME_ANY;

	dev = adapter_search_found_devices(adapter, &match);
	if (dev) {
		adapter->oor_devices = g_slist_remove(adapter->oor_devices,
							dev);
		if (dev->rssi != rssi)
			goto done;

		eir_data_free(&eir_data);

		return;
	}

	/* New device in the discovery session */

	name = read_stored_data(&adapter->bdaddr, bdaddr, "names");

	if (eir_data.flags < 0) {
		le = FALSE;

		legacy = pairing_is_legacy(&adapter->bdaddr, bdaddr, data,
									name);

		if (!name && main_opts.name_resolv &&
				adapter_has_discov_sessions(adapter))
			name_status = NAME_REQUIRED;
		else
			name_status = NAME_NOT_REQUIRED;
	} else {
		le = TRUE;
		legacy = FALSE;
		name_status = NAME_NOT_REQUIRED;
	}

	alias = read_stored_data(&adapter->bdaddr, bdaddr, "aliases");

	dev = found_device_new(bdaddr, le, name, alias, class, legacy,
						name_status, eir_data.flags);
	free(name);
	free(alias);

	adapter->found_devices = g_slist_prepend(adapter->found_devices, dev);

done:
	dev->rssi = rssi;

	adapter->found_devices = g_slist_sort(adapter->found_devices,
						(GCompareFunc) dev_rssi_cmp);

	g_slist_foreach(eir_data.services, remove_same_uuid, dev);
	g_slist_foreach(eir_data.services, dev_prepend_uuid, dev);

	adapter_emit_device_found(adapter, dev);

	eir_data_free(&eir_data);
}

int adapter_remove_found_device(struct btd_adapter *adapter, bdaddr_t *bdaddr)
{
	struct remote_dev_info *dev, match;

	memset(&match, 0, sizeof(struct remote_dev_info));
	bacpy(&match.bdaddr, bdaddr);

	dev = adapter_search_found_devices(adapter, &match);
	if (!dev)
		return -1;

	dev->name_status = NAME_NOT_REQUIRED;

	return 0;
}

void adapter_mode_changed(struct btd_adapter *adapter, uint8_t scan_mode)
{
	const gchar *path = adapter_get_path(adapter);
	gboolean discoverable, pairable;

	DBG("old 0x%02x new 0x%02x", adapter->scan_mode, scan_mode);

	if (adapter->scan_mode == scan_mode)
		return;

	adapter_remove_discov_timeout(adapter);

	switch (scan_mode) {
	case SCAN_DISABLED:
		adapter->mode = MODE_OFF;
		discoverable = FALSE;
		pairable = FALSE;
		break;
	case SCAN_PAGE:
		adapter->mode = MODE_CONNECTABLE;
		discoverable = FALSE;
		pairable = adapter->pairable;
		break;
	case (SCAN_PAGE | SCAN_INQUIRY):
		adapter->mode = MODE_DISCOVERABLE;
		discoverable = TRUE;
		pairable = adapter->pairable;
		if (adapter->discov_timeout != 0)
			adapter_set_discov_timeout(adapter,
						adapter->discov_timeout);
		break;
	case SCAN_INQUIRY:
		/* Address the scenario where a low-level application like
		 * hciconfig changed the scan mode */
		if (adapter->discov_timeout != 0)
			adapter_set_discov_timeout(adapter,
						adapter->discov_timeout);

		/* ignore, this event should not be sent */
	default:
		/* ignore, reserved */
		return;
	}

	/* If page scanning gets toggled emit the Pairable property */
	if ((adapter->scan_mode & SCAN_PAGE) != (scan_mode & SCAN_PAGE))
		emit_property_changed(connection, adapter->path,
					ADAPTER_INTERFACE, "Pairable",
					DBUS_TYPE_BOOLEAN, &pairable);

	if (!discoverable)
		adapter_set_limited_discoverable(adapter, FALSE);

	emit_property_changed(connection, path,
				ADAPTER_INTERFACE, "Discoverable",
				DBUS_TYPE_BOOLEAN, &discoverable);

	adapter->scan_mode = scan_mode;

	set_mode_complete(adapter);
}

struct agent *adapter_get_agent(struct btd_adapter *adapter)
{
	if (!adapter)
		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, connection);

	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, connection);

	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(connection, adapter, device, TRUE);
	}
}

gboolean adapter_has_discov_sessions(struct btd_adapter *adapter)
{
	if (!adapter || !adapter->disc_sessions)
		return FALSE;

	return TRUE;
}

void adapter_suspend_discovery(struct btd_adapter *adapter)
{
	if (adapter->disc_sessions == NULL ||
			adapter->state == STATE_SUSPENDED)
		return;

	DBG("Suspending discovery");

	adapter_set_state(adapter, STATE_SUSPENDED);
}

void adapter_resume_discovery(struct btd_adapter *adapter)
{
	DBG("Resuming discovery");

	adapter_set_state(adapter, STATE_IDLE);
}

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)
{
	adapter->loaded_drivers = g_slist_remove(adapter->loaded_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 service_auth *auth = user_data;

	device_set_authorizing(auth->device, FALSE);

	auth->cb(derr, auth->user_data);
}

static gboolean auth_idle_cb(gpointer user_data)
{
	struct service_auth *auth = user_data;
	struct btd_adapter *adapter = auth->adapter;

	adapter->auth_idle_id = 0;

	auth->cb(NULL, auth->user_data);

	return FALSE;
}

static int adapter_authorize(struct btd_adapter *adapter, const bdaddr_t *dst,
					const char *uuid, service_auth_cb cb,
					void *user_data)
{
	struct service_auth *auth;
	struct btd_device *device;
	struct agent *agent;
	char address[18];
	const gchar *dev_path;
	int err;

	ba2str(dst, address);
	device = adapter_find_device(adapter, address);
	if (!device)
		return -EPERM;

	/* Device connected? */
	if (!g_slist_find(adapter->connections, device))
		return -ENOTCONN;

	if (adapter->auth_idle_id)
		return -EBUSY;

	auth = g_try_new0(struct service_auth, 1);
	if (!auth)
		return -ENOMEM;

	auth->cb = cb;
	auth->user_data = user_data;
	auth->device = device;
	auth->adapter = adapter;

	if (device_is_trusted(device) == TRUE) {
		adapter->auth_idle_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE,
							auth_idle_cb, auth,
							g_free);
		return 0;
	}

	agent = device_get_agent(device);
	if (!agent) {
		g_free(auth);
		return -EPERM;
	}

	dev_path = device_get_path(device);

	err = agent_authorize(agent, dev_path, uuid, agent_auth_cb, auth, g_free);
	if (err < 0)
		g_free(auth);
	else
		device_set_authorizing(device, TRUE);

	return err;
}

int btd_request_authorization(const bdaddr_t *src, const bdaddr_t *dst,
					const char *uuid, service_auth_cb cb,
					void *user_data)
{
	struct btd_adapter *adapter;
	GSList *l;

	if (bacmp(src, BDADDR_ANY) != 0) {
		adapter = manager_find_adapter(src);
		if (!adapter)
			return -EPERM;

		return adapter_authorize(adapter, dst, uuid, cb, user_data);
	}

	for (l = manager_get_adapters(); l != NULL; l = g_slist_next(l)) {
		int err;

		adapter = l->data;

		err = adapter_authorize(adapter, dst, uuid, cb, user_data);
		if (err == 0)
			return 0;
	}

	return -EPERM;
}

int btd_cancel_authorization(const bdaddr_t *src, const bdaddr_t *dst)
{
	struct btd_adapter *adapter = manager_find_adapter(src);
	struct btd_device *device;
	struct agent *agent;
	char address[18];
	int err;

	if (!adapter)
		return -EPERM;

	ba2str(dst, address);
	device = adapter_find_device(adapter, address);
	if (!device)
		return -EPERM;

	if (adapter->auth_idle_id) {
		g_source_remove(adapter->auth_idle_id);
		adapter->auth_idle_id = 0;
		return 0;
	}

	/*
	 * FIXME: Cancel fails if authorization is requested to adapter's
	 * agent and in the meanwhile CreatePairedDevice is called.
	 */

	agent = device_get_agent(device);
	if (!agent)
		return -EPERM;

	err = agent_cancel(agent);

	if (err == 0)
		device_set_authorizing(device, FALSE);

	return err;
}

static gchar *adapter_any_path = NULL;
static int adapter_any_refcount = 0;

const char *adapter_any_get_path(void)
{
	return adapter_any_path;
}

const char *btd_adapter_any_request_path(void)
{
	if (adapter_any_refcount++ > 0)
		return adapter_any_path;

	adapter_any_path = g_strdup_printf("%s/any", manager_get_base_path());

	return adapter_any_path;
}

void btd_adapter_any_release_path(void)
{
	adapter_any_refcount--;

	if (adapter_any_refcount > 0)
		return;

	g_free(adapter_any_path);
	adapter_any_path = NULL;
}

gboolean adapter_is_pairable(struct btd_adapter *adapter)
{
	return adapter->pairable;
}

gboolean adapter_powering_down(struct btd_adapter *adapter)
{
	return adapter->off_requested;
}

int btd_adapter_restore_powered(struct btd_adapter *adapter)
{
	char mode[14], address[18];

	if (!adapter_ops)
		return -EINVAL;

	if (!main_opts.remember_powered)
		return -EINVAL;

	if (adapter->up)
		return 0;

	ba2str(&adapter->bdaddr, address);
	if (read_device_mode(address, mode, sizeof(mode)) == 0 &&
						g_str_equal(mode, "off"))
		return 0;

	return adapter_ops->set_powered(adapter->dev_id, TRUE);
}

int btd_adapter_switch_online(struct btd_adapter *adapter)
{
	if (!adapter_ops)
		return -EINVAL;

	if (adapter->up)
		return 0;

	return adapter_ops->set_powered(adapter->dev_id, TRUE);
}

int btd_adapter_switch_offline(struct btd_adapter *adapter)
{
	if (!adapter_ops)
		return -EINVAL;

	if (!adapter->up)
		return 0;

	return adapter_ops->set_powered(adapter->dev_id, FALSE);
}

int btd_register_adapter_ops(struct btd_adapter_ops *ops, gboolean priority)
{
	if (ops->setup == NULL)
		return -EINVAL;

	if (priority)
		ops_candidates = g_slist_prepend(ops_candidates, ops);
	else
		ops_candidates = g_slist_append(ops_candidates, ops);

	return 0;
}

void btd_adapter_cleanup_ops(struct btd_adapter_ops *ops)
{
	ops_candidates = g_slist_remove(ops_candidates, ops);
	ops->cleanup();

	if (adapter_ops == ops)
		adapter_ops = NULL;
}

int adapter_ops_setup(void)
{
	GSList *l;
	int ret;

	if (!ops_candidates)
		return -EINVAL;

	for (l = ops_candidates; l != NULL; l = g_slist_next(l)) {
		struct btd_adapter_ops *ops = l->data;

		ret = ops->setup();
		if (ret < 0)
			continue;

		adapter_ops = ops;
		break;
	}

	return ret;
}

void btd_adapter_register_powered_callback(struct btd_adapter *adapter,
						btd_adapter_powered_cb cb)
{
	adapter->powered_callbacks =
			g_slist_append(adapter->powered_callbacks, cb);
}

void btd_adapter_unregister_powered_callback(struct btd_adapter *adapter,
						btd_adapter_powered_cb cb)
{
	adapter->powered_callbacks =
			g_slist_remove(adapter->powered_callbacks, cb);
}

int btd_adapter_set_fast_connectable(struct btd_adapter *adapter,
							gboolean enable)
{
	if (!adapter_ops)
		return -EINVAL;

	if (!adapter->up)
		return -EINVAL;

	return adapter_ops->set_fast_connectable(adapter->dev_id, enable);
}

int btd_adapter_read_clock(struct btd_adapter *adapter, bdaddr_t *bdaddr,
				int which, int timeout, uint32_t *clock,
				uint16_t *accuracy)
{
	if (!adapter_ops)
		return -EINVAL;

	if (!adapter->up)
		return -EINVAL;

	return adapter_ops->read_clock(adapter->dev_id, bdaddr, which,
						timeout, clock, accuracy);
}

int btd_adapter_disconnect_device(struct btd_adapter *adapter, bdaddr_t *bdaddr)
{
	return adapter_ops->disconnect(adapter->dev_id, bdaddr);
}

int btd_adapter_remove_bonding(struct btd_adapter *adapter, bdaddr_t *bdaddr)
{
	return adapter_ops->remove_bonding(adapter->dev_id, bdaddr);
}

int btd_adapter_pincode_reply(struct btd_adapter *adapter, bdaddr_t *bdaddr,
					const char *pin, size_t pin_len)
{
	return adapter_ops->pincode_reply(adapter->dev_id, bdaddr, pin,
								pin_len);
}

int btd_adapter_confirm_reply(struct btd_adapter *adapter, bdaddr_t *bdaddr,
							gboolean success)
{
	return adapter_ops->confirm_reply(adapter->dev_id, bdaddr, success);
}

int btd_adapter_passkey_reply(struct btd_adapter *adapter, bdaddr_t *bdaddr,
							uint32_t passkey)
{
	return adapter_ops->passkey_reply(adapter->dev_id, bdaddr, passkey);
}

int btd_adapter_encrypt_link(struct btd_adapter *adapter, bdaddr_t *bdaddr,
					bt_hci_result_t cb, gpointer user_data)
{
	return adapter_ops->encrypt_link(adapter->dev_id, bdaddr, cb, user_data);
}

int btd_adapter_set_did(struct btd_adapter *adapter, uint16_t vendor,
					uint16_t product, uint16_t version)
{
	return adapter_ops->set_did(adapter->dev_id, vendor, product, version);
}

int btd_adapter_retry_authentication(struct btd_adapter *adapter, bdaddr_t *bdaddr)
{
	return adapter_ops->retry_authentication(adapter->dev_id, bdaddr);
}

int adapter_create_bonding(struct btd_adapter *adapter, bdaddr_t *bdaddr,
								uint8_t io_cap)
{
	return adapter_ops->create_bonding(adapter->dev_id, bdaddr, io_cap);
}

int adapter_cancel_bonding(struct btd_adapter *adapter, bdaddr_t *bdaddr)
{
	return adapter_ops->cancel_bonding(adapter->dev_id, bdaddr);
}

int btd_adapter_read_local_oob_data(struct btd_adapter *adapter)
{
	return adapter_ops->read_local_oob_data(adapter->dev_id);
}

int btd_adapter_add_remote_oob_data(struct btd_adapter *adapter,
			bdaddr_t *bdaddr, uint8_t *hash, uint8_t *randomizer)
{
	return adapter_ops->add_remote_oob_data(adapter->dev_id, bdaddr, hash,
								randomizer);
}

int btd_adapter_remove_remote_oob_data(struct btd_adapter *adapter,
							bdaddr_t *bdaddr)
{
	return adapter_ops->remove_remote_oob_data(adapter->dev_id, bdaddr);
}