diff options
Diffstat (limited to 'hcid/adapter.c')
| -rw-r--r-- | hcid/adapter.c | 4577 | 
1 files changed, 4577 insertions, 0 deletions
diff --git a/hcid/adapter.c b/hcid/adapter.c new file mode 100644 index 00000000..29bf51ca --- /dev/null +++ b/hcid/adapter.c @@ -0,0 +1,4577 @@ +/* + * + *  BlueZ - Bluetooth protocol stack for Linux + * + *  Copyright (C) 2006-2007  Nokia Corporation + *  Copyright (C) 2004-2008  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 <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <time.h> +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/l2cap.h> +#include <bluetooth/sdp.h> +#include <bluetooth/sdp_lib.h> + +#include <glib.h> +#include <dbus/dbus.h> +#include <gdbus.h> + +#include "hcid.h" + +#include "adapter.h" +#include "device.h" + +#include "textfile.h" +#include "oui.h" +#include "dbus-common.h" +#include "dbus-hci.h" +#include "dbus-sdp.h" +#include "dbus-database.h" +#include "dbus-service.h" +#include "dbus-security.h" +#include "dbus-error.h" +#include "error.h" +#include "glib-helper.h" +#include "logging.h" +#include "agent.h" + +#define NUM_ELEMENTS(table) (sizeof(table)/sizeof(const char *)) + +#define IO_CAPABILITY_DISPLAYONLY	0x00 +#define IO_CAPABILITY_DISPLAYYESNO	0x01 +#define IO_CAPABILITY_KEYBOARDONLY	0x02 +#define IO_CAPABILITY_NOINPUTOUTPUT	0x03 +#define IO_CAPABILITY_INVALID		0xFF + +struct mode_req { +	struct adapter	*adapter; +	DBusConnection	*conn;		/* Connection reference */ +	DBusMessage	*msg;		/* Message reference */ +	uint8_t		mode;		/* Requested mode */ +	guint		id;		/* Listener id */ +}; + +static const char *service_cls[] = { +	"positioning", +	"networking", +	"rendering", +	"capturing", +	"object transfer", +	"audio", +	"telephony", +	"information" +}; + +static const char *major_cls[] = { +	"miscellaneous", +	"computer", +	"phone", +	"access point", +	"audio/video", +	"peripheral", +	"imaging", +	"wearable", +	"toy", +	"uncategorized" +}; + +static const char *computer_minor_cls[] = { +	"uncategorized", +	"desktop", +	"server", +	"laptop", +	"handheld", +	"palm", +	"wearable" +}; + +static const char *phone_minor_cls[] = { +	"uncategorized", +	"cellular", +	"cordless", +	"smart phone", +	"modem", +	"isdn" +}; + +static const char *access_point_minor_cls[] = { +	"fully", +	"1-17 percent", +	"17-33 percent", +	"33-50 percent", +	"50-67 percent", +	"67-83 percent", +	"83-99 percent", +	"not available" +}; + +static const char *audio_video_minor_cls[] = { +	"uncategorized", +	"headset", +	"handsfree", +	"unknown", +	"microphone", +	"loudspeaker", +	"headphones", +	"portable audio", +	"car audio", +	"set-top box", +	"hifi audio", +	"vcr", +	"video camera", +	"camcorder", +	"video monitor", +	"video display and loudspeaker", +	"video conferencing", +	"unknown", +	"gaming/toy" +}; + +static const char *peripheral_minor_cls[] = { +	"uncategorized", +	"keyboard", +	"pointing", +	"combo" +}; + +#if 0 +static const char *peripheral_2_minor_cls[] = { +	"uncategorized", +	"joystick", +	"gamepad", +	"remote control", +	"sensing", +	"digitizer tablet", +	"card reader" +}; +#endif + +static const char *imaging_minor_cls[] = { +	"display", +	"camera", +	"scanner", +	"printer" +}; + +static const char *wearable_minor_cls[] = { +	"wrist watch", +	"pager", +	"jacket", +	"helmet", +	"glasses" +}; + +static const char *toy_minor_cls[] = { +	"robot", +	"vehicle", +	"doll", +	"controller", +	"game" +}; + +static inline DBusMessage *invalid_args(DBusMessage *msg) +{ +	return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments", +			"Invalid arguments in method call"); +} + +static inline DBusMessage *not_available(DBusMessage *msg) +{ +	return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable", +			"Not Available"); +} + +static inline DBusMessage *adapter_not_ready(DBusMessage *msg) +{ +	return g_dbus_create_error(msg, ERROR_INTERFACE ".NotReady", +			"Adapter is not ready"); +} + +static inline DBusMessage *no_such_adapter(DBusMessage *msg) +{ +	return g_dbus_create_error(msg, ERROR_INTERFACE ".NoSuchAdapter", +							"No such adapter"); +} + +static inline DBusMessage *failed_strerror(DBusMessage *msg, int err) +{ +	return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", +			strerror(err)); +} + +static inline DBusMessage *in_progress(DBusMessage *msg, const char *str) +{ +	return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress", str); +} + +static inline DBusMessage *not_in_progress(DBusMessage *msg, const char *str) +{ +	return g_dbus_create_error(msg, ERROR_INTERFACE ".NotInProgress", str); +} + +static inline DBusMessage *not_authorized(DBusMessage *msg) +{ +	return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAuthorized", +			"Not authorized"); +} + +static inline DBusMessage *unsupported_major_class(DBusMessage *msg) +{ +	return g_dbus_create_error(msg, +			ERROR_INTERFACE ".UnsupportedMajorClass", +			"Unsupported Major Class"); +} + +static int auth_req_cmp(const void *p1, const void *p2) +{ +	const struct pending_auth_info *pb1 = p1; +	const bdaddr_t *bda = p2; + +	return bda ? bacmp(&pb1->bdaddr, bda) : -1; +} + +void adapter_auth_request_replied(struct adapter *adapter, bdaddr_t *dba) +{ +	GSList *l; +	struct pending_auth_info *auth; + +	l = g_slist_find_custom(adapter->auth_reqs, dba, auth_req_cmp); +	if (!l) +		return; + +	auth = l->data; + +	auth->replied = TRUE; +} + +struct pending_auth_info *adapter_find_auth_request(struct adapter *adapter, +							bdaddr_t *dba) +{ +	GSList *l; + +	l = g_slist_find_custom(adapter->auth_reqs, dba, auth_req_cmp); +	if (l) +		return l->data; + +	return NULL; +} + +void adapter_remove_auth_request(struct adapter *adapter, bdaddr_t *dba) +{ +	GSList *l; +	struct pending_auth_info *auth; + +	l = g_slist_find_custom(adapter->auth_reqs, dba, auth_req_cmp); +	if (!l) +		return; + +	auth = l->data; + +	adapter->auth_reqs = g_slist_remove(adapter->auth_reqs, auth); + +	g_free(auth); +} + +struct pending_auth_info *adapter_new_auth_request(struct adapter *adapter, +							bdaddr_t *dba, +							auth_type_t type) +{ +	struct pending_auth_info *info; + +	debug("hcid_dbus_new_auth_request"); + +	info = g_new0(struct pending_auth_info, 1); + +	bacpy(&info->bdaddr, dba); +	info->type = type; +	adapter->auth_reqs = g_slist_append(adapter->auth_reqs, info); + +	if (adapter->bonding && !bacmp(dba, &adapter->bonding->bdaddr)) +		adapter->bonding->auth_active = 1; + +	return info; +} + +int pending_remote_name_cancel(struct adapter *adapter) +{ +	struct remote_dev_info *dev, match; +	GSList *l; +	int dd, err = 0; + +	/* find the pending remote name request */ +	memset(&match, 0, sizeof(struct remote_dev_info)); +	bacpy(&match.bdaddr, BDADDR_ANY); +	match.name_status = NAME_REQUESTED; + +	l = g_slist_find_custom(adapter->found_devices, &match, +			(GCompareFunc) found_device_cmp); +	if (!l) /* no pending request */ +		return 0; + +	dd = hci_open_dev(adapter->dev_id); +	if (dd < 0) +		return -ENODEV; + +	dev = l->data; + +	if (hci_read_remote_name_cancel(dd, &dev->bdaddr, 1000) < 0) { +		error("Remote name cancel failed: %s(%d)", strerror(errno), errno); +		err = -errno; +	} + +	/* free discovered devices list */ +	g_slist_foreach(adapter->found_devices, (GFunc) g_free, NULL); +	g_slist_free(adapter->found_devices); +	adapter->found_devices = NULL; + +	hci_close_dev(dd); +	return err; +} + +static int auth_info_agent_cmp(const void *a, const void *b) +{ +	const struct pending_auth_info *auth = a; +	const struct agent *agent = b; + +	if (auth->agent == agent) +		return 0; + +	return -1; +} + +static void device_agent_removed(struct agent *agent, void *user_data) +{ +	struct device *device = user_data; +	struct pending_auth_info *auth; +	GSList *l; + +	device->agent = NULL; + +	l = g_slist_find_custom(device->adapter->auth_reqs, agent, +					auth_info_agent_cmp); +	if (!l) +		return; + +	auth = l->data; +	auth->agent = NULL; +} + +static struct bonding_request_info *bonding_request_new(DBusConnection *conn, +							DBusMessage *msg, +							struct adapter *adapter, +							const char *address, +							const char *agent_path, +							uint8_t capability) +{ +	struct bonding_request_info *bonding; +	struct device *device; + +	debug("bonding_request_new(%s)", address); + +	if (hcid_dbus_use_experimental() && agent_path) { +		const char *name = dbus_message_get_sender(msg); + +		device = adapter_get_device(conn, adapter, address); +		if (!device) +			return NULL; + +		device->agent = agent_create(adapter, name, agent_path, +						capability, +						device_agent_removed, +						device); +		debug("Temporary agent registered for hci%d/%s at %s:%s", +				adapter->dev_id, device->address, name, +				agent_path); +	} + +	bonding = g_new0(struct bonding_request_info, 1); + +	bonding->conn = dbus_connection_ref(conn); +	bonding->msg = dbus_message_ref(msg); +	bonding->adapter = adapter; + +	str2ba(address, &bonding->bdaddr); + +	return bonding; +} + +const char *mode2str(uint8_t mode) +{ +	switch(mode) { +	case MODE_OFF: +		return "off"; +	case MODE_CONNECTABLE: +		return "connectable"; +	case MODE_DISCOVERABLE: +		return "discoverable"; +	case MODE_LIMITED: +		return "limited"; +	default: +		return "unknown"; +	} +} + +static uint8_t on_mode(const char *addr) +{ +	char mode[14]; +	bdaddr_t sba; + +	str2ba(addr, &sba); + +	if (read_on_mode(&sba, mode, sizeof(mode)) < 0) +		return MODE_CONNECTABLE; + +	return str2mode(addr, mode); +} + +uint8_t str2mode(const char *addr, const char *mode) +{ +	if (strcasecmp("off", mode) == 0) +		return MODE_OFF; +	else if (strcasecmp("connectable", mode) == 0) +		return MODE_CONNECTABLE; +	else if (strcasecmp("discoverable", mode) == 0) +		return MODE_DISCOVERABLE; +	else if (strcasecmp("limited", mode) == 0) +		return MODE_LIMITED; +	else if (strcasecmp("on", mode) == 0) +		return on_mode(addr); +	else +		return MODE_UNKNOWN; +} + +static DBusMessage *adapter_get_info(DBusConnection *conn, +				DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	const char *property; +	DBusMessage *reply; +	DBusMessageIter iter; +	DBusMessageIter dict; +	bdaddr_t ba; +	char str[249]; +	uint8_t cls[3]; + +	if (check_address(adapter->address) < 0) +		return adapter_not_ready(msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_iter_init_append(reply, &iter); + +	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, +			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING +			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING +			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + +	property = adapter->address; +	dbus_message_iter_append_dict_entry(&dict, "address", +			DBUS_TYPE_STRING, &property); + +	memset(str, 0, sizeof(str)); +	property = str; +	str2ba(adapter->address, &ba); + +	if (!read_local_name(&ba, str)) +		dbus_message_iter_append_dict_entry(&dict, "name", +			DBUS_TYPE_STRING, &property); + +	get_device_version(adapter->dev_id, str, sizeof(str)); +	dbus_message_iter_append_dict_entry(&dict, "version", +			DBUS_TYPE_STRING, &property); + +	get_device_revision(adapter->dev_id, str, sizeof(str)); +	dbus_message_iter_append_dict_entry(&dict, "revision", +			DBUS_TYPE_STRING, &property); + +	get_device_manufacturer(adapter->dev_id, str, sizeof(str)); +	dbus_message_iter_append_dict_entry(&dict, "manufacturer", +			DBUS_TYPE_STRING, &property); + +	get_device_company(adapter->dev_id, str, sizeof(str)); +	dbus_message_iter_append_dict_entry(&dict, "company", +			DBUS_TYPE_STRING, &property); + +	property = mode2str(adapter->mode); + +	dbus_message_iter_append_dict_entry(&dict, "mode", +			DBUS_TYPE_STRING, &property); + +	dbus_message_iter_append_dict_entry(&dict, "discoverable_timeout", +				DBUS_TYPE_UINT32, &adapter->discov_timeout); + +	if (!read_local_class(&ba, cls)) { +		uint32_t class; + +		memcpy(&class, cls, 3); +		dbus_message_iter_append_dict_entry(&dict, "class", +			DBUS_TYPE_UINT32, &class); + +		property = major_class_str(class); +		dbus_message_iter_append_dict_entry(&dict, "major_class", +			DBUS_TYPE_STRING, &property); + +		property = minor_class_str(class); +		dbus_message_iter_append_dict_entry(&dict, "minor_class", +			DBUS_TYPE_STRING, &property); +	} + +	dbus_message_iter_close_container(&iter, &dict); + +	return reply; +} + +static DBusMessage *adapter_get_address(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	const char *paddr = adapter->address; +	DBusMessage *reply; + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return invalid_args(msg); + +	if (check_address(paddr) < 0) +		return adapter_not_ready(msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &paddr, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *adapter_get_version(DBusConnection *conn, +				DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	char str[20], *str_ptr = str; +	int err; + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return invalid_args(msg); + +	err = get_device_version(adapter->dev_id, str, sizeof(str)); +	if (err < 0) +		return failed_strerror(msg, -err); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *adapter_get_revision(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	char str[64], *str_ptr = str; +	int err; + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return invalid_args(msg); + +	err = get_device_revision(adapter->dev_id, str, sizeof(str)); +	if (err < 0) +		return failed_strerror(msg, -err); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *adapter_get_manufacturer(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	char str[64], *str_ptr = str; +	int err; + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return invalid_args(msg); + +	err = get_device_manufacturer(adapter->dev_id, str, sizeof(str)); +	if (err < 0) +		return failed_strerror(msg, -err); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *adapter_get_company(DBusConnection *conn, +				DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	char str[64], *str_ptr = str; +	int err; + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return invalid_args(msg); + +	err = get_device_company(adapter->dev_id, str, sizeof(str)); +	if (err < 0) +		return failed_strerror(msg, -err); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *adapter_list_modes(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	DBusMessage *reply; +	DBusMessageIter iter; +	DBusMessageIter array_iter; +	const char *mode_ptr[] = { "off", "connectable", "discoverable", "limited" }; +	int i; + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return invalid_args(msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_iter_init_append(reply, &iter); +	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, +						DBUS_TYPE_STRING_AS_STRING, &array_iter); +	for (i = 0; i < 4; i++) +		dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_STRING, +								&mode_ptr[i]); + +	dbus_message_iter_close_container(&iter, &array_iter); + +	return reply; +} + +static DBusMessage *adapter_get_mode(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	const struct adapter *adapter = data; +	DBusMessage *reply = NULL; +	const char *mode; + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return invalid_args(msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	mode = mode2str(adapter->mode); + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &mode, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *set_mode(DBusConnection *conn, DBusMessage *msg, +				uint8_t new_mode, void *data) +{ +	struct adapter *adapter = data; +	uint8_t scan_enable; +	uint8_t current_scan = adapter->scan_enable; +	bdaddr_t local; +	gboolean limited; +	int err, dd; +	const char *mode; + +	switch(new_mode) { +	case MODE_OFF: +		scan_enable = SCAN_DISABLED; +		break; +	case MODE_CONNECTABLE: +		scan_enable = SCAN_PAGE; +		break; +	case MODE_DISCOVERABLE: +	case MODE_LIMITED: +		scan_enable = (SCAN_PAGE | SCAN_INQUIRY); +		break; +	default: +		return invalid_args(msg); +	} + +	/* Do reverse resolution in case of "on" mode */ +	mode = mode2str(new_mode); + +	dd = hci_open_dev(adapter->dev_id); +	if (dd < 0) +		return no_such_adapter(msg); + +	if (!adapter->up && +			(hcid.offmode == HCID_OFFMODE_NOSCAN || +			 (hcid.offmode == HCID_OFFMODE_DEVDOWN && +			  scan_enable != SCAN_DISABLED))) { +		/* Start HCI device */ +		if (ioctl(dd, HCIDEVUP, adapter->dev_id) == 0) +			goto done; /* on success */ + +		if (errno != EALREADY) { +			err = errno; +			error("Can't init device hci%d: %s (%d)\n", +				adapter->dev_id, strerror(errno), errno); + +			hci_close_dev(dd); +			return failed_strerror(msg, err); +		} +	} + +	if (adapter->up && scan_enable == SCAN_DISABLED && +			hcid.offmode == HCID_OFFMODE_DEVDOWN) { +		if (ioctl(dd, HCIDEVDOWN, adapter->dev_id) < 0) { +			hci_close_dev(dd); +			return failed_strerror(msg, errno); +		} + +		goto done; +	} + +	limited = (new_mode == MODE_LIMITED ? TRUE : FALSE); +	err = set_limited_discoverable(dd, adapter->class, limited); +	if (err < 0) { +		hci_close_dev(dd); +		return failed_strerror(msg, -err); +	} + +	if (current_scan != scan_enable) { +		struct hci_request rq; +		uint8_t status = 0; + +		memset(&rq, 0, sizeof(rq)); +		rq.ogf    = OGF_HOST_CTL; +		rq.ocf    = OCF_WRITE_SCAN_ENABLE; +		rq.cparam = &scan_enable; +		rq.clen   = sizeof(scan_enable); +		rq.rparam = &status; +		rq.rlen   = sizeof(status); +		rq.event = EVT_CMD_COMPLETE; + +		if (hci_send_req(dd, &rq, 1000) < 0) { +			err = errno; +			error("Sending write scan enable command failed: %s (%d)", +					strerror(errno), errno); +			hci_close_dev(dd); +			return failed_strerror(msg, err); +		} + +		if (status) { +			error("Setting scan enable failed with status 0x%02x", +					status); +			hci_close_dev(dd); +			return failed_strerror(msg, bt_error(status)); +		} +	} else { +		/* discoverable or limited */ +		if ((scan_enable & SCAN_INQUIRY) && (new_mode != adapter->mode)) { +			g_dbus_emit_signal(conn, +					dbus_message_get_path(msg), +					ADAPTER_INTERFACE, +					"ModeChanged", +					DBUS_TYPE_STRING, &mode, +					DBUS_TYPE_INVALID); + +			if (adapter->timeout_id) +				g_source_remove(adapter->timeout_id); + +			if (!adapter->sessions && !adapter->discov_timeout) +				adapter->timeout_id = g_timeout_add(adapter->discov_timeout * 1000, +						discov_timeout_handler, adapter); +		} +	} +done: +	str2ba(adapter->address, &local); +	write_device_mode(&local, mode); + +	hci_close_dev(dd); + +	adapter->mode = new_mode; + +	return dbus_message_new_method_return(msg); +} + +gint find_session(struct mode_req *req, DBusMessage *msg) +{ +	const char *name = dbus_message_get_sender(req->msg); +	const char *sender = dbus_message_get_sender(msg); + +	return strcmp(name, sender); +} + +static void confirm_mode_cb(struct agent *agent, DBusError *err, void *data) +{ +	struct mode_req *req = data; +	DBusMessage *reply; + +	if (err && dbus_error_is_set(err)) { +		reply = dbus_message_new_error(req->msg, err->name, err->message); +		dbus_connection_send(req->conn, reply, NULL); +		dbus_message_unref(reply); +		goto cleanup; +	} + +	reply = set_mode(req->conn, req->msg, req->mode, req->adapter); +	dbus_connection_send(req->conn, reply, NULL); +	dbus_message_unref(reply); + +	if (!g_slist_find_custom(req->adapter->sessions, req->msg, +			(GCompareFunc) find_session)) +		goto cleanup; + +	return; + +cleanup: +	dbus_message_unref(req->msg); +	if (req->id) +		g_dbus_remove_watch(req->conn, req->id); +	dbus_connection_unref(req->conn); +	g_free(req); +} + +static DBusMessage *confirm_mode(DBusConnection *conn, DBusMessage *msg, +					const char *mode, void *data) +{ +	struct adapter *adapter = data; +	struct mode_req *req; +	int ret; + +	if (!adapter->agent) +		return dbus_message_new_method_return(msg); + +	req = g_new0(struct mode_req, 1); +	req->adapter = adapter; +	req->conn = dbus_connection_ref(conn); +	req->msg = dbus_message_ref(msg); +	req->mode = str2mode(adapter->address, mode); + +	ret = agent_confirm_mode_change(adapter->agent, mode, confirm_mode_cb, +					req); +	if (ret < 0) { +		dbus_connection_unref(req->conn); +		dbus_message_unref(req->msg); +		g_free(req); +		return invalid_args(msg); +	} + +	return NULL; +} + +static DBusMessage *adapter_set_mode(DBusConnection *conn, +				DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	const char *mode; + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &mode, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (!mode) +		return invalid_args(msg); + +	adapter->global_mode = str2mode(adapter->address, mode); + +	if (adapter->global_mode == adapter->mode) +		return dbus_message_new_method_return(msg); + +	if (adapter->sessions && adapter->global_mode < adapter->mode) +		return confirm_mode(conn, msg, mode, data); + +	return set_mode(conn, msg, str2mode(adapter->address, mode), data); +} + +static DBusMessage *adapter_get_discoverable_to(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	const struct adapter *adapter = data; +	DBusMessage *reply; + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return invalid_args(msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, DBUS_TYPE_UINT32, &adapter->discov_timeout, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static void resolve_paths(DBusMessage *msg, char **old_path, char **new_path) +{ +	const char *path = dbus_message_get_path(msg); + +	if (!path) +		return; + +	if (old_path) +		*old_path = NULL; + +	if (new_path) +		*new_path = NULL; + +	/* old path calls */ +	if (g_str_has_prefix(path, BASE_PATH)) { +		if (old_path) +			*old_path = g_strdup(path); + +		if (hcid_dbus_use_experimental() && new_path) +			*new_path = g_strdup(path + ADAPTER_PATH_INDEX); + +		return; +	} + +	if (old_path) +		*old_path = g_strconcat(BASE_PATH, path, NULL); + +	if (new_path) +		*new_path = g_strdup(path); +} + +static DBusMessage *set_discoverable_timeout(DBusConnection *conn, +							DBusMessage *msg, +							uint32_t timeout, +							void *data) +{ +	struct adapter *adapter = data; +	bdaddr_t bdaddr; +	char *old_path, *new_path; + +	if (adapter->timeout_id) { +		g_source_remove(adapter->timeout_id); +		adapter->timeout_id = 0; +	} + +	if ((timeout != 0) && (adapter->scan_enable & SCAN_INQUIRY)) +		adapter->timeout_id = g_timeout_add(timeout * 1000, +						discov_timeout_handler, +						adapter); + +	adapter->discov_timeout = timeout; + +	str2ba(adapter->address, &bdaddr); +	write_discoverable_timeout(&bdaddr, timeout); + +	resolve_paths(msg, &old_path, &new_path); + +	g_dbus_emit_signal(conn, old_path, +					ADAPTER_INTERFACE, +					"DiscoverableTimeoutChanged", +					DBUS_TYPE_UINT32, &timeout, +					DBUS_TYPE_INVALID); +	if (new_path) { +		dbus_connection_emit_property_changed(conn, new_path, +						ADAPTER_INTERFACE, +						"DiscoverableTimeout", +						DBUS_TYPE_UINT32, &timeout); +	} + +	g_free(old_path); +	g_free(new_path); + +	return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_set_discoverable_to(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	uint32_t timeout; + +	if (!adapter->up) +		return adapter_not_ready(msg); + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_UINT32, &timeout, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	return set_discoverable_timeout(conn, msg, timeout, data); +} + +static DBusMessage *adapter_is_connectable(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	const struct adapter *adapter = data; +	DBusMessage *reply; +	const uint8_t scan_enable = adapter->scan_enable; +	dbus_bool_t connectable = FALSE; + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return invalid_args(msg); + +	if (scan_enable & SCAN_PAGE) +		connectable = TRUE; + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connectable, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *adapter_is_discoverable(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	const struct adapter *adapter = data; +	DBusMessage *reply; +	const uint8_t scan_enable = adapter->scan_enable; +	dbus_bool_t discoverable = FALSE; + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return invalid_args(msg); + +	if (scan_enable & SCAN_INQUIRY) +		discoverable = TRUE; + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &discoverable, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *adapter_is_connected(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	DBusMessage *reply; +	dbus_bool_t connected = FALSE; + +	struct adapter *adapter = data; +	GSList *l = adapter->active_conn; + +	const char *peer_addr; +	bdaddr_t peer_bdaddr; + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &peer_addr, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (check_address(peer_addr) < 0) +		return invalid_args(msg); + +	str2ba(peer_addr, &peer_bdaddr); + +	l = g_slist_find_custom(l, &peer_bdaddr, active_conn_find_by_bdaddr); +	if (l) +		connected = TRUE; + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *adapter_list_connections(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	DBusMessage *reply; +	DBusMessageIter iter; +	DBusMessageIter array_iter; +	struct adapter *adapter = data; +	GSList *l = adapter->active_conn; + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return invalid_args(msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_iter_init_append(reply, &iter); +	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, +					DBUS_TYPE_STRING_AS_STRING, &array_iter); + +	while (l) { +		char peer_addr[18]; +		const char *paddr = peer_addr; +		struct active_conn_info *dev = l->data; + +		ba2str(&dev->bdaddr, peer_addr); + +		dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_STRING, +						&paddr); + +		l = l->next; +	} + +	dbus_message_iter_close_container(&iter, &array_iter); + +	return reply; +} + +static DBusMessage *adapter_get_major_class(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	const struct adapter *adapter = data; +	DBusMessage *reply; +	const char *str_ptr = "computer"; + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return invalid_args(msg); + +	/* FIXME: Currently, only computer major class is supported */ +	if ((adapter->class[1] & 0x1f) != 1) +		return unsupported_major_class(msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *adapter_list_minor_classes(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	const struct adapter *adapter = data; +	DBusMessage *reply; +	DBusMessageIter iter; +	DBusMessageIter array_iter; +	const char **minor_ptr; +	uint8_t major_class; +	int size, i; + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return invalid_args(msg); + +	major_class = adapter->class[1] & 0x1F; + +	switch (major_class) { +	case 1: /* computer */ +		minor_ptr = computer_minor_cls; +		size = sizeof(computer_minor_cls) / sizeof(*computer_minor_cls); +		break; +	case 2: /* phone */ +		minor_ptr = phone_minor_cls; +		size = sizeof(phone_minor_cls) / sizeof(*phone_minor_cls); +		break; +	default: +		return unsupported_major_class(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_STRING_AS_STRING, &array_iter); +	for (i = 0; i < size; i++) +		dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_STRING, +						&minor_ptr[i]); + +	dbus_message_iter_close_container(&iter, &array_iter); + +	return reply; +} + +static DBusMessage *adapter_get_minor_class(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	const char *str_ptr = ""; +	uint8_t minor_class; + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return invalid_args(msg); + +	/* FIXME: Currently, only computer major class is supported */ +	if ((adapter->class[1] & 0x1f) != 1) +		return unsupported_major_class(msg); + +	minor_class = adapter->class[0] >> 2; + +	/* Validate computer minor class */ +	if (minor_class > (sizeof(computer_minor_cls) / sizeof(*computer_minor_cls))) +		goto failed; + +	str_ptr = computer_minor_cls[minor_class]; + +failed: +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *adapter_set_minor_class(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	const char *minor; +	uint32_t dev_class = 0xFFFFFFFF; +	int i, dd; + +	if (!adapter->up) +		return adapter_not_ready(msg); + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &minor, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (!minor) +		return invalid_args(msg); + +	dd = hci_open_dev(adapter->dev_id); +	if (dd < 0) +		return no_such_adapter(msg); + +	/* Currently, only computer major class is supported */ +	if ((adapter->class[1] & 0x1f) != 1) { +		hci_close_dev(dd); +		return unsupported_major_class(msg); +	} +	for (i = 0; i < sizeof(computer_minor_cls) / sizeof(*computer_minor_cls); i++) +		if (!strcasecmp(minor, computer_minor_cls[i])) { +			/* Remove the format type */ +			dev_class = i << 2; +			break; +		} + +	/* Check if it's a valid minor class */ +	if (dev_class == 0xFFFFFFFF) { +		hci_close_dev(dd); +		return invalid_args(msg); +	} + +	/* set the service class and major class  */ +	dev_class |= (adapter->class[2] << 16) | (adapter->class[1] << 8); + +	if (hci_write_class_of_dev(dd, dev_class, 2000) < 0) { +		int err = errno; +		error("Can't write class of device on hci%d: %s(%d)", +				adapter->dev_id, strerror(errno), errno); +		hci_close_dev(dd); +		return failed_strerror(msg, err); +	} + +	g_dbus_emit_signal(conn, dbus_message_get_path(msg), +					ADAPTER_INTERFACE, "MinorClassChanged", +					DBUS_TYPE_STRING, &minor, +					DBUS_TYPE_INVALID); + +	hci_close_dev(dd); + +	return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_get_service_classes(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	DBusMessageIter iter; +	DBusMessageIter array_iter; +	const char *str_ptr; +	int i; + +	if (!adapter->up) +		return adapter_not_ready(msg); + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return invalid_args(msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_iter_init_append(reply, &iter); + +	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, +				DBUS_TYPE_STRING_AS_STRING, &array_iter); + +	for (i = 0; i < (sizeof(service_cls) / sizeof(*service_cls)); i++) { +		if (adapter->class[2] & (1 << i)) { +			str_ptr = service_cls[i]; +			dbus_message_iter_append_basic(&array_iter, +						DBUS_TYPE_STRING, &str_ptr); +		} +	} + +	dbus_message_iter_close_container(&iter, &array_iter); + +	return reply; +} + +static DBusMessage *adapter_get_name(DBusConnection *conn, +				DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	char str[249], *str_ptr = str; +	int err; +	bdaddr_t ba; + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return invalid_args(msg); + +	str2ba(adapter->address, &ba); + +	err = read_local_name(&ba, str); +	if (err < 0) { +		if (!adapter->up) +			return adapter_not_ready(msg); + +		err = get_device_name(adapter->dev_id, str, sizeof(str)); +		if (err < 0) +			return failed_strerror(msg, -err); +	} + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *set_name(DBusConnection *conn, DBusMessage *msg, +					const char *name, void *data) +{ +	struct adapter *adapter = data; +	bdaddr_t bdaddr; +	int ecode; +	char *new_path; + +	if (!g_utf8_validate(name, -1, NULL)) { +		error("Name change failed: the supplied name isn't valid UTF-8"); +		return invalid_args(msg); +	} + +	str2ba(adapter->address, &bdaddr); + +	write_local_name(&bdaddr, (char *) name); + +	if (!adapter->up) +		goto done; + +	ecode = set_device_name(adapter->dev_id, name); +	if (ecode < 0) +		return failed_strerror(msg, -ecode); +done: +	resolve_paths(msg, NULL, &new_path); + +	if (new_path) { +		dbus_connection_emit_property_changed(conn, new_path, +						ADAPTER_INTERFACE, +						"Name", DBUS_TYPE_STRING, +						&name); +	} + +	g_free(new_path); +	return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_set_name(DBusConnection *conn, +				DBusMessage *msg, void *data) +{ +	char *str_ptr; + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &str_ptr, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	return set_name(conn, msg, str_ptr, data); +} + +static DBusMessage *adapter_get_remote_info(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	DBusMessageIter iter; +	DBusMessageIter dict; +	bdaddr_t src, dst; +	const char *addr_ptr; +	char filename[PATH_MAX + 1]; +	char buf[64]; +	const char *ptr; +	char *str; +	dbus_bool_t boolean; +	uint32_t class; +	int compid, ver, subver; + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &addr_ptr, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (check_address(addr_ptr) < 0) +		return invalid_args(msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_iter_init_append(reply, &iter); + +	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, +			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING +			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING +			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + +	/* Name */ +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "names"); +	str = textfile_caseget(filename, addr_ptr); +	if (str) { +		dbus_message_iter_append_dict_entry(&dict, "name", +				DBUS_TYPE_STRING, &str); +		free(str); +	} + +	str2ba(adapter->address, &src); +	str2ba(addr_ptr, &dst); + +	/* Remote device class */ +	if (read_remote_class(&src, &dst, &class) == 0) { + +		dbus_message_iter_append_dict_entry(&dict, "class", +				DBUS_TYPE_UINT32, &class); + +		ptr = major_class_str(class); +		dbus_message_iter_append_dict_entry(&dict, "major_class", +				DBUS_TYPE_STRING, &ptr); + +		ptr = minor_class_str(class); +		dbus_message_iter_append_dict_entry(&dict, "minor_class", +				DBUS_TYPE_STRING, &ptr); +	} + +	/* Alias */ +	if (get_device_alias(adapter->dev_id, &dst, buf, sizeof(buf)) > 0) { +		ptr = buf; +		dbus_message_iter_append_dict_entry(&dict, "alias", +				DBUS_TYPE_STRING, &ptr); +	} + +	/* Bonded */ +	create_name(filename, PATH_MAX, STORAGEDIR, +			adapter->address, "linkkeys"); +	str = textfile_caseget(filename, addr_ptr); +	if (str) { +		boolean = TRUE; +		free(str); +	} else { +		boolean = FALSE; +	} + +	dbus_message_iter_append_dict_entry(&dict, "bonded", +			DBUS_TYPE_BOOLEAN, &boolean); + +	/* Trusted */ +	boolean = read_trust(&src, addr_ptr, GLOBAL_TRUST); +	dbus_message_iter_append_dict_entry(&dict, "trusted", +			DBUS_TYPE_BOOLEAN, &boolean); + +	/* Connected */ +	if (g_slist_find_custom(adapter->active_conn, &dst, +				active_conn_find_by_bdaddr)) +		boolean = TRUE; +	else +		boolean = FALSE; + +	dbus_message_iter_append_dict_entry(&dict, "connected", +			DBUS_TYPE_BOOLEAN, &boolean); + +	/* HCI Revision/Manufacturer/Version */ +	create_name(filename, PATH_MAX, STORAGEDIR, +			adapter->address, "manufacturers"); + +	str = textfile_caseget(filename, addr_ptr); +	if (!str) +		goto done; + +	if (sscanf(str, "%d %d %d", &compid, &ver, &subver) != 3) { +		/* corrupted file data */ +		free(str); +		goto done; +	} + +	free(str); + +	ptr = buf; +	snprintf(buf, 16, "HCI 0x%X", subver); +	dbus_message_iter_append_dict_entry(&dict, "revision", +			DBUS_TYPE_STRING, &ptr); + +	ptr = bt_compidtostr(compid); +	dbus_message_iter_append_dict_entry(&dict, "manufacturer", +			DBUS_TYPE_STRING, &ptr); + +	str = lmp_vertostr(ver); +	snprintf(buf, 64, "Bluetooth %s", str); +	bt_free(str); + +	create_name(filename, PATH_MAX, STORAGEDIR, +			adapter->address, "features"); + +	str = textfile_caseget(filename, addr_ptr); +	if (str) { +		if (strlen(str) == 16) { +			uint8_t features; +			/* Getting the third byte */ +			features  = ((str[6] - 48) << 4) | (str[7] - 48); +			if (features & (LMP_EDR_ACL_2M | LMP_EDR_ACL_3M)) +				snprintf(buf, 64, "Bluetooth %s + EDR", ptr); + +		} +		free(str); +	} +	ptr = buf; +	dbus_message_iter_append_dict_entry(&dict, "version", +			DBUS_TYPE_STRING, &ptr); + +done: +	dbus_message_iter_close_container(&iter, &dict); + +	return reply; +} + +static DBusMessage *adapter_get_remote_svc(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	return get_remote_svc_rec(conn, msg, data, SDP_FORMAT_BINARY); +} + +static DBusMessage *adapter_get_remote_svc_xml(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	return get_remote_svc_rec(conn, msg, data, SDP_FORMAT_XML); +} + +static DBusMessage *adapter_get_remote_svc_handles(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	return get_remote_svc_handles(conn, msg, data); +} + +static DBusMessage *adapter_get_remote_svc_identifiers(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	return get_remote_svc_identifiers(conn, msg, data); +} + +static DBusMessage *adapter_finish_sdp_transact(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	return finish_remote_svc_transact(conn, msg, data); +} + +static DBusMessage *adapter_get_remote_version(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	char filename[PATH_MAX + 1]; +	char *addr_ptr, *str; +	char *str_ver = NULL; +	char info_array[64], *info = info_array; +	int compid, ver, subver; + +	memset(info_array, 0, 64); + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &addr_ptr, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (check_address(addr_ptr) < 0) +		return invalid_args(msg); + +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, +			"manufacturers"); + +	str = textfile_caseget(filename, addr_ptr); +	if (!str) +		return not_available(msg); + +	if (sscanf(str, "%d %d %d", &compid, &ver, &subver) != 3) { +		/* corrupted file data */ +		free(str); +		goto failed; +	} + +	free(str); + +	str_ver = lmp_vertostr(ver); + +	/* Default value */ +	snprintf(info, 64, "Bluetooth %s", str_ver); + +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, +			"features"); + +	str = textfile_caseget(filename, addr_ptr); +	if (!str) +		goto failed; + +	/* Check if the data is not corrupted */ +	if (strlen(str) == 16) { +		uint8_t features; +		/* Getting the third byte */ +		features  = ((str[6] - 48) << 4) | (str[7] - 48); +		if (features & (LMP_EDR_ACL_2M | LMP_EDR_ACL_3M)) +			snprintf(info, 64, "Bluetooth %s + EDR", str_ver); +	} + +	free(str); + +failed: +	if (str_ver) +		bt_free(str_ver); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &info, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *adapter_get_remote_revision(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	char filename[PATH_MAX + 1]; +	char *addr_ptr, *str; +	char info_array[16], *info = info_array; +	int compid, ver, subver; + +	memset(info_array, 0, 16); + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &addr_ptr, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (check_address(addr_ptr) < 0) +		return invalid_args(msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, +			"manufacturers"); + +	str = textfile_caseget(filename, addr_ptr); +	if (!str) +		return not_available(msg); + +	if (sscanf(str, "%d %d %d", &compid, &ver, &subver) == 3) +		snprintf(info, 16, "HCI 0x%X", subver); + +	free(str); + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &info, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *adapter_get_remote_manufacturer(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	char filename[PATH_MAX + 1]; +	char *addr_ptr, *str; +	char info_array[64], *info = info_array; +	int compid, ver, subver; + +	memset(info_array, 0, 64); + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &addr_ptr, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (check_address(addr_ptr) < 0) +		return invalid_args(msg); + +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, +			"manufacturers"); + +	str = textfile_caseget(filename, addr_ptr); +	if (!str) +		return not_available(msg); + +	if (sscanf(str, "%d %d %d", &compid, &ver, &subver) == 3) +		info = bt_compidtostr(compid); + +	free(str); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &info, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *adapter_get_remote_company(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	DBusMessage *reply; +	bdaddr_t bdaddr; +	char oui[9], *str_bdaddr, *tmp; + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &str_bdaddr, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	str2ba(str_bdaddr, &bdaddr); +	ba2oui(&bdaddr, oui); + +	tmp = ouitocomp(oui); +	if (!tmp) +		return not_available(msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) { +		free(tmp); +		return NULL; +	} + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &tmp, +					DBUS_TYPE_INVALID); + +	free(tmp); + +	return reply; +} + +static DBusMessage *get_remote_class(DBusConnection *conn, DBusMessage *msg, +						void *data, uint32_t *class) +{ +	struct adapter *adapter = data; +	char *addr_peer; +	bdaddr_t local, peer; +	int ecode; + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &addr_peer, +				DBUS_TYPE_INVALID)) { +		return invalid_args(msg); +	} + +	if (check_address(addr_peer) < 0) +		return invalid_args(msg); + +	str2ba(addr_peer, &peer); +	str2ba(adapter->address, &local); + +	ecode = read_remote_class(&local, &peer, class); +	if (ecode < 0) +		return not_available(msg); + +	return NULL; +} + +static DBusMessage *adapter_get_remote_major_class(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	DBusMessage *reply; +	const char *major_class; +	uint32_t class; + +	reply = get_remote_class(conn, msg, data, &class); +	if (reply) +		return reply; + +	major_class = major_class_str(class); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &major_class, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *adapter_get_remote_minor_class(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	DBusMessage *reply; +	const char *minor_class; +	uint32_t class; + +	reply = get_remote_class(conn, msg, data, &class); +	if (reply) +		return reply; + +	minor_class = minor_class_str(class); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &minor_class, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static void append_class_string(const char *class, DBusMessageIter *iter) +{ +	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &class); +} + +static DBusMessage *adapter_get_remote_service_cls(DBusConnection *conn, +							DBusMessage *msg, +							void *data) +{ +	DBusMessage *reply; +	DBusMessageIter iter, array_iter; +	GSList *service_classes; +	uint32_t class; + +	reply = get_remote_class(conn, msg, data, &class); +	if (reply) +		return reply; + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	service_classes = service_classes_str(class); + +	dbus_message_iter_init_append(reply, &iter); +	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, +						DBUS_TYPE_STRING_AS_STRING, &array_iter); + +	g_slist_foreach(service_classes, (GFunc) append_class_string, +			&array_iter); + +	dbus_message_iter_close_container(&iter, &array_iter); + +	g_slist_free(service_classes); + +	return reply; +} + +static DBusMessage *adapter_get_remote_class(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	DBusMessage *reply; +	uint32_t class; + +	reply = get_remote_class(conn, msg, data, &class); +	if (reply) +		return reply; + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, DBUS_TYPE_UINT32, &class, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *adapter_get_remote_features(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	char filename[PATH_MAX + 1]; +	struct adapter *adapter = data; +	DBusMessage *reply = NULL; +	DBusMessageIter iter, array_iter; +	uint8_t features[8], *ptr = features; +	const char *addr; +	char *str; +	int i; + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &addr, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (check_address(addr) < 0) +		return invalid_args(msg); + +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "features"); + +	str = textfile_caseget(filename, addr); +	if (!str) +		return not_available(msg); + +	memset(features, 0, sizeof(features)); +	for (i = 0; i < sizeof(features); i++) { +		char tmp[3]; + +		memcpy(tmp, str + (i * 2), 2); +		tmp[2] = '\0'; + +		features[i] = (uint8_t) strtol(tmp, NULL, 16); +	} + +	reply = dbus_message_new_method_return(msg); +	if (!reply) { +		free(str); +		return NULL; +	} + +	dbus_message_iter_init_append(reply, &iter); +	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, +				DBUS_TYPE_BYTE_AS_STRING, &array_iter); + +	dbus_message_iter_append_fixed_array(&array_iter, +				DBUS_TYPE_BYTE, &ptr, sizeof(features)); + +	dbus_message_iter_close_container(&iter, &array_iter); + +	free(str); + +	return reply; +} + +static DBusMessage *adapter_get_remote_name(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	char filename[PATH_MAX + 1]; +	struct adapter *adapter = data; +	DBusMessage *reply = NULL; +	const char *peer_addr; +	bdaddr_t peer_bdaddr; +	char *str; + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &peer_addr, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (check_address(peer_addr) < 0) +		return invalid_args(msg); + +	/* check if it is in the cache */ +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "names"); + +	str = textfile_caseget(filename, peer_addr); + +	if (str) { +		reply = dbus_message_new_method_return(msg); +		if (!reply) { +			free(str); +			return NULL; +		} + +		/* send the cached name */ +		dbus_message_append_args(reply, DBUS_TYPE_STRING, &str, +						DBUS_TYPE_INVALID); + +		free(str); +		return reply; +	} + +	if (!adapter->up) +		return adapter_not_ready(msg); + +	/* If the discover process is not running, return an error */ +	if (!adapter->discov_active && !adapter->pdiscov_active) +		return not_available(msg); + +	/* Queue the request when there is a discovery running */ +	str2ba(peer_addr, &peer_bdaddr); +	found_device_add(&adapter->found_devices, &peer_bdaddr, 0, NAME_REQUIRED); + +        return g_dbus_create_error(msg, +			ERROR_INTERFACE ".RequestDeferred", +			"Request Deferred"); +} + +static DBusMessage *adapter_get_remote_alias(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	char str[249], *str_ptr = str, *addr_ptr; +	bdaddr_t bdaddr; +	int ecode; + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &addr_ptr, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (check_address(addr_ptr) < 0) +		return invalid_args(msg); + +	str2ba(addr_ptr, &bdaddr); + +	ecode = get_device_alias(adapter->dev_id, &bdaddr, str, sizeof(str)); +	if (ecode < 0) +		return not_available(msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *adapter_set_remote_alias(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	char *alias, *addr, *old_path, *new_path; +	bdaddr_t bdaddr; +	int ecode; + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &addr, +				DBUS_TYPE_STRING, &alias, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if ((strlen(alias) == 0) || (check_address(addr) < 0)) { +		error("Alias change failed: Invalid parameter"); +		return invalid_args(msg); +	} + +	str2ba(addr, &bdaddr); + +	ecode = set_device_alias(adapter->dev_id, &bdaddr, alias); +	if (ecode < 0) +		return failed_strerror(msg, -ecode); + +	resolve_paths(msg, &old_path, &new_path); + +	g_dbus_emit_signal(conn, old_path, +					ADAPTER_INTERFACE, "RemoteAliasChanged", +					DBUS_TYPE_STRING, &addr, +					DBUS_TYPE_STRING, &alias, +					DBUS_TYPE_INVALID); + +	if (new_path) { +		struct device *device; + +		device = adapter_find_device(adapter, addr); +		if (device) { +			dbus_connection_emit_property_changed(conn, +					device->path, DEVICE_INTERFACE, +					"Alias", DBUS_TYPE_STRING, &alias); +		} +	} + +	g_free(old_path); +	g_free(new_path); + +	return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_clear_remote_alias(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	char *addr_ptr; +	bdaddr_t bdaddr; +	int ecode, had_alias = 1; + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &addr_ptr, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (check_address(addr_ptr) < 0) { +		error("Alias clear failed: Invalid parameter"); +		return invalid_args(msg); +	} + +	str2ba(addr_ptr, &bdaddr); + +	ecode = get_device_alias(adapter->dev_id, &bdaddr, NULL, 0); +	if (ecode == -ENXIO) +		had_alias = 0; + +	ecode = set_device_alias(adapter->dev_id, &bdaddr, NULL); +	if (ecode < 0) +		return failed_strerror(msg, -ecode); + +	if (had_alias) +		g_dbus_emit_signal(conn, dbus_message_get_path(msg), +						ADAPTER_INTERFACE, +						"RemoteAliasCleared", +						DBUS_TYPE_STRING, &addr_ptr, +						DBUS_TYPE_INVALID); + +	return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_last_seen(DBusConnection *conn, +				DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	char filename[PATH_MAX + 1]; +	char *addr_ptr, *str; + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &addr_ptr, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (check_address(addr_ptr) < 0) +		return invalid_args(msg); + +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, +			"lastseen"); + +	str = textfile_caseget(filename, addr_ptr); +	if (!str) +		return not_available(msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) { +		free(str); +		return NULL; +	} + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &str, +					DBUS_TYPE_INVALID); + +	free(str); + +	return reply; +} + +static DBusMessage *adapter_last_used(DBusConnection *conn, +				DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	char filename[PATH_MAX + 1]; +	char *addr_ptr, *str; + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &addr_ptr, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (check_address(addr_ptr) < 0) +		return invalid_args(msg); + +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, +			"lastused"); + +	str = textfile_caseget(filename, addr_ptr); +	if (!str) +		return not_available(msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) { +		free(str); +		return NULL; +	} + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &str, +					DBUS_TYPE_INVALID); + +	free(str); + +	return reply; +} + +gboolean dc_pending_timeout_handler(void *data) +{ +	int dd; +	struct adapter *adapter = data; +	struct pending_dc_info *pending_dc = adapter->pending_dc; +	DBusMessage *reply; + +	dd = hci_open_dev(adapter->dev_id); + +	if (dd < 0) { +		error_no_such_adapter(pending_dc->conn, +				      pending_dc->msg); +		dc_pending_timeout_cleanup(adapter); +		return FALSE; +	} + +	/* Send the HCI disconnect command */ +	if (hci_disconnect(dd, htobs(pending_dc->conn_handle), +				HCI_OE_USER_ENDED_CONNECTION, +				500) < 0) { +		int err = errno; +		error("Disconnect failed"); +		error_failed_errno(pending_dc->conn, pending_dc->msg, err); +	} else { +		reply = dbus_message_new_method_return(pending_dc->msg); +		if (reply) { +			dbus_connection_send(pending_dc->conn, reply, NULL); +			dbus_message_unref(reply); +		} else +			error("Failed to allocate disconnect reply"); +	} + +	hci_close_dev(dd); +	dc_pending_timeout_cleanup(adapter); + +	return FALSE; +} + +void dc_pending_timeout_cleanup(struct adapter *adapter) +{ +	dbus_connection_unref(adapter->pending_dc->conn); +	dbus_message_unref(adapter->pending_dc->msg); +	g_free(adapter->pending_dc); +	adapter->pending_dc = NULL; +} + +static DBusMessage *adapter_dc_remote_device(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	GSList *l = adapter->active_conn; +	const char *peer_addr; +	bdaddr_t peer_bdaddr; + +	if (!adapter->up) +		return adapter_not_ready(msg); + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &peer_addr, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (check_address(peer_addr) < 0) +		return invalid_args(msg); + +	str2ba(peer_addr, &peer_bdaddr); + +	l = g_slist_find_custom(l, &peer_bdaddr, active_conn_find_by_bdaddr); +	if (!l) +		return g_dbus_create_error(msg, +				ERROR_INTERFACE ".NotConnected", +				"Device not connected"); + +	if (adapter->pending_dc) +		return in_progress(msg, "Disconnection in progress"); + +	adapter->pending_dc = g_new0(struct pending_dc_info, 1); + +	/* Start waiting... */ +	adapter->pending_dc->timeout_id = +		g_timeout_add(DC_PENDING_TIMEOUT, +			      dc_pending_timeout_handler, +			      adapter); + +	if (!adapter->pending_dc->timeout_id) { +		g_free(adapter->pending_dc); +		adapter->pending_dc = NULL; +		return NULL; +	} + +	adapter->pending_dc->conn = dbus_connection_ref(conn); +	adapter->pending_dc->msg = dbus_message_ref(msg); +	adapter->pending_dc->conn_handle = +		((struct active_conn_info *) l->data)->handle; + +	g_dbus_emit_signal(conn, dbus_message_get_path(msg), +					ADAPTER_INTERFACE, +					"RemoteDeviceDisconnectRequested", +					DBUS_TYPE_STRING, &peer_addr, +					DBUS_TYPE_INVALID); + +	return NULL; +} + +static void reply_authentication_failure(struct bonding_request_info *bonding) +{ +	DBusMessage *reply; +	int status; + +	status = bonding->hci_status ? +			bonding->hci_status : HCI_AUTHENTICATION_FAILURE; + +	reply = new_authentication_return(bonding->msg, status); +	if (reply) { +		dbus_connection_send(bonding->conn, reply, NULL); +		dbus_message_unref(reply); +	} +} + +struct device *adapter_find_device(struct adapter *adapter, const char *dest) +{ +	struct device *device; +	GSList *l; + +	if (!adapter) +		return NULL; + +	l = g_slist_find_custom(adapter->devices, +				dest, (GCompareFunc) device_address_cmp); +	if (!l) +		return NULL; + +	device = l->data; + +	return device; +} + +struct device *adapter_create_device(DBusConnection *conn, +				struct adapter *adapter, const char *address) +{ +	struct device *device; + +	debug("adapter_create_device(%s)", address); + +	device = device_create(conn, adapter, address); +	if (!device) +		return NULL; + +	device->temporary = TRUE; + +	adapter->devices = g_slist_append(adapter->devices, device); + +	return device; +} + +static DBusMessage *remove_bonding(DBusConnection *conn, DBusMessage *msg, +					const char *address, void *data) +{ +	struct adapter *adapter = data; +	struct device *device; +	char path[MAX_PATH_LENGTH], filename[PATH_MAX + 1]; +	char *str; +	bdaddr_t src, dst; +	GSList *l; +	int dev, err; +	gboolean paired; + +	str2ba(adapter->address, &src); +	str2ba(address, &dst); + +	dev = hci_open_dev(adapter->dev_id); +	if (dev < 0 && msg) +		return no_such_adapter(msg); + +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, +			"linkkeys"); + +	/* textfile_del doesn't return an error when the key is not found */ +	str = textfile_caseget(filename, address); +	paired = str ? TRUE : FALSE; +	g_free(str); + +	if (!paired && msg) { +		hci_close_dev(dev); +		return g_dbus_create_error(msg, +				ERROR_INTERFACE ".DoesNotExist", +				"Bonding does not exist"); +	} + +	/* Delete the link key from storage */ +	if (textfile_casedel(filename, address) < 0 && msg) { +		hci_close_dev(dev); +		err = errno; +		return failed_strerror(msg, err); +	} + +	/* Delete the link key from the Bluetooth chip */ +	hci_delete_stored_link_key(dev, &dst, 0, 1000); + +	/* find the connection */ +	l = g_slist_find_custom(adapter->active_conn, &dst, +				active_conn_find_by_bdaddr); +	if (l) { +		struct active_conn_info *con = l->data; +		/* Send the HCI disconnect command */ +		if ((hci_disconnect(dev, htobs(con->handle), +					HCI_OE_USER_ENDED_CONNECTION, 500) < 0) +					&& msg){ +			int err = errno; +			error("Disconnect failed"); +			hci_close_dev(dev); +			return failed_strerror(msg, err); +		} +	} + +	hci_close_dev(dev); + +	if (paired) { +		snprintf(path, MAX_PATH_LENGTH, BASE_PATH "/hci%d", +			adapter->dev_id); +		g_dbus_emit_signal(conn, path, +					ADAPTER_INTERFACE, "BondingRemoved", +					DBUS_TYPE_STRING, &address, +					DBUS_TYPE_INVALID); +	} + +	device = adapter_find_device(adapter, address); +	if (!device) +		goto proceed; + +	if (paired) { +		gboolean paired = FALSE; +		dbus_connection_emit_property_changed(conn, device->path, +					DEVICE_INTERFACE, "Paired", +					DBUS_TYPE_BOOLEAN, &paired); +	} + +proceed: +	if(!msg) +		goto done; + +	return dbus_message_new_method_return(msg); + +done: +	return NULL; +} + + +void adapter_remove_device(DBusConnection *conn, struct adapter *adapter, +				struct device *device) +{ +	bdaddr_t src; +	char path[MAX_PATH_LENGTH]; + +	str2ba(adapter->address, &src); +	delete_entry(&src, "profiles", device->address); + +	remove_bonding(conn, NULL, device->address, adapter); + +	if (!device->temporary) { +		snprintf(path, MAX_PATH_LENGTH, "/hci%d", adapter->dev_id); +		g_dbus_emit_signal(conn, path, +				ADAPTER_INTERFACE, +				"DeviceRemoved", +				DBUS_TYPE_OBJECT_PATH, &device->path, +				DBUS_TYPE_INVALID); +	} + +	if (device->agent) { +		agent_destroy(device->agent, FALSE); +		device->agent = NULL; +	} + +	adapter->devices = g_slist_remove(adapter->devices, device); + +	device_remove(conn, device); +} + +struct device *adapter_get_device(DBusConnection *conn, +				struct adapter *adapter, const gchar *address) +{ +	struct device *device; + +	debug("adapter_get_device(%s)", address); + +	if (!adapter) +		return NULL; + +	device = adapter_find_device(adapter, address); +	if (device) +		return device; + +	return adapter_create_device(conn, adapter, address); +} + +void remove_pending_device(struct adapter *adapter) +{ +	struct device *device; +	char address[18]; + +	ba2str(&adapter->bonding->bdaddr, address); +	device = adapter_find_device(adapter, address); +	if (!device) +		return; + +	if (device->temporary) +		adapter_remove_device(adapter->bonding->conn, adapter, device); +} + +static gboolean create_bonding_conn_complete(GIOChannel *io, GIOCondition cond, +						struct adapter *adapter) +{ +	struct hci_request rq; +	auth_requested_cp cp; +	evt_cmd_status rp; +	struct l2cap_conninfo cinfo; +	socklen_t len; +	int sk, dd, ret; + +	if (!adapter->bonding) { +		/* If we come here it implies a bug somewhere */ +		debug("create_bonding_conn_complete: no pending bonding!"); +		g_io_channel_close(io); +		g_io_channel_unref(io); +		return FALSE; +	} + +	if (cond & G_IO_NVAL) { +		error_authentication_canceled(adapter->bonding->conn, +						adapter->bonding->msg); +		goto cleanup; +	} + +	if (cond & (G_IO_HUP | G_IO_ERR)) { +		debug("Hangup or error on bonding IO channel"); + +		if (!adapter->bonding->auth_active) +			error_connection_attempt_failed(adapter->bonding->conn, +							adapter->bonding->msg, +							ENETDOWN); +		else +			reply_authentication_failure(adapter->bonding); + +		goto failed; +	} + +	sk = g_io_channel_unix_get_fd(io); + +	len = sizeof(ret); +	if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &ret, &len) < 0) { +		error("Can't get socket error: %s (%d)", +				strerror(errno), errno); +		error_failed_errno(adapter->bonding->conn, adapter->bonding->msg, +				errno); +		goto failed; +	} + +	if (ret != 0) { +		if (adapter->bonding->auth_active) +			reply_authentication_failure(adapter->bonding); +		else +			error_connection_attempt_failed(adapter->bonding->conn, +							adapter->bonding->msg, +							ret); +		goto failed; +	} + +	len = sizeof(cinfo); +	if (getsockopt(sk, SOL_L2CAP, L2CAP_CONNINFO, &cinfo, &len) < 0) { +		error("Can't get connection info: %s (%d)", +				strerror(errno), errno); +		error_failed_errno(adapter->bonding->conn, adapter->bonding->msg, +				errno); +		goto failed; +	} + +	dd = hci_open_dev(adapter->dev_id); +	if (dd < 0) { +		error_no_such_adapter(adapter->bonding->conn, +					adapter->bonding->msg); +		goto failed; +	} + +	memset(&rp, 0, sizeof(rp)); + +	memset(&cp, 0, sizeof(cp)); +	cp.handle = htobs(cinfo.hci_handle); + +	memset(&rq, 0, sizeof(rq)); +	rq.ogf    = OGF_LINK_CTL; +	rq.ocf    = OCF_AUTH_REQUESTED; +	rq.cparam = &cp; +	rq.clen   = AUTH_REQUESTED_CP_SIZE; +	rq.rparam = &rp; +	rq.rlen   = EVT_CMD_STATUS_SIZE; +	rq.event  = EVT_CMD_STATUS; + +	if (hci_send_req(dd, &rq, 500) < 0) { +		error("Unable to send HCI request: %s (%d)", +					strerror(errno), errno); +		error_failed_errno(adapter->bonding->conn, adapter->bonding->msg, +				errno); +		hci_close_dev(dd); +		goto failed; +	} + +	if (rp.status) { +		error("HCI_Authentication_Requested failed with status 0x%02x", +				rp.status); +		error_failed_errno(adapter->bonding->conn, adapter->bonding->msg, +				bt_error(rp.status)); +		hci_close_dev(dd); +		goto failed; +	} + +	hci_close_dev(dd); + +	adapter->bonding->auth_active = 1; + +	adapter->bonding->io_id = g_io_add_watch(io, +						G_IO_NVAL | G_IO_HUP | G_IO_ERR, +						(GIOFunc) create_bonding_conn_complete, +						adapter); + +	return FALSE; + +failed: +	g_io_channel_close(io); +	remove_pending_device(adapter); + +cleanup: +	g_dbus_remove_watch(adapter->bonding->conn, +				adapter->bonding->listener_id); +	bonding_request_free(adapter->bonding); +	adapter->bonding = NULL; + +	return FALSE; +} + +static void cancel_auth_request(struct pending_auth_info *auth, int dev_id) +{ +	int dd; + +	if (auth->replied) +		return; + +	dd = hci_open_dev(dev_id); +	if (dd < 0) { +		error("hci_open_dev: %s (%d)", strerror(errno), errno); +		return; +	} + +	switch (auth->type) { +	case AUTH_TYPE_PINCODE: +		hci_send_cmd(dd, OGF_LINK_CTL, OCF_PIN_CODE_NEG_REPLY, +				6, &auth->bdaddr); +		break; +	case AUTH_TYPE_CONFIRM: +		hci_send_cmd(dd, OGF_LINK_CTL, OCF_USER_CONFIRM_NEG_REPLY, +				6, &auth->bdaddr); +		break; +	case AUTH_TYPE_PASSKEY: +		hci_send_cmd(dd, OGF_LINK_CTL, OCF_USER_PASSKEY_NEG_REPLY, +				6, &auth->bdaddr); +		break; +	case AUTH_TYPE_NOTIFY: +		/* User Notify doesn't require any reply */ +		break; +	} + +	auth->replied = TRUE; + +	hci_close_dev(dd); +} + +static void create_bond_req_exit(void *user_data) +{ +	struct adapter *adapter = user_data; +	struct pending_auth_info *auth; +	char path[MAX_PATH_LENGTH]; + +	snprintf(path, sizeof(path), "%s/hci%d", BASE_PATH, adapter->dev_id); + +	debug("CreateConnection requestor exited before bonding was completed"); + +	cancel_passkey_agent_requests(adapter->passkey_agents, path, +					&adapter->bonding->bdaddr); +	release_passkey_agents(adapter, &adapter->bonding->bdaddr); + +	auth = adapter_find_auth_request(adapter, &adapter->bonding->bdaddr); +	if (auth) { +		cancel_auth_request(auth, adapter->dev_id); +		if (auth->agent) +			agent_cancel(auth->agent); +		adapter_remove_auth_request(adapter, &adapter->bonding->bdaddr); +	} + +	remove_pending_device(adapter); + +	g_io_channel_close(adapter->bonding->io); +	if (adapter->bonding->io_id) +		g_source_remove(adapter->bonding->io_id); +	bonding_request_free(adapter->bonding); +	adapter->bonding = NULL; +} + +static DBusMessage *create_bonding(DBusConnection *conn, DBusMessage *msg, +				const char *address, const char *agent_path, +				uint8_t capability, void *data) +{ +	char filename[PATH_MAX + 1]; +	char *str; +	struct adapter *adapter = data; +	struct bonding_request_info *bonding; +	bdaddr_t bdaddr; +	int sk; + +	str2ba(address, &bdaddr); + +	/* check if there is a pending discover: requested by D-Bus/non clients */ +	if (adapter->discov_active) +		return in_progress(msg, "Discover in progress"); + +	pending_remote_name_cancel(adapter); + +	if (adapter->bonding) +		return in_progress(msg, "Bonding in progress"); + +	if (adapter_find_auth_request(adapter, &bdaddr)) +		return in_progress(msg, "Bonding in progress"); + +	/* check if a link key already exists */ +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, +			"linkkeys"); + +	str = textfile_caseget(filename, address); +	if (str) { +		free(str); +		return g_dbus_create_error(msg, +				ERROR_INTERFACE ".AlreadyExists", +				"Bonding already exists"); +	} + +	sk = l2raw_connect(adapter->address, &bdaddr); +	if (sk < 0) +		return g_dbus_create_error(msg, +				ERROR_INTERFACE ".ConnectionAttemptFailed", +				"Connection attempt failed"); + +	bonding = bonding_request_new(conn, msg, adapter, address, agent_path, +					capability); +	if (!bonding) { +		close(sk); +		return NULL; +	} + +	bonding->io = g_io_channel_unix_new(sk); +	bonding->io_id = g_io_add_watch(bonding->io, +					G_IO_OUT | G_IO_NVAL | G_IO_HUP | G_IO_ERR, +					(GIOFunc) create_bonding_conn_complete, +					adapter); + +	bonding->listener_id = g_dbus_add_disconnect_watch(conn, +						dbus_message_get_sender(msg), +						create_bond_req_exit, adapter, +						NULL); + +	adapter->bonding = bonding; + +	return NULL; +} + +static DBusMessage *adapter_create_bonding(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	char *address; + +	if (!adapter->up) +		return adapter_not_ready(msg); + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &address, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (check_address(address) < 0) +		return invalid_args(msg); + +	return create_bonding(conn, msg, address, NULL, +				IO_CAPABILITY_INVALID, data); +} +static DBusMessage *adapter_cancel_bonding(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	const char *address; +	bdaddr_t bdaddr; +	struct bonding_request_info *bonding = adapter->bonding; +	struct pending_auth_info *auth_req; + +	if (!adapter->up) +		return adapter_not_ready(msg); + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &address, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (check_address(address) < 0) +		return invalid_args(msg); + +	str2ba(address, &bdaddr); +	if (!bonding || bacmp(&bonding->bdaddr, &bdaddr)) +		return not_in_progress(msg, "Bonding is not in progress"); + +	if (strcmp(dbus_message_get_sender(adapter->bonding->msg), +				dbus_message_get_sender(msg))) +		return not_authorized(msg); + +	adapter->bonding->cancel = 1; + +	auth_req = adapter_find_auth_request(adapter, &bdaddr); +	if (auth_req) { +		if (auth_req->replied) { +			/* +			 * If disconnect can't be applied and the PIN code +			 * request was already replied it doesn't make sense +			 * cancel the remote passkey: return not authorized. +			 */ +			g_io_channel_close(adapter->bonding->io); +			return not_authorized(msg); +		} + +		cancel_auth_request(auth_req, adapter->dev_id); +		if (auth_req->agent) +			agent_cancel(auth_req->agent); +		adapter_remove_auth_request(adapter, &bdaddr); +	} + +	g_io_channel_close(adapter->bonding->io); + +	return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_remove_bonding(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	char *address; + +	if (!adapter->up) +		return adapter_not_ready(msg); + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &address, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (check_address(address) < 0) +		return invalid_args(msg); + +	return remove_bonding(conn, msg, address, data); +} + +static DBusMessage *adapter_has_bonding(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	char filename[PATH_MAX + 1]; +	char *addr_ptr, *str; +	dbus_bool_t result; + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &addr_ptr, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (check_address(addr_ptr) < 0) +		return invalid_args(msg); + +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, +			"linkkeys"); + +	str = textfile_caseget(filename, addr_ptr); +	if (str) { +		result = TRUE; +		free(str); +	} else +		result = FALSE; + +	reply = dbus_message_new_method_return(msg); + +	dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &result, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static void list_bondings_do_append(char *key, char *value, void *data) +{ +	DBusMessageIter *iter = data; +	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &key); +} + +static DBusMessage *adapter_list_bondings(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessageIter iter; +	DBusMessageIter array_iter; +	DBusMessage *reply; +	char filename[PATH_MAX + 1]; + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return invalid_args(msg); + +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, +			"linkkeys"); + +	reply = dbus_message_new_method_return(msg); + +	dbus_message_iter_init_append(reply, &iter); + +	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, +				DBUS_TYPE_STRING_AS_STRING, &array_iter); + +	textfile_foreach(filename, list_bondings_do_append, &array_iter); + +	dbus_message_iter_close_container(&iter, &array_iter); + +	return reply; +} + +static DBusMessage *adapter_get_pin_code_length(DBusConnection *conn, +							DBusMessage *msg, +							void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	bdaddr_t local, peer; +	char *addr_ptr; +	uint8_t length; +	int len; + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &addr_ptr, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (check_address(addr_ptr) < 0) +		return invalid_args(msg); + +	str2ba(adapter->address, &local); + +	str2ba(addr_ptr, &peer); + +	len = read_pin_length(&local, &peer); +	if (len < 0) +		return g_dbus_create_error(msg, +				ERROR_INTERFACE ".DoesNotExist", +				"Record does not exist"); + +	reply = dbus_message_new_method_return(msg); + +	length = len; + +	dbus_message_append_args(reply, DBUS_TYPE_BYTE, &length, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *adapter_get_encryption_key_size(DBusConnection *conn, +							DBusMessage *msg, +							void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	bdaddr_t bdaddr; +	char *addr_ptr; +	uint8_t size; +	int val; + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &addr_ptr, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (check_address(addr_ptr) < 0) +		return invalid_args(msg); + +	str2ba(addr_ptr, &bdaddr); + +	val = get_encryption_key_size(adapter->dev_id, &bdaddr); +	if (val < 0) +		return failed_strerror(msg, -val); + +	reply = dbus_message_new_method_return(msg); + +	size = val; + +	dbus_message_append_args(reply, DBUS_TYPE_BYTE, &size, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static void periodic_discover_req_exit(void *user_data) +{ +	struct adapter *adapter = user_data; + +	debug("PeriodicDiscovery requestor exited"); + +	/* Cleanup the discovered devices list and send the cmd to exit from +	 * periodic inquiry or cancel remote name request. The return value can +	 * be ignored. */ + +	cancel_periodic_discovery(adapter); +} + +static DBusMessage *adapter_start_periodic(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	periodic_inquiry_cp cp; +	struct hci_request rq; +	struct adapter *adapter = data; +	uint8_t lap[3] = { 0x33, 0x8b, 0x9e }; +	uint8_t status; +	int dd; + +	if (!adapter->up) +		return adapter_not_ready(msg); + +	if (dbus_message_is_method_call(msg, ADAPTER_INTERFACE, +				"StartPeriodicDiscovery")) { +		if (!dbus_message_has_signature(msg, +					DBUS_TYPE_INVALID_AS_STRING)) +			return invalid_args(msg); +	} + +	if (adapter->discov_active || adapter->pdiscov_active) +		return in_progress(msg, "Discover in progress"); + +	pending_remote_name_cancel(adapter); + +	dd = hci_open_dev(adapter->dev_id); +	if (dd < 0) +		return no_such_adapter(msg); + +	memset(&cp, 0, sizeof(cp)); +	memcpy(&cp.lap, lap, 3); +	cp.max_period = htobs(24); +	cp.min_period = htobs(16); +	cp.length  = 0x08; +	cp.num_rsp = 0x00; + +	memset(&rq, 0, sizeof(rq)); +	rq.ogf    = OGF_LINK_CTL; +	rq.ocf    = OCF_PERIODIC_INQUIRY; +	rq.cparam = &cp; +	rq.clen   = PERIODIC_INQUIRY_CP_SIZE; +	rq.rparam = &status; +	rq.rlen   = sizeof(status); +	rq.event  = EVT_CMD_COMPLETE; + +	if (hci_send_req(dd, &rq, 1000) < 0) { +		int err = errno; +		error("Unable to start periodic inquiry: %s (%d)", +				strerror(errno), errno); +		hci_close_dev(dd); +		return failed_strerror(msg, err); +	} + +	if (status) { +		error("HCI_Periodic_Inquiry_Mode failed with status 0x%02x", +				status); +		hci_close_dev(dd); +		return failed_strerror(msg, bt_error(status)); +	} + +	adapter->pdiscov_requestor = g_strdup(dbus_message_get_sender(msg)); + +	if (adapter->pdiscov_resolve_names) +		adapter->discov_type = PERIODIC_INQUIRY | RESOLVE_NAME; +	else +		adapter->discov_type = PERIODIC_INQUIRY; + +	hci_close_dev(dd); + +	/* track the request owner to cancel it automatically if the owner +	 * exits */ +	adapter->pdiscov_listener = g_dbus_add_disconnect_watch(conn, +						dbus_message_get_sender(msg), +						periodic_discover_req_exit, +						adapter, NULL); + +	return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_stop_periodic(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	int err; + +	if (!adapter->up) +		return adapter_not_ready(msg); + +	if (dbus_message_is_method_call(msg, ADAPTER_INTERFACE, +				"StopPeriodicDiscovery")) { +		if (!dbus_message_has_signature(msg, +					DBUS_TYPE_INVALID_AS_STRING)) +			return invalid_args(msg); +	} + +	if (!adapter->pdiscov_active) +		return g_dbus_create_error(msg, +				ERROR_INTERFACE ".NotAuthorized", +				"Not authorized"); +	/* +	 * Cleanup the discovered devices list and send the cmd to exit +	 * from periodic inquiry mode or cancel remote name request. +	 */ +	err = cancel_periodic_discovery(adapter); +	if (err < 0) { +		if (err == -ENODEV) +			return no_such_adapter(msg); + +		else +			return failed_strerror(msg, -err); +	} + +	return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_is_periodic(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	DBusMessage *reply; +	struct adapter *adapter = data; +	dbus_bool_t active = adapter->pdiscov_active; + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &active, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *adapter_set_pdiscov_resolve(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	dbus_bool_t resolve; + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_BOOLEAN, &resolve, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	debug("SetPeriodicDiscoveryNameResolving(%s)", +			resolve ? "TRUE" : "FALSE"); + +	adapter->pdiscov_resolve_names = resolve; + +	if (adapter->pdiscov_active) { +		if (resolve) +			adapter->discov_type |= RESOLVE_NAME; +		else +			adapter->discov_type &= ~RESOLVE_NAME; +	} + +	return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_get_pdiscov_resolve(DBusConnection *conn, +							DBusMessage *msg, +							void *data) +{ +	DBusMessage *reply; +	struct adapter *adapter = data; +	dbus_bool_t resolve = adapter->pdiscov_resolve_names; + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return invalid_args(msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &resolve, +					DBUS_TYPE_INVALID); + +	return reply; +} + +static void discover_devices_req_exit(void *user_data) +{ +	struct adapter *adapter = user_data; + +	debug("DiscoverDevices requestor exited"); + +	/* Cleanup the discovered devices list and send the command to cancel +	 * inquiry or cancel remote name request. The return can be ignored. */ +	cancel_discovery(adapter); +} + +static DBusMessage *adapter_discover_devices(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	const char *method; +	inquiry_cp cp; +	evt_cmd_status rp; +	struct hci_request rq; +	struct adapter *adapter = data; +	uint8_t lap[3] = { 0x33, 0x8b, 0x9e }; +	int dd; + +	if (!adapter->up) +		return adapter_not_ready(msg); + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return invalid_args(msg); + +	if (adapter->discov_active) +		return in_progress(msg, "Discover in progress"); + +	pending_remote_name_cancel(adapter); + +	if (adapter->bonding) +		return in_progress(msg, "Bonding in progress"); + +	dd = hci_open_dev(adapter->dev_id); +	if (dd < 0) +		return no_such_adapter(msg); + +	memset(&cp, 0, sizeof(cp)); +	memcpy(&cp.lap, lap, 3); +	cp.length  = 0x08; +	cp.num_rsp = 0x00; + +	memset(&rq, 0, sizeof(rq)); +	rq.ogf    = OGF_LINK_CTL; +	rq.ocf    = OCF_INQUIRY; +	rq.cparam = &cp; +	rq.clen   = INQUIRY_CP_SIZE; +	rq.rparam = &rp; +	rq.rlen   = EVT_CMD_STATUS_SIZE; +	rq.event  = EVT_CMD_STATUS; + +	if (hci_send_req(dd, &rq, 500) < 0) { +		int err = errno; +		error("Unable to start inquiry: %s (%d)", +				strerror(errno), errno); +		hci_close_dev(dd); +		return failed_strerror(msg, err); +	} + +	if (rp.status) { +		error("HCI_Inquiry command failed with status 0x%02x", +				rp.status); +		hci_close_dev(dd); +		return failed_strerror(msg, bt_error(rp.status)); +	} + +	method = dbus_message_get_member(msg); +	if (strcmp("DiscoverDevicesWithoutNameResolving", method) == 0) +		adapter->discov_type |= STD_INQUIRY; +	else +		adapter->discov_type |= (STD_INQUIRY | RESOLVE_NAME); + +	adapter->discov_requestor = g_strdup(dbus_message_get_sender(msg)); + + +	hci_close_dev(dd); + +	/* track the request owner to cancel it automatically if the owner +	 * exits */ +	adapter->discov_listener = g_dbus_add_disconnect_watch(conn, +						dbus_message_get_sender(msg), +						discover_devices_req_exit, +						adapter, NULL); + +	return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_cancel_discovery(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	int err; + +	if (!adapter->up) +		return adapter_not_ready(msg); + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return invalid_args(msg); + +	/* is there discover pending? or discovery cancel was requested +	 * previously */ +	if (!adapter->discov_active || adapter->discovery_cancel) +		return g_dbus_create_error(msg, +				ERROR_INTERFACE ".NotAuthorized", +				"Not Authorized"); + +	/* only the discover requestor can cancel the inquiry process */ +	if (!adapter->discov_requestor || +			strcmp(adapter->discov_requestor, dbus_message_get_sender(msg))) +		return g_dbus_create_error(msg, +				ERROR_INTERFACE ".NotAuthorized", +				"Not Authorized"); + +	/* Cleanup the discovered devices list and send the cmd to cancel +	 * inquiry or cancel remote name request */ +	err = cancel_discovery(adapter); +	if (err < 0) { +		if (err == -ENODEV) +			return no_such_adapter(msg); +		else +			return failed_strerror(msg, -err); +	} + +	/* Reply before send DiscoveryCompleted */ +	adapter->discovery_cancel = dbus_message_ref(msg); + +	return NULL; +} + +struct remote_device_list_t { +	GSList *list; +	time_t time; +}; + +static void list_remote_devices_do_append(char *key, char *value, void *data) +{ +	struct remote_device_list_t *param = data; +	char *address; +	struct tm date; + +	if (g_slist_find_custom(param->list, key, (GCompareFunc) strcasecmp)) +		return; + +	if (param->time){ +		strptime(value, "%Y-%m-%d %H:%M:%S %Z", &date); +		if (difftime(mktime(&date), param->time) < 0) +			return; +	} + +	address = g_strdup(key); + +	param->list = g_slist_append(param->list, address); +} + +static void remote_devices_do_append(void *data, void *user_data) +{ +	DBusMessageIter *iter = user_data; + +	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &data); +} + +static DBusMessage *adapter_list_remote_devices(DBusConnection *conn, +							DBusMessage *msg, +							void *data) +{ +	struct adapter *adapter = data; +	DBusMessageIter iter; +	DBusMessageIter array_iter; +	DBusMessage *reply; +	char filename[PATH_MAX + 1]; +	struct remote_device_list_t param = { NULL, 0 }; + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return invalid_args(msg); + +	/* Add Bonded devices to the list */ +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "linkkeys"); +	textfile_foreach(filename, list_remote_devices_do_append, ¶m); + +	/* Add Trusted devices to the list */ +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "trusts"); +	textfile_foreach(filename, list_remote_devices_do_append, ¶m); + +	/* Add Last Used devices to the list */ +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "lastused"); +	textfile_foreach(filename, list_remote_devices_do_append, ¶m); + +	reply = dbus_message_new_method_return(msg); + +	dbus_message_iter_init_append(reply, &iter); + +	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, +				DBUS_TYPE_STRING_AS_STRING, &array_iter); + +	g_slist_foreach(param.list, remote_devices_do_append, &array_iter); + +	g_slist_foreach(param.list, (GFunc) free, NULL); +	g_slist_free(param.list); + +	dbus_message_iter_close_container(&iter, &array_iter); + +	return reply; +} + +static void append_connected(struct active_conn_info *dev, GSList *list) +{ +	char address[18]; + +	ba2str(&dev->bdaddr, address); +	if (g_slist_find_custom(list, address, (GCompareFunc) strcasecmp)) +		return; + +	list = g_slist_append(list, g_strdup(address)); +} + +static DBusMessage *adapter_list_recent_remote_devices(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	struct tm date; +	const char *string; +	DBusMessageIter iter; +	DBusMessageIter array_iter; +	DBusMessage *reply; +	char filename[PATH_MAX + 1]; +	struct remote_device_list_t param = { NULL, 0 }; +	int len; + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &string, +				DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	/* Date format is "YYYY-MM-DD HH:MM:SS GMT" */ +	len = strlen(string); +	if (len && (strptime(string, "%Y-%m-%d %H:%M:%S", &date) == NULL)) +		return invalid_args(msg); + +	/* Bonded and trusted: mandatory entries(no matter the date/time) */ +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "linkkeys"); +	textfile_foreach(filename, list_remote_devices_do_append, ¶m); + +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "trusts"); +	textfile_foreach(filename, list_remote_devices_do_append, ¶m); + +	/* Last seen/used: append devices since the date informed */ +	if (len) +		param.time = mktime(&date); + +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "lastseen"); +	textfile_foreach(filename, list_remote_devices_do_append, ¶m); + +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "lastused"); +	textfile_foreach(filename, list_remote_devices_do_append, ¶m); + +	/* connected: force appending connected devices, lastused might not match */ +	g_slist_foreach(adapter->active_conn, (GFunc) append_connected, param.list); + +	reply = dbus_message_new_method_return(msg); + +	dbus_message_iter_init_append(reply, &iter); + +	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, +				DBUS_TYPE_STRING_AS_STRING, &array_iter); + +	g_slist_foreach(param.list, remote_devices_do_append, &array_iter); + +	g_slist_foreach(param.list, (GFunc) free, NULL); +	g_slist_free(param.list); + +	dbus_message_iter_close_container(&iter, &array_iter); + +	return reply; +} + + +static DBusMessage *adapter_set_trusted(DBusConnection *conn, +				DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	bdaddr_t local; +	const char *address; +	char *old_path, *new_path; + +	if (!dbus_message_get_args(msg, NULL, +			DBUS_TYPE_STRING, &address, +			DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (check_address(address) < 0) +		return invalid_args(msg); + +	str2ba(adapter->address, &local); + +	write_trust(&local, address, GLOBAL_TRUST, TRUE); + +	resolve_paths(msg, &old_path, &new_path); + +	g_dbus_emit_signal(conn, old_path, +					ADAPTER_INTERFACE, "TrustAdded", +					DBUS_TYPE_STRING, &address, +					DBUS_TYPE_INVALID); + +	if (new_path) { +		struct device *device; +		gboolean trust = TRUE; + +		device = adapter_find_device(adapter, address); +		if (device) { +			dbus_connection_emit_property_changed(conn, +					device->path, DEVICE_INTERFACE, +					"Trusted", DBUS_TYPE_BOOLEAN, &trust); +		} +	} + +	return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_is_trusted(DBusConnection *conn, +				DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	const char *address; +	dbus_bool_t trusted; +	bdaddr_t local; + +	if (!dbus_message_get_args(msg, NULL, +			DBUS_TYPE_STRING, &address, +			DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (check_address(address) < 0) +		return invalid_args(msg); + +	str2ba(adapter->address, &local); + +	trusted = read_trust(&local, address, GLOBAL_TRUST); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, +				DBUS_TYPE_BOOLEAN, &trusted, +				DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *adapter_remove_trust(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	const char *address; +	bdaddr_t local; +	char *old_path, *new_path; + +	if (!dbus_message_get_args(msg, NULL, +			DBUS_TYPE_STRING, &address, +			DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	if (check_address(address) < 0) +		return invalid_args(msg); + +	str2ba(adapter->address, &local); + +	write_trust(&local, address, GLOBAL_TRUST, FALSE); + +	resolve_paths(msg, &old_path, &new_path); + +	g_dbus_emit_signal(conn, old_path, +					ADAPTER_INTERFACE, "TrustRemoved", +					DBUS_TYPE_STRING, &address, +					DBUS_TYPE_INVALID); + +	if (new_path) { +		struct device *device; +		gboolean trust = FALSE; + +		device = adapter_find_device(adapter, address); +		if (device) { +			dbus_connection_emit_property_changed(conn, +					device->path, DEVICE_INTERFACE, +					"Trusted", DBUS_TYPE_BOOLEAN, &trust); +		} +	} + +	return dbus_message_new_method_return(msg); +} + +static DBusMessage *adapter_list_trusts(DBusConnection *conn, +						DBusMessage *msg, +						void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	GSList *trusts, *l; +	char **addrs; +	bdaddr_t local; +	int len; + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	str2ba(adapter->address, &local); + +	trusts = list_trusts(&local, GLOBAL_TRUST); + +	addrs = g_new(char *, g_slist_length(trusts)); + +	for (l = trusts, len = 0; l; l = l->next, len++) +		addrs[len] = l->data; + +	dbus_message_append_args(reply, +			DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, +			&addrs, len, +			DBUS_TYPE_INVALID); + +	g_free(addrs); +	g_slist_foreach(trusts, (GFunc) g_free, NULL); +	g_slist_free(trusts); + +	return reply; +} + +static DBusMessage *get_properties(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	const char *property; +	DBusMessage *reply; +	DBusMessageIter iter; +	DBusMessageIter dict; +	bdaddr_t ba; +	char str[249]; + +	if (check_address(adapter->address) < 0) +		return adapter_not_ready(msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_iter_init_append(reply, &iter); + +	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, +			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING +			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING +			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + +	/* Address */ +	property = adapter->address; +	dbus_message_iter_append_dict_entry(&dict, "Address", +			DBUS_TYPE_STRING, &property); + +	/* Name */ +	memset(str, 0, sizeof(str)); +	property = str; +	str2ba(adapter->address, &ba); + +	if (!read_local_name(&ba, str)) +		dbus_message_iter_append_dict_entry(&dict, "Name", +			DBUS_TYPE_STRING, &property); + +	/* Mode */ +	property = mode2str(adapter->mode); + +	dbus_message_iter_append_dict_entry(&dict, "Mode", +			DBUS_TYPE_STRING, &property); + +	/* DiscoverableTimeout */ +	dbus_message_iter_append_dict_entry(&dict, "DiscoverableTimeout", +				DBUS_TYPE_UINT32, &adapter->discov_timeout); + +	/* PeriodicDiscovery */ +	dbus_message_iter_append_dict_entry(&dict, "PeriodicDiscovery", +				DBUS_TYPE_BOOLEAN, &adapter->pdiscov_active); + +	dbus_message_iter_close_container(&iter, &dict); + +	return reply; +} + +static DBusMessage *set_property(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessageIter iter; +	DBusMessageIter sub; +	const char *property; + +	if (!dbus_message_iter_init(msg, &iter)) +		return invalid_args(msg); + +	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) +		return invalid_args(msg); + +	dbus_message_iter_get_basic(&iter, &property); +	dbus_message_iter_next(&iter); + +	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) +		return invalid_args(msg); +	dbus_message_iter_recurse(&iter, &sub); + +	if (g_str_equal("Name", property)) { +		const char *name; + +		if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) +			return invalid_args(msg); +		dbus_message_iter_get_basic(&sub, &name); + +		return set_name(conn, msg, name, data); +	} else if (g_str_equal("DiscoverableTimeout", property)) { +		uint32_t timeout; + +		if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT32) +			return invalid_args(msg); +		dbus_message_iter_get_basic(&sub, &timeout); + +		return set_discoverable_timeout(conn, msg, timeout, data); +	} else if (g_str_equal("PeriodicDiscovery", property)) { +		dbus_bool_t value; + +		if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN) +			return invalid_args(msg); +		dbus_message_iter_get_basic(&sub, &value); + +		if (value) +			return adapter_start_periodic(conn, msg, data); +		else +			return adapter_stop_periodic(conn, msg, data); +	} else if (g_str_equal("Mode", property)) { +		const char *mode; + +		if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) +			return invalid_args(msg); + +		dbus_message_iter_get_basic(&sub, &mode); + +		adapter->global_mode = str2mode(adapter->address, mode); + +		if (adapter->global_mode == adapter->mode) +			return dbus_message_new_method_return(msg); + +		if (adapter->sessions && adapter->global_mode < adapter->mode) +			return confirm_mode(conn, msg, mode, data); + +		return set_mode(conn, msg, str2mode(adapter->address, mode), +				data); +	} + +	return invalid_args(msg); +} + +static void session_exit(void *data) +{ +	struct mode_req *req = data; +	struct adapter *adapter = req->adapter; + +	adapter->sessions = g_slist_remove(adapter->sessions, req); + +	if (!adapter->sessions) { +		debug("Falling back to '%s' mode", mode2str(adapter->global_mode)); +		/* FIXME: fallback to previous mode +		set_mode(req->conn, req->msg, adapter->global_mode, adapter); +		*/ +	} +	dbus_connection_unref(req->conn); +	dbus_message_unref(req->msg); +	g_free(req); +} + +static DBusMessage *request_mode(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	const char *mode; +	struct adapter *adapter = data; +	struct mode_req *req; +	uint8_t new_mode; +	int ret; + +	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &mode, +						DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	new_mode = str2mode(adapter->address, mode); +	if (new_mode != MODE_CONNECTABLE && new_mode != MODE_DISCOVERABLE) +		return invalid_args(msg); + +	if (!adapter->agent) +		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", +				"No agent registered"); + +	if (g_slist_find_custom(adapter->sessions, msg, +			(GCompareFunc) find_session)) +		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", +				"Mode already requested"); + +	req = g_new0(struct mode_req, 1); +	req->adapter = adapter; +	req->conn = dbus_connection_ref(conn); +	req->msg = dbus_message_ref(msg); +	req->mode = new_mode; +	req->id = g_dbus_add_disconnect_watch(conn, +					dbus_message_get_sender(msg), +					session_exit, req, NULL); + +	if (!adapter->sessions) +		adapter->global_mode = adapter->mode; +	adapter->sessions = g_slist_append(adapter->sessions, req); + +	/* No need to change mode */ +	if (adapter->mode >= new_mode) +		return dbus_message_new_method_return(msg); + +	ret = agent_confirm_mode_change(adapter->agent, mode, confirm_mode_cb, +					req); +	if (ret < 0) { +		dbus_message_unref(req->msg); +		g_dbus_remove_watch(req->conn, req->id); +		dbus_connection_unref(req->conn); +		g_free(req); +		return invalid_args(msg); +	} + +	return NULL; +} + +static DBusMessage *release_mode(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	GSList *l; + +	l = g_slist_find_custom(adapter->sessions, msg, +			(GCompareFunc) find_session); +	if (!l) +		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", +				"No Mode to release"); + +	session_exit(l->data); + +	return dbus_message_new_method_return(msg); +} + +static DBusMessage *list_devices(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	GSList *l; +	DBusMessageIter iter; +	DBusMessageIter array_iter; + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return invalid_args(msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_iter_init_append(reply, &iter); +	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, +				DBUS_TYPE_OBJECT_PATH_AS_STRING, &array_iter); + +	for (l = adapter->devices; l; l = l->next) { +		struct device *device = l->data; + +		if (device->temporary) +			continue; + +		dbus_message_iter_append_basic(&array_iter, +				DBUS_TYPE_OBJECT_PATH, &device->path); +	} + +	dbus_message_iter_close_container(&iter, &array_iter); + +	return reply; +} + +static DBusMessage *create_device(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	struct device *device; +	const gchar *address; + +	if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address, +						DBUS_TYPE_INVALID) == FALSE) +		return invalid_args(msg); + +	if (check_address(address) < 0) +		return invalid_args(msg); + +	if (adapter_find_device(adapter, address)) +		return g_dbus_create_error(msg, +				ERROR_INTERFACE ".AlreadyExists", +				"Device already exists"); + +	debug("create_device(%s)", address); + +	device = device_create(conn, adapter, address); +	if (!device) +		return NULL; + +	device->temporary = FALSE; + +	device_browse(device, conn, msg, NULL); + +	adapter->devices = g_slist_append(adapter->devices, device); + +	return NULL; +} + +static uint8_t parse_io_capability(const char *capability) +{ +	if (g_str_equal(capability, "")) +		return IO_CAPABILITY_DISPLAYYESNO; +	if (g_str_equal(capability, "DisplayOnly")) +		return IO_CAPABILITY_DISPLAYONLY; +	if (g_str_equal(capability, "DisplayYesNo")) +		return IO_CAPABILITY_DISPLAYYESNO; +	if (g_str_equal(capability, "KeyboardOnly")) +		return IO_CAPABILITY_KEYBOARDONLY; +	if (g_str_equal(capability, "NoInputOutput")) +		return IO_CAPABILITY_NOINPUTOUTPUT; +	return IO_CAPABILITY_INVALID; +} + +static DBusMessage *create_paired_device(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	const gchar *address, *agent_path, *capability; +	uint8_t cap; + +	if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address, +					DBUS_TYPE_OBJECT_PATH, &agent_path, +					DBUS_TYPE_STRING, &capability, +						DBUS_TYPE_INVALID) == FALSE) +		return invalid_args(msg); + +	if (check_address(address) < 0) +		return invalid_args(msg); + +	cap = parse_io_capability(capability); +	if (cap == IO_CAPABILITY_INVALID) +		return invalid_args(msg); + +	return create_bonding(conn, msg, address, agent_path, cap, data); +} + +static gint device_path_cmp(struct device *device, const gchar *path) +{ +	return strcasecmp(device->path, path); +} + +static DBusMessage *remove_device(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	struct device *device; +	const char *path; +	GSList *l; + +	if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, +						DBUS_TYPE_INVALID) == FALSE) +		return invalid_args(msg); + +	l = g_slist_find_custom(adapter->devices, +			path, (GCompareFunc) device_path_cmp); +	if (!l) +		return g_dbus_create_error(msg, +				ERROR_INTERFACE ".DoesNotExist", +				"Device does not exist"); +	device = l->data; + +	if (device->temporary || device->discov_active) +		return g_dbus_create_error(msg, +				ERROR_INTERFACE ".DoesNotExist", +				"Device creation in progress"); + +	adapter_remove_device(conn, adapter, device); + +	return dbus_message_new_method_return(msg); +} + +static DBusMessage *find_device(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	struct device *device; +	DBusMessage *reply; +	const gchar *address; +	GSList *l; + +	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &address, +						DBUS_TYPE_INVALID)) +		return invalid_args(msg); + +	l = g_slist_find_custom(adapter->devices, +			address, (GCompareFunc) device_address_cmp); +	if (!l) +		return g_dbus_create_error(msg, +				ERROR_INTERFACE ".DoesNotExist", +				"Device does not exist"); + +	device = l->data; + +	if (device->temporary) +		return g_dbus_create_error(msg, +				ERROR_INTERFACE ".DoesNotExist", +				"Device creation in progress"); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, +				DBUS_TYPE_OBJECT_PATH, &device->path, +				DBUS_TYPE_INVALID); + +	return reply; +} + +static void agent_removed(struct agent *agent, struct adapter *adapter) +{ +	struct pending_auth_info *auth; +	GSList *l; + +	adapter->agent = NULL; + +	l = g_slist_find_custom(adapter->auth_reqs, agent, +					auth_info_agent_cmp); +	if (!l) +		return; + +	auth = l->data; +	auth->agent = NULL; +} + +static DBusMessage *register_agent(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	const char *path, *name, *capability; +	struct agent *agent; +	struct 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 g_dbus_create_error(msg, +				ERROR_INTERFACE ".AlreadyExists", +				"Agent already exists"); + +	cap = parse_io_capability(capability); +	if (cap == IO_CAPABILITY_INVALID) +		return invalid_args(msg); + +	name = dbus_message_get_sender(msg); + +	agent = agent_create(adapter, name, path, cap, +				(agent_remove_cb) agent_removed, adapter); +	if (!agent) +		return g_dbus_create_error(msg, +				ERROR_INTERFACE ".Failed", +				"Failed to create a new agent"); + +	adapter->agent = agent; + +	debug("Agent registered for hci%d at %s:%s", adapter->dev_id, name, +			path); + +	return dbus_message_new_method_return(msg); +} + +static DBusMessage *unregister_agent(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	const char *path, *name; +	struct adapter *adapter = data; + +	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, +						DBUS_TYPE_INVALID)) +		return NULL; + +	name = dbus_message_get_sender(msg); + +	if (!adapter->agent || !agent_matches(adapter->agent, name, path)) +		return g_dbus_create_error(msg, +				ERROR_INTERFACE ".DoesNotExist", +				"No such agent"); + +	agent_destroy(adapter->agent, FALSE); +	adapter->agent = NULL; + +	return dbus_message_new_method_return(msg); +} + +static DBusMessage *add_service_record(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	const char *sender, *record; +	dbus_uint32_t handle; +	bdaddr_t src; +	int err; + +	if (dbus_message_get_args(msg, NULL, +			DBUS_TYPE_STRING, &record, DBUS_TYPE_INVALID) == FALSE) +		return NULL; + +	sender = dbus_message_get_sender(msg); +	str2ba(adapter->address, &src); +	err = add_xml_record(conn, sender, &src, record, &handle); +	if (err < 0) +		return failed_strerror(msg, err); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, DBUS_TYPE_UINT32, &handle, +							DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *update_service_record(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	bdaddr_t src; + +	str2ba(adapter->address, &src); + +	return update_xml_record(conn, msg, &src); +} + +static DBusMessage *remove_service_record(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	dbus_uint32_t handle; +	const char *sender; + +	if (dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &handle, +						DBUS_TYPE_INVALID) == FALSE) +		return NULL; + +	sender = dbus_message_get_sender(msg); + +	if (remove_record(conn, sender, handle) < 0) +		return not_available(msg); + +	return dbus_message_new_method_return(msg); +} + +static DBusMessage *request_authorization(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	/* FIXME implement the request */ + +	return NULL; +} + +static DBusMessage *cancel_authorization(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	/* FIXME implement cancel request */ + +	return dbus_message_new_method_return(msg); +} + +/* BlueZ 4.0 API */ +static GDBusMethodTable adapter_methods[] = { +	{ "GetProperties",	"",	"a{sv}",get_properties		}, +	{ "SetProperty",	"sv",	"",	set_property, +						G_DBUS_METHOD_FLAG_ASYNC}, +	{ "RequestMode",	"s",	"",	request_mode, +						G_DBUS_METHOD_FLAG_ASYNC}, +	{ "ReleaseMode",	"",	"",	release_mode		}, +	{ "DiscoverDevices",	"",	"",	adapter_discover_devices}, +	{ "CancelDiscovery",	"",	"",	adapter_cancel_discovery, +						G_DBUS_METHOD_FLAG_ASYNC}, +	{ "ListDevices",	"",	"ao",	list_devices		}, +	{ "CreateDevice",	"s",	"o",	create_device, +						G_DBUS_METHOD_FLAG_ASYNC}, +	{ "CreatePairedDevice",	"sos",	"o",	create_paired_device, +						G_DBUS_METHOD_FLAG_ASYNC}, +	{ "RemoveDevice",	"o",	"",	remove_device		}, +	{ "FindDevice",		"s",	"o",	find_device		}, +	{ "RegisterAgent",	"os",	"",	register_agent		}, +	{ "UnregisterAgent",	"o",	"",	unregister_agent	}, +	{ "AddServiceRecord",	"s",	"u",	add_service_record	}, +	{ "UpdateServiceRecord","us",	"",	update_service_record	}, +	{ "RemoveServiceRecord","u",	"",	remove_service_record	}, +	{ "RequestAuthorization","su",	"",	request_authorization, +						G_DBUS_METHOD_FLAG_ASYNC}, +	{ "CancelAuthorization","",	"",	cancel_authorization	}, +	{ } +}; + +/* Deprecated */ +static GDBusMethodTable old_adapter_methods[] = { +	{ "GetInfo",			"",	"a{sv}", +						adapter_get_info		}, +	{ "GetAddress",			"",	"s", +						adapter_get_address		}, +	{ "GetVersion",			"",	"s", +						adapter_get_version		}, +	{ "GetRevision",		"",	"s", +						adapter_get_revision		}, +	{ "GetManufacturer",		"",	"s", +						adapter_get_manufacturer	}, +	{ "GetCompany",			"",	"s", +						adapter_get_company		}, +	{ "ListAvailableModes",		"",	"as", +						adapter_list_modes		}, +	{ "GetMode",			"",	"s", +						adapter_get_mode		}, +	{ "SetMode",			"s",	"", +						adapter_set_mode		}, +	{ "GetDiscoverableTimeout",	"",	"u", +						adapter_get_discoverable_to	}, +	{ "SetDiscoverableTimeout",	"u",	"", +						adapter_set_discoverable_to	}, +	{ "IsConnectable",		"",	"b", +						adapter_is_connectable		}, +	{ "IsDiscoverable",		"",	"b", +						adapter_is_discoverable		}, +	{ "IsConnected",		"s",	"b", +						adapter_is_connected		}, +	{ "ListConnections",		"",	"as", +						adapter_list_connections	}, +	{ "GetMajorClass",		"",	"s", +						adapter_get_major_class		}, +	{ "ListAvailableMinorClasses",	"",	"as", +						adapter_list_minor_classes	}, +	{ "GetMinorClass",		"",	"s", +						adapter_get_minor_class		}, +	{ "SetMinorClass",		"s",	"", +						adapter_set_minor_class		}, +	{ "GetServiceClasses",		"",	"as", +						adapter_get_service_classes	}, +	{ "GetName",			"",	"s", +						adapter_get_name		}, +	{ "SetName",			"s",	"", +						adapter_set_name		}, + +	{ "GetRemoteInfo",			"s",	"a{sv}", +						adapter_get_remote_info		}, +	{ "GetRemoteServiceRecord",		"su",	"ay", +		adapter_get_remote_svc,		G_DBUS_METHOD_FLAG_ASYNC	}, +	{ "GetRemoteServiceRecordAsXML",	"su",	"s", +		adapter_get_remote_svc_xml,	G_DBUS_METHOD_FLAG_ASYNC	}, +	{ "GetRemoteServiceHandles",		"ss",	"au", +		adapter_get_remote_svc_handles,	G_DBUS_METHOD_FLAG_ASYNC	}, +	{ "GetRemoteServiceIdentifiers",	"s",	"as", +		adapter_get_remote_svc_identifiers, G_DBUS_METHOD_FLAG_ASYNC	}, +	{ "FinishRemoteServiceTransaction",	"s",	"", +						adapter_finish_sdp_transact	}, +	{ "GetRemoteVersion",			"s",	"s", +						adapter_get_remote_version	}, +	{ "GetRemoteRevision",			"s",	"s", +						adapter_get_remote_revision	}, +	{ "GetRemoteManufacturer",		"s",	"s", +						adapter_get_remote_manufacturer	}, +	{ "GetRemoteCompany",			"s",	"s", +						adapter_get_remote_company	}, +	{ "GetRemoteMajorClass",		"s",	"s", +						adapter_get_remote_major_class	}, +	{ "GetRemoteMinorClass",		"s",	"s", +						adapter_get_remote_minor_class	}, +	{ "GetRemoteServiceClasses",		"s",	"as", +						adapter_get_remote_service_cls	}, +	{ "GetRemoteClass",			"s",	"u", +						adapter_get_remote_class	}, +	{ "GetRemoteFeatures",			"s",	"ay", +						adapter_get_remote_features	}, +	{ "GetRemoteName",			"s",	"s", +						adapter_get_remote_name		}, +	{ "GetRemoteAlias",			"s",	"s", +						adapter_get_remote_alias	}, +	{ "SetRemoteAlias",			"ss",	"", +						adapter_set_remote_alias	}, +	{ "ClearRemoteAlias",			"s",	"", +						adapter_clear_remote_alias	}, + +	{ "LastSeen",		"s",	"s", +						adapter_last_seen		}, +	{ "LastUsed",		"s",	"s", +						adapter_last_used		}, + +	{ "DisconnectRemoteDevice",	"s",	"", +			adapter_dc_remote_device,	G_DBUS_METHOD_FLAG_ASYNC}, + +	{ "CreateBonding",		"s",	"", +			adapter_create_bonding,		G_DBUS_METHOD_FLAG_ASYNC}, +	{ "CancelBondingProcess",	"s",	"", +						adapter_cancel_bonding		}, +	{ "RemoveBonding",		"s",	"", +						adapter_remove_bonding		}, +	{ "HasBonding",			"s",	"b", +						adapter_has_bonding		}, +	{ "ListBondings",		"",	"as", +						adapter_list_bondings		}, +	{ "GetPinCodeLength",		"s",	"y", +						adapter_get_pin_code_length	}, +	{ "GetEncryptionKeySize",	"s",	"y", +						adapter_get_encryption_key_size	}, + +	{ "StartPeriodicDiscovery",	"",	"", +						adapter_start_periodic		}, +	{ "StopPeriodicDiscovery",	"",	"", +						adapter_stop_periodic		}, +	{ "IsPeriodicDiscovery",	"",	"b", +						adapter_is_periodic		}, +	{ "SetPeriodicDiscoveryNameResolving",	"b",	"", +						adapter_set_pdiscov_resolve	}, +	{ "GetPeriodicDiscoveryNameResolving",	"",	"b", +						adapter_get_pdiscov_resolve	}, +	{ "DiscoverDevices",			"",	"", +						adapter_discover_devices	}, +	{ "CancelDiscovery",			"",	"", +			adapter_cancel_discovery, G_DBUS_METHOD_FLAG_ASYNC	}, +	{ "DiscoverDevicesWithoutNameResolving","",	"", +						adapter_discover_devices	}, +	{ "ListRemoteDevices",			"",	"as", +						adapter_list_remote_devices	}, +	{ "ListRecentRemoteDevices",		"s",	"as", +						adapter_list_recent_remote_devices}, + +	{ "SetTrusted",		"s",	"", +						adapter_set_trusted		}, +	{ "IsTrusted",		"s",	"b", +						adapter_is_trusted		}, +	{ "RemoveTrust",	"s",	"", +						adapter_remove_trust		}, +	{ "ListTrusts",		"",	"as", +						adapter_list_trusts		}, + +	{ } +}; + +/* BlueZ 4.X */ +static GDBusSignalTable adapter_signals[] = { +	{ "DiscoveryStarted",		""		}, +	{ "DiscoveryCompleted",		""		}, +	{ "DeviceCreated",		"o"		}, +	{ "DeviceRemoved",		"o"		}, +	{ "DeviceFound",		"sa{sv}"	}, +	{ "PropertyChanged",		"sv"		}, +	{ "DeviceDisappeared",		"s"		}, +	{ } +}; + +/* Deprecated */ +static GDBusSignalTable old_adapter_signals[] = { +	{ "DiscoveryStarted",			""	}, +	{ "DiscoveryCompleted",			""	}, +	{ "ModeChanged",			"s"	}, +	{ "DiscoverableTimeoutChanged",		"u"	}, +	{ "MinorClassChanged",			"s"	}, +	{ "NameChanged",			"s"	}, +	{ "PeriodicDiscoveryStarted",		""	}, +	{ "PeriodicDiscoveryStopped",		""	}, +	{ "RemoteDeviceFound",			"sun"	}, +	{ "RemoteDeviceDisappeared",		"s"	}, +	{ "RemoteClassUpdated",			"su"	}, +	{ "RemoteNameUpdated",			"ss"	}, +	{ "RemoteNameFailed",			"s"	}, +	{ "RemoteNameRequested",		"s"	}, +	{ "RemoteAliasChanged",			"ss"	}, +	{ "RemoteAliasCleared",			"s"	}, +	{ "RemoteDeviceConnected",		"s"	}, +	{ "RemoteDeviceDisconnectRequested",	"s"	}, +	{ "RemoteDeviceDisconnected",		"s"	}, +	{ "RemoteIdentifiersUpdated",		"sas"	}, +	{ "BondingCreated",			"s"	}, +	{ "BondingRemoved",			"s"	}, +	{ "TrustAdded",				"s"	}, +	{ "TrustRemoved",			"s"	}, +	{ } +}; + +dbus_bool_t adapter_init(DBusConnection *conn, +		const char *path, struct adapter *adapter) +{ +	if (hcid_dbus_use_experimental()) +		g_dbus_register_interface(conn, path + ADAPTER_PATH_INDEX, +				ADAPTER_INTERFACE, adapter_methods, +				adapter_signals, NULL, adapter, NULL); + +	return g_dbus_register_interface(conn, +			path, ADAPTER_INTERFACE, +			old_adapter_methods, old_adapter_signals, +			NULL, adapter, NULL); +} + +dbus_bool_t adapter_cleanup(DBusConnection *conn, const char *path) +{ +	return g_dbus_unregister_interface(conn, path, ADAPTER_INTERFACE); +} + +const char *major_class_str(uint32_t class) +{ +	uint8_t index = (class >> 8) & 0x1F; + +	if (index > 8) +		return major_cls[9]; /* uncategorized */ + +	return major_cls[index]; +} + +const char *minor_class_str(uint32_t class) +{ +	uint8_t major_index = (class >> 8) & 0x1F; +	uint8_t minor_index; + +	switch (major_index) { +	case 1: /* computer */ +		minor_index = (class >> 2) & 0x3F; +		if (minor_index < NUM_ELEMENTS(computer_minor_cls)) +			return computer_minor_cls[minor_index]; +		else +			return ""; +	case 2: /* phone */ +		minor_index = (class >> 2) & 0x3F; +		if (minor_index < NUM_ELEMENTS(phone_minor_cls)) +			return phone_minor_cls[minor_index]; +		return ""; +	case 3: /* access point */ +		minor_index = (class >> 5) & 0x07; +		if (minor_index < NUM_ELEMENTS(access_point_minor_cls)) +			return access_point_minor_cls[minor_index]; +		else +			return ""; +	case 4: /* audio/video */ +		minor_index = (class >> 2) & 0x3F; +		if (minor_index < NUM_ELEMENTS(audio_video_minor_cls)) +			return audio_video_minor_cls[minor_index]; +		else +			return ""; +	case 5: /* peripheral */ +		minor_index = (class >> 6) & 0x03; +		if (minor_index < NUM_ELEMENTS(peripheral_minor_cls)) +			return peripheral_minor_cls[minor_index]; +		else +			return ""; +	case 6: /* imaging */ +		{ +			uint8_t shift_minor = 0; + +			minor_index = (class >> 4) & 0x0F; +			while (shift_minor < (sizeof(imaging_minor_cls) / sizeof(*imaging_minor_cls))) { +				if (((minor_index >> shift_minor) & 0x01) == 0x01) +					return imaging_minor_cls[shift_minor]; +				shift_minor++; +			} +		} +		break; +	case 7: /* wearable */ +		minor_index = (class >> 2) & 0x3F; +		if (minor_index < NUM_ELEMENTS(wearable_minor_cls)) +			return wearable_minor_cls[minor_index]; +		else +			return ""; +	case 8: /* toy */ +		minor_index = (class >> 2) & 0x3F; +		if (minor_index < NUM_ELEMENTS(toy_minor_cls)) +			return toy_minor_cls[minor_index]; +		else +			return ""; +	} + +	return ""; +} + +GSList *service_classes_str(uint32_t class) +{ +	uint8_t services = class >> 16; +	GSList *l = NULL; +	int i; + +	for (i = 0; i < (sizeof(service_cls) / sizeof(*service_cls)); i++) { +		if (!(services & (1 << i))) +			continue; + +		l = g_slist_append(l, (void *) service_cls[i]); +	} + +	return l; +}  | 
