diff options
Diffstat (limited to 'hcid/adapter.c')
| -rw-r--r-- | hcid/adapter.c | 3367 | 
1 files changed, 3367 insertions, 0 deletions
diff --git a/hcid/adapter.c b/hcid/adapter.c new file mode 100644 index 00000000..17e63f99 --- /dev/null +++ b/hcid/adapter.c @@ -0,0 +1,3367 @@ +/* + * + *  BlueZ - Bluetooth protocol stack for Linux + * + *  Copyright (C) 2004-2007  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 <glib.h> + +#include <dbus/dbus.h> + +#include "hcid.h" +#include "dbus.h" + +#include "textfile.h" +#include "oui.h" +#include "adapter.h" +#include "dbus-common.h" +#include "dbus-helper.h" +#include "dbus-hci.h" +#include "dbus-sdp.h" +#include "dbus-error.h" + +#define NUM_ELEMENTS(table) (sizeof(table)/sizeof(const char *)) + +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" +}; + +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 struct bonding_request_info *bonding_request_new(bdaddr_t *peer, +							DBusConnection *conn, +							DBusMessage *msg) +{ +	struct bonding_request_info *bonding; + +	bonding = g_new0(struct bonding_request_info, 1); + +	bacpy(&bonding->bdaddr, peer); + +	bonding->conn = dbus_connection_ref(conn); +	bonding->rq = dbus_message_ref(msg); + +	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"; +	} +} + +uint8_t str2mode(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 +		return MODE_UNKNOWN; +} + +static DBusHandlerResult adapter_get_info(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	const char *property = adapter->address; +	DBusMessage *reply; +	DBusMessageIter iter; +	DBusMessageIter dict; +	bdaddr_t ba; +	char str[249]; +	uint8_t cls[3]; + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	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); + +	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 send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &paddr, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	err = get_device_version(adapter->dev_id, str, sizeof(str)); +	if (err < 0) +		return error_failed(conn, msg, -err); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	err = get_device_revision(adapter->dev_id, str, sizeof(str)); +	if (err < 0) +		return error_failed(conn, msg, -err); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	err = get_device_manufacturer(adapter->dev_id, str, sizeof(str)); +	if (err < 0) +		return error_failed(conn, msg, -err); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	err = get_device_company(adapter->dev_id, str, sizeof(str)); +	if (err < 0) +		return error_failed(conn, msg, -err); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	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 send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	mode = mode2str(adapter->mode); + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &mode, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult adapter_set_mode(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	const char *mode; +	uint8_t scan_enable; +	uint8_t new_mode, current_scan = adapter->scan_enable; +	bdaddr_t local; +	gboolean limited; +	int err, dd; + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &mode, +				DBUS_TYPE_INVALID)) +		return error_invalid_arguments(conn, msg); + +	if (!mode) +		return error_invalid_arguments(conn, msg); + +	new_mode = str2mode(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 error_invalid_arguments(conn, msg); +	} + +	dd = hci_open_dev(adapter->dev_id); +	if (dd < 0) +		return error_no_such_adapter(conn, 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 error_failed(conn, 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 error_failed(conn, 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 error_failed(conn, 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 error_failed(conn, msg, err); +		} + +		if (status) { +			error("Setting scan enable failed with status 0x%02x", +					status); +			hci_close_dev(dd); +			return error_failed(conn, msg, bt_error(status)); +		} +	} else { +		/* discoverable or limited */ +		if ((scan_enable & SCAN_INQUIRY) && (new_mode != adapter->mode)) { +			dbus_connection_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->discov_timeout != 0) +				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); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	adapter->mode = new_mode; + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	dbus_message_append_args(reply, DBUS_TYPE_UINT32, &adapter->discov_timeout, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult adapter_set_discoverable_to(DBusConnection *conn, +							DBusMessage *msg, +							void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	uint32_t timeout; +	bdaddr_t bdaddr; + +	if (!adapter->up) +		return error_not_ready(conn, msg); + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_UINT32, &timeout, +				DBUS_TYPE_INVALID)) +		return error_invalid_arguments(conn, msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	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); + +	dbus_connection_emit_signal(conn, dbus_message_get_path(msg), +					ADAPTER_INTERFACE, +					"DiscoverableTimeoutChanged", +					DBUS_TYPE_UINT32, &timeout, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	if (scan_enable & SCAN_PAGE) +		connectable = TRUE; + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connectable, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	if (scan_enable & SCAN_INQUIRY) +		discoverable = TRUE; + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &discoverable, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	if (check_address(peer_addr) < 0) +		return error_invalid_arguments(conn, 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 DBUS_HANDLER_RESULT_NEED_MEMORY; + +	dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	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 send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	/* FIXME: Currently, only computer major class is supported */ +	if ((adapter->class[1] & 0x1f) != 1) +		return error_unsupported_major_class(conn, msg); + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult adapter_list_minor_classes(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	const struct adapter *adapter = data; +	DBusMessage *reply = NULL; +	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 error_invalid_arguments(conn, 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 error_unsupported_major_class(conn, msg); +	} + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	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 send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	/* FIXME: Currently, only computer major class is supported */ +	if ((adapter->class[1] & 0x1f) != 1) +		return error_unsupported_major_class(conn, msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	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: +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult adapter_set_minor_class(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	const char *minor; +	uint32_t dev_class = 0xFFFFFFFF; +	int i, dd; + +	if (!adapter->up) +		return error_not_ready(conn, msg); + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &minor, +				DBUS_TYPE_INVALID)) +		return error_invalid_arguments(conn, msg); + +	if (!minor) +		return error_invalid_arguments(conn, msg); + +	dd = hci_open_dev(adapter->dev_id); +	if (dd < 0) +		return error_no_such_adapter(conn, msg); + +	/* Currently, only computer major class is supported */ +	if ((adapter->class[1] & 0x1f) != 1) { +		hci_close_dev(dd); +		return error_unsupported_major_class(conn, 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 error_invalid_arguments(conn, 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 error_failed(conn, msg, err); +	} + +	dbus_connection_emit_signal(conn, dbus_message_get_path(msg), +					ADAPTER_INTERFACE, "MinorClassChanged", +					DBUS_TYPE_STRING, &minor, +					DBUS_TYPE_INVALID); + +	reply = dbus_message_new_method_return(msg); + +	hci_close_dev(dd); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_not_ready(conn, msg); + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return error_invalid_arguments(conn, msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	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 send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	str2ba(adapter->address, &ba); + +	err = read_local_name(&ba, str); +	if (err < 0) { +		if (!adapter->up) +			return error_not_ready(conn, msg); + +		err = get_device_name(adapter->dev_id, str, sizeof(str)); +		if (err < 0) +			return error_failed(conn, msg, -err); +	} + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult adapter_set_name(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	bdaddr_t bdaddr; +	char *str_ptr; +	int ecode; + +	if (!adapter->up) +		return error_not_ready(conn, msg); + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &str_ptr, +				DBUS_TYPE_INVALID)) +		return error_invalid_arguments(conn, msg); + +	if (!g_utf8_validate(str_ptr, -1, NULL)) { +		error("Name change failed: the supplied name isn't valid UTF-8"); +		return error_invalid_arguments(conn, msg); +	} + +	hci_devba(adapter->dev_id, &bdaddr); + +	write_local_name(&bdaddr, str_ptr); + +	ecode = set_device_name(adapter->dev_id, str_ptr); +	if (ecode < 0) +		return error_failed(conn, msg, -ecode); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	if (check_address(addr_ptr) < 0) +		return error_invalid_arguments(conn, msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	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 send_message_and_unref(conn, reply); +} + +static DBusHandlerResult adapter_get_remote_svc(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	return get_remote_svc_rec(conn, msg, data, SDP_FORMAT_BINARY); +} + +static DBusHandlerResult adapter_get_remote_svc_xml(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	return get_remote_svc_rec(conn, msg, data, SDP_FORMAT_XML); +} + +static DBusHandlerResult adapter_get_remote_svc_handles(DBusConnection *conn, +							DBusMessage *msg, +							void *data) +{ +	return get_remote_svc_handles(conn, msg, data); +} + +static DBusHandlerResult adapter_get_remote_svc_identifiers(DBusConnection *conn, +								DBusMessage *msg, +								void *data) +{ +	return get_remote_svc_identifiers(conn, msg, data); +} + +static DBusHandlerResult adapter_finish_sdp_transact(DBusConnection *conn, +							DBusMessage *msg, +							void *data) +{ +	return finish_remote_svc_transact(conn, msg, data); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	if (check_address(addr_ptr) < 0) +		return error_invalid_arguments(conn, msg); + +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, +			"manufacturers"); + +	str = textfile_caseget(filename, addr_ptr); +	if (!str) +		return error_not_available(conn, 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 DBUS_HANDLER_RESULT_NEED_MEMORY; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &info, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	if (check_address(addr_ptr) < 0) +		return error_invalid_arguments(conn, msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, +			"manufacturers"); + +	str = textfile_caseget(filename, addr_ptr); +	if (!str) +		return error_not_available(conn, 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 send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	if (check_address(addr_ptr) < 0) +		return error_invalid_arguments(conn, msg); + +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, +			"manufacturers"); + +	str = textfile_caseget(filename, addr_ptr); +	if (!str) +		return error_not_available(conn, 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 DBUS_HANDLER_RESULT_NEED_MEMORY; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &info, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	str2ba(str_bdaddr, &bdaddr); +	ba2oui(&bdaddr, oui); + +	tmp = ouitocomp(oui); +	if (!tmp) +		return error_not_available(conn, msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) { +		free(tmp); +		return DBUS_HANDLER_RESULT_NEED_MEMORY; +	} + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &tmp, +					DBUS_TYPE_INVALID); + +	free(tmp); + +	return send_message_and_unref(conn, reply); +} + +static int 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)) { +		error_invalid_arguments(conn, msg); +		return -1; +	} + +	if (check_address(addr_peer) < 0) { +		error_invalid_arguments(conn, msg); +		return -1; +	} + +	str2ba(addr_peer, &peer); +	str2ba(adapter->address, &local); + +	ecode = read_remote_class(&local, &peer, class); +	if (ecode < 0) { +		error_not_available(conn, msg); +		return -1; +	} + +	return 0; +} + +static DBusHandlerResult adapter_get_remote_major_class(DBusConnection *conn, +							DBusMessage *msg, +							void *data) +{ +	DBusMessage *reply; +	const char *major_class; +	uint32_t class; + +	if (get_remote_class(conn, msg, data, &class) < 0) +		return DBUS_HANDLER_RESULT_HANDLED; + +	major_class = major_class_str(class); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &major_class, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult adapter_get_remote_minor_class(DBusConnection *conn, +							DBusMessage *msg, +							void *data) +{ +	DBusMessage *reply; +	const char *minor_class; +	uint32_t class; + +	if (get_remote_class(conn, msg, data, &class) < 0) +		return DBUS_HANDLER_RESULT_HANDLED; + +	minor_class = minor_class_str(class); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &minor_class, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static void append_class_string(const char *class, DBusMessageIter *iter) +{ +	dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &class); +} + +static DBusHandlerResult adapter_get_remote_service_cls(DBusConnection *conn, +							DBusMessage *msg, +							void *data) +{ +	DBusMessage *reply; +	DBusMessageIter iter, array_iter; +	GSList *service_classes; +	uint32_t class; + +	if (get_remote_class(conn, msg, data, &class) < 0) +		return DBUS_HANDLER_RESULT_HANDLED; + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	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 send_message_and_unref(conn, reply); +} + +static DBusHandlerResult adapter_get_remote_class(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	DBusMessage *reply; +	uint32_t class; + +	if (get_remote_class(conn, msg, data, &class) < 0) +		return DBUS_HANDLER_RESULT_HANDLED; + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	dbus_message_append_args(reply, DBUS_TYPE_UINT32, &class, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	if (check_address(addr) < 0) +		return error_invalid_arguments(conn, msg); + +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, "features"); + +	str = textfile_caseget(filename, addr); +	if (!str) +		return error_not_available(conn, 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 DBUS_HANDLER_RESULT_NEED_MEMORY; +	} + +	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 send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	if (check_address(peer_addr) < 0) +		return error_invalid_arguments(conn, 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 DBUS_HANDLER_RESULT_NEED_MEMORY; +		} + +		/* send the cached name */ +		dbus_message_append_args(reply, DBUS_TYPE_STRING, &str, +						DBUS_TYPE_INVALID); + +		free(str); +		return send_message_and_unref(conn, reply); +	} + +	if (!adapter->up) +		return error_not_ready(conn, msg); + +	/* If the discover process is not running, return an error */ +	if (!adapter->discov_active && !adapter->pdiscov_active) +		return error_not_available(conn, 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 error_request_deferred(conn, msg); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	if (check_address(addr_ptr) < 0) +		return error_invalid_arguments(conn, msg); + +	str2ba(addr_ptr, &bdaddr); + +	ecode = get_device_alias(adapter->dev_id, &bdaddr, str, sizeof(str)); +	if (ecode < 0) +		return error_not_available(conn, msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &str_ptr, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult adapter_set_remote_alias(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	char *alias, *addr; +	bdaddr_t bdaddr; +	int ecode; + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &addr, +				DBUS_TYPE_STRING, &alias, +				DBUS_TYPE_INVALID)) +		return error_invalid_arguments(conn, msg); + +	if ((strlen(alias) == 0) || (check_address(addr) < 0)) { +		error("Alias change failed: Invalid parameter"); +		return error_invalid_arguments(conn, msg); +	} + +	str2ba(addr, &bdaddr); + +	ecode = set_device_alias(adapter->dev_id, &bdaddr, alias); +	if (ecode < 0) +		return error_failed(conn, msg, -ecode); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	dbus_connection_emit_signal(conn, dbus_message_get_path(msg), +					ADAPTER_INTERFACE, "RemoteAliasChanged", +					DBUS_TYPE_STRING, &addr, +					DBUS_TYPE_STRING, &alias, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult adapter_clear_remote_alias(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	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 error_invalid_arguments(conn, msg); + +	if (check_address(addr_ptr) < 0) { +		error("Alias clear failed: Invalid parameter"); +		return error_invalid_arguments(conn, 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 error_failed(conn, msg, -ecode); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	if (had_alias) +		dbus_connection_emit_signal(conn, dbus_message_get_path(msg), +						ADAPTER_INTERFACE, +						"RemoteAliasCleared", +						DBUS_TYPE_STRING, &addr_ptr, +						DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	if (check_address(addr_ptr) < 0) +		return error_invalid_arguments(conn, msg); + +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, +			"lastseen"); + +	str = textfile_caseget(filename, addr_ptr); +	if (!str) +		return error_not_available(conn, msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) { +		free(str); +		return DBUS_HANDLER_RESULT_NEED_MEMORY; +	} + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &str, +					DBUS_TYPE_INVALID); + +	free(str); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	if (check_address(addr_ptr) < 0) +		return error_invalid_arguments(conn, msg); + +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, +			"lastused"); + +	str = textfile_caseget(filename, addr_ptr); +	if (!str) +		return error_not_available(conn, msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) { +		free(str); +		return DBUS_HANDLER_RESULT_NEED_MEMORY; +	} + +	dbus_message_append_args(reply, DBUS_TYPE_STRING, &str, +					DBUS_TYPE_INVALID); + +	free(str); + +	return send_message_and_unref(conn, 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, pending_dc->conn_handle, +				HCI_OE_USER_ENDED_CONNECTION, +			   	500) < 0) { +		int err = errno; +		error("Disconnect failed"); +		error_failed(pending_dc->conn, pending_dc->msg, err); +	} else { +		reply = dbus_message_new_method_return(pending_dc->msg); +		if (!reply) +			error("Failed to allocate disconnect reply"); +		else +			send_message_and_unref(pending_dc->conn, 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 DBusHandlerResult 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 error_not_ready(conn, msg); + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &peer_addr, +				DBUS_TYPE_INVALID)) +		return error_invalid_arguments(conn, msg); + +	if (check_address(peer_addr) < 0) +		return error_invalid_arguments(conn, msg); + +	str2ba(peer_addr, &peer_bdaddr); + +	l = g_slist_find_custom(l, &peer_bdaddr, active_conn_find_by_bdaddr); +	if (!l) +		return error_not_connected(conn, msg); + +	if (adapter->pending_dc) +		return error_disconnect_in_progress(conn, msg); + +	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 DBUS_HANDLER_RESULT_NEED_MEMORY; +	} + +	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; + +	dbus_connection_emit_signal(conn, dbus_message_get_path(msg), +					ADAPTER_INTERFACE, +					"RemoteDeviceDisconnectRequested", +					DBUS_TYPE_STRING, &peer_addr, +					DBUS_TYPE_INVALID); + +	return DBUS_HANDLER_RESULT_HANDLED; +} + +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->rq, status); +	if (reply) +		send_message_and_unref(bonding->conn, reply); +} + +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->rq); +		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->rq, +							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(adapter->bonding->conn, adapter->bonding->rq, +				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->rq, +							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(adapter->bonding->conn, adapter->bonding->rq, +				errno); +		goto failed; +	} + +	dd = hci_open_dev(adapter->dev_id); +	if (dd < 0) { +		error_no_such_adapter(adapter->bonding->conn, +					adapter->bonding->rq); +		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(adapter->bonding->conn, adapter->bonding->rq, +				errno); +		hci_close_dev(dd); +		goto failed; +	} + +	if (rp.status) { +		error("HCI_Authentication_Requested failed with status 0x%02x", +				rp.status); +		error_failed(adapter->bonding->conn, adapter->bonding->rq, +				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); + +cleanup: +	name_listener_remove(adapter->bonding->conn, +				dbus_message_get_sender(adapter->bonding->rq), +				(name_cb_t) create_bond_req_exit, adapter); + +	bonding_request_free(adapter->bonding); +	adapter->bonding = NULL; + +	return FALSE; +} + +static DBusHandlerResult adapter_create_bonding(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	char filename[PATH_MAX + 1]; +	char *str, *peer_addr = NULL; +	struct adapter *adapter = data; +	bdaddr_t peer_bdaddr; +	int sk; + +	if (!adapter->up) +		return error_not_ready(conn, msg); + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &peer_addr, +				DBUS_TYPE_INVALID)) +		return error_invalid_arguments(conn, msg); + +	if (check_address(peer_addr) < 0) +		return error_invalid_arguments(conn, msg); + +	str2ba(peer_addr, &peer_bdaddr); + +	/* check if there is a pending discover: requested by D-Bus/non clients */ +	if (adapter->discov_active || (adapter->pdiscov_active && !adapter->pinq_idle)) +		return error_discover_in_progress(conn, msg); + +	pending_remote_name_cancel(adapter); + +	if (adapter->bonding) +		return error_bonding_in_progress(conn, msg); + +	if (g_slist_find_custom(adapter->pin_reqs, &peer_bdaddr, pin_req_cmp)) +		return error_bonding_in_progress(conn, msg); + +	/* check if a link key already exists */ +	create_name(filename, PATH_MAX, STORAGEDIR, adapter->address, +			"linkkeys"); + +	str = textfile_caseget(filename, peer_addr); +	if (str) { +		free(str); +		return error_bonding_already_exists(conn, msg); +	} + +	sk = l2raw_connect(adapter->address, &peer_bdaddr); +	if (sk < 0) +		return error_connection_attempt_failed(conn, msg, 0); + +	adapter->bonding = bonding_request_new(&peer_bdaddr, conn, msg); +	if (!adapter->bonding) { +		close(sk); +		return DBUS_HANDLER_RESULT_NEED_MEMORY; +	} + +	adapter->bonding->io = g_io_channel_unix_new(sk); +	adapter->bonding->io_id = g_io_add_watch(adapter->bonding->io, +						G_IO_OUT | G_IO_NVAL | G_IO_HUP | G_IO_ERR, +						(GIOFunc) create_bonding_conn_complete, +						adapter); + +	name_listener_add(conn, dbus_message_get_sender(msg), +			(name_cb_t) create_bond_req_exit, adapter); + +	return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult adapter_cancel_bonding(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	bdaddr_t peer_bdaddr; +	const char *peer_addr; +	GSList *l; + +	if (!adapter->up) +		return error_not_ready(conn, msg); + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &peer_addr, +				DBUS_TYPE_INVALID)) +		return error_invalid_arguments(conn, msg); + +	if (check_address(peer_addr) < 0) +		return error_invalid_arguments(conn, msg); + +	str2ba(peer_addr, &peer_bdaddr); + +	if (!adapter->bonding || bacmp(&adapter->bonding->bdaddr, &peer_bdaddr)) +		return error_bonding_not_in_progress(conn, msg); + +	if (strcmp(dbus_message_get_sender(adapter->bonding->rq), +				dbus_message_get_sender(msg))) +		return error_not_authorized(conn, msg); + +	adapter->bonding->cancel = 1; + +	l = g_slist_find_custom(adapter->pin_reqs, &peer_bdaddr, pin_req_cmp); +	if (l) { +		struct pending_pin_info *pin_req = l->data; + +		if (pin_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 error_not_authorized(conn, msg); +		} else { +			int dd = hci_open_dev(adapter->dev_id); +			if (dd < 0) { +				error("Can't open hci%d: %s (%d)", +					adapter->dev_id, strerror(errno), errno); +				return DBUS_HANDLER_RESULT_HANDLED; +			} + +			hci_send_cmd(dd, OGF_LINK_CTL, OCF_PIN_CODE_NEG_REPLY, +					6, &peer_bdaddr); + +			hci_close_dev(dd); +		}  + +		adapter->pin_reqs = g_slist_remove(adapter->pin_reqs, pin_req); +		g_free(pin_req); +	} + +	g_io_channel_close(adapter->bonding->io); + +	reply = dbus_message_new_method_return(msg); +	send_message_and_unref(conn, reply); + +	return DBUS_HANDLER_RESULT_HANDLED; +} + +static DBusHandlerResult adapter_remove_bonding(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	GSList *l; +	DBusMessage *reply; +	char filename[PATH_MAX + 1]; +	char *addr_ptr, *str; +	bdaddr_t bdaddr; +	int dd; + +	if (!adapter->up) +		return error_not_ready(conn, msg); + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_STRING, &addr_ptr, +				DBUS_TYPE_INVALID)) +		return error_invalid_arguments(conn, msg); + +	if (check_address(addr_ptr) < 0) +		return error_invalid_arguments(conn, msg); + +	dd = hci_open_dev(adapter->dev_id); +	if (dd < 0) +		return error_no_such_adapter(conn, 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, addr_ptr); +	if (!str) { +		hci_close_dev(dd); +		return error_bonding_does_not_exist(conn, msg); +	} + +	free(str); + +	/* Delete the link key from storage */ +	if (textfile_casedel(filename, addr_ptr) < 0) { +		int err = errno; +		hci_close_dev(dd); +		return error_failed(conn, msg, err); +	} + +	str2ba(addr_ptr, &bdaddr); + +	/* Delete the link key from the Bluetooth chip */ +	hci_delete_stored_link_key(dd, &bdaddr, 0, 1000); + +	/* find the connection */ +	l = g_slist_find_custom(adapter->active_conn, &bdaddr, +			active_conn_find_by_bdaddr); +	if (l) { +		struct active_conn_info *con = l->data; +		/* Send the HCI disconnect command */ +		if (hci_disconnect(dd, htobs(con->handle), +					HCI_OE_USER_ENDED_CONNECTION, 500) < 0) { +			int err = errno; +			error("Disconnect failed"); +			hci_close_dev(dd); +			return error_failed(conn, msg, err); +		} +	} + +	dbus_connection_emit_signal(conn, dbus_message_get_path(msg), +					ADAPTER_INTERFACE, "BondingRemoved", +					DBUS_TYPE_STRING, &addr_ptr, +					DBUS_TYPE_INVALID); + +	reply = dbus_message_new_method_return(msg); + +	hci_close_dev(dd); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	if (check_address(addr_ptr) < 0) +		return error_invalid_arguments(conn, 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 send_message_and_unref(conn, 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 DBusHandlerResult 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 error_invalid_arguments(conn, 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 send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	if (check_address(addr_ptr) < 0) +		return error_invalid_arguments(conn, msg); + +	str2ba(adapter->address, &local); + +	str2ba(addr_ptr, &peer); + +	len = read_pin_length(&local, &peer); +	if (len < 0) +		return error_record_does_not_exist(conn, msg); + +	reply = dbus_message_new_method_return(msg); + +	length = len; + +	dbus_message_append_args(reply, DBUS_TYPE_BYTE, &length, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	if (check_address(addr_ptr) < 0) +		return error_invalid_arguments(conn, msg); + +	str2ba(addr_ptr, &bdaddr); + +	val = get_encryption_key_size(adapter->dev_id, &bdaddr); +	if (val < 0) +		return error_failed(conn, msg, -val); + +	reply = dbus_message_new_method_return(msg); + +	size = val; + +	dbus_message_append_args(reply, DBUS_TYPE_BYTE, &size, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult adapter_start_periodic(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	DBusMessage *reply; +	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 error_not_ready(conn, msg); + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return error_invalid_arguments(conn, msg); + +	if (adapter->discov_active || adapter->pdiscov_active) +		return error_discover_in_progress(conn, msg); + +	pending_remote_name_cancel(adapter); + +	dd = hci_open_dev(adapter->dev_id); +	if (dd < 0) +		return error_no_such_adapter(conn, 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 error_failed(conn, msg, err); +	} + +	if (status) { +		error("HCI_Periodic_Inquiry_Mode failed with status 0x%02x", +				status); +		hci_close_dev(dd); +		return error_failed(conn, 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; + +	reply = dbus_message_new_method_return(msg); + +	hci_close_dev(dd); + +	/* track the request owner to cancel it automatically if the owner +	 * exits */ +	name_listener_add(conn, dbus_message_get_sender(msg), +				(name_cb_t) periodic_discover_req_exit, +				adapter); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult adapter_stop_periodic(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	DBusMessage *reply; +	struct adapter *adapter = data; +	int err; + +	if (!adapter->up) +		return error_not_ready(conn, msg); + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return error_invalid_arguments(conn, msg); + +	if (!adapter->pdiscov_active) +		return error_not_authorized(conn, msg); + +	/*  +	 * 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 error_no_such_adapter(conn, msg); +		else +			return error_failed(conn, msg, -err); +	} + +	reply = dbus_message_new_method_return(msg); +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 DBUS_HANDLER_RESULT_NEED_MEMORY; + +	dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &active, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult adapter_set_pdiscov_resolve(DBusConnection *conn, +							DBusMessage *msg, +							void *data) +{ +	DBusMessage *reply; +	struct adapter *adapter = data; +	dbus_bool_t resolve; + +	if (!dbus_message_get_args(msg, NULL, +				DBUS_TYPE_BOOLEAN, &resolve, +				DBUS_TYPE_INVALID)) +		return error_invalid_arguments(conn, msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	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 send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &resolve, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult adapter_discover_devices(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	DBusMessage *reply; +	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 error_not_ready(conn, msg); + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return error_invalid_arguments(conn, msg); + +	if (adapter->discov_active) +		return error_discover_in_progress(conn, msg); + +	pending_remote_name_cancel(adapter); + +	if (adapter->bonding) +		return error_bonding_in_progress(conn, msg); + +	dd = hci_open_dev(adapter->dev_id); +	if (dd < 0) +		return error_no_such_adapter(conn, 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 error_failed(conn, msg, err); +	} + +	if (rp.status) { +		error("HCI_Inquiry command failed with status 0x%02x", +				rp.status); +		hci_close_dev(dd); +		return error_failed(conn, 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)); + +	reply = dbus_message_new_method_return(msg); + +	hci_close_dev(dd); + +	/* track the request owner to cancel it automatically if the owner +	 * exits */ +	name_listener_add(conn, dbus_message_get_sender(msg), +				(name_cb_t) discover_devices_req_exit, adapter); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult adapter_cancel_discovery(DBusConnection *conn, +						DBusMessage *msg, void *data) +{ +	struct adapter *adapter = data; +	int err; + +	if (!adapter->up) +		return error_not_ready(conn, msg); + +	if (!dbus_message_has_signature(msg, DBUS_TYPE_INVALID_AS_STRING)) +		return error_invalid_arguments(conn, msg); + +	/* is there discover pending? or discovery cancel was requested +	 * previously */ +	if (!adapter->discov_active || adapter->discovery_cancel) +		return error_not_authorized(conn, msg); + +	/* only the discover requestor can cancel the inquiry process */ +	if (!adapter->discov_requestor || +			strcmp(adapter->discov_requestor, dbus_message_get_sender(msg))) +		return error_not_authorized(conn, msg); + +	/*  +	 * 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 error_no_such_adapter(conn, msg); +		else +			return error_failed(conn, msg, -err); +	} + +	/* Reply before send DiscoveryCompleted */ +	adapter->discovery_cancel = dbus_message_ref(msg); + +	return DBUS_HANDLER_RESULT_HANDLED; +} + +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 DBusHandlerResult 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 error_invalid_arguments(conn, 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 send_message_and_unref(conn, 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 DBusHandlerResult 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 error_invalid_arguments(conn, 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 error_invalid_arguments(conn, 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 send_message_and_unref(conn, reply); +} + + +static DBusHandlerResult adapter_set_trusted(DBusConnection *conn, +						DBusMessage *msg, +						void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	bdaddr_t local; +	const char *address; + +	if (!dbus_message_get_args(msg, NULL, +			DBUS_TYPE_STRING, &address, +			DBUS_TYPE_INVALID)) +		return error_invalid_arguments(conn, msg); + +	if (check_address(address) < 0) +		return error_invalid_arguments(conn, msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	str2ba(adapter->address, &local); + +	write_trust(&local, address, GLOBAL_TRUST, TRUE); + +	dbus_connection_emit_signal(conn, dbus_message_get_path(msg), +					ADAPTER_INTERFACE, "TrustAdded", +					DBUS_TYPE_STRING, &address, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 error_invalid_arguments(conn, msg); + +	if (check_address(address) < 0) +		return error_invalid_arguments(conn, msg); + +	str2ba(adapter->address, &local); + +	trusted = read_trust(&local, address, GLOBAL_TRUST); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	dbus_message_append_args(reply, +				DBUS_TYPE_BOOLEAN, &trusted, +				DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult adapter_remove_trust(DBusConnection *conn, +						DBusMessage *msg, +						void *data) +{ +	struct adapter *adapter = data; +	DBusMessage *reply; +	const char *address; +	bdaddr_t local; + +	if (!dbus_message_get_args(msg, NULL, +			DBUS_TYPE_STRING, &address, +			DBUS_TYPE_INVALID)) +		return error_invalid_arguments(conn, msg); + +	if (check_address(address) < 0) +		return error_invalid_arguments(conn, msg); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return DBUS_HANDLER_RESULT_NEED_MEMORY; + +	str2ba(adapter->address, &local); + +	write_trust(&local, address, GLOBAL_TRUST, FALSE); + +	dbus_connection_emit_signal(conn, dbus_message_get_path(msg), +					ADAPTER_INTERFACE, "TrustRemoved", +					DBUS_TYPE_STRING, &address, +					DBUS_TYPE_INVALID); + +	return send_message_and_unref(conn, reply); +} + +static DBusHandlerResult 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 DBUS_HANDLER_RESULT_NEED_MEMORY; + +	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 send_message_and_unref(conn, reply); +} + +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; +} + +static DBusMethodVTable adapter_methods[] = { +	{ "GetInfo",				adapter_get_info, +		"",	"{sv}"	}, +	{ "GetAddress",				adapter_get_address, +		"",	"s"	}, +	{ "GetVersion",				adapter_get_version, +		"",	"s"	}, +	{ "GetRevision",			adapter_get_revision, +		"",	"s"	}, +	{ "GetManufacturer",			adapter_get_manufacturer, +		"",	"s"	}, +	{ "GetCompany",				adapter_get_company, +		"",	"s"	}, +	{ "ListAvailableModes",			adapter_list_modes, +		"",	"as"	}, +	{ "GetMode",				adapter_get_mode, +		"",	"s"	}, +	{ "SetMode",				adapter_set_mode, +		"s",	""	}, +	{ "GetDiscoverableTimeout",		adapter_get_discoverable_to, +		"",	"u"	}, +	{ "SetDiscoverableTimeout",		adapter_set_discoverable_to, +		"u",	""	}, +	{ "IsConnectable",			adapter_is_connectable, +		"",	"b"	}, +	{ "IsDiscoverable",			adapter_is_discoverable, +		"",	"b"	}, +	{ "IsConnected",			adapter_is_connected, +		"s",	"b"	}, +	{ "ListConnections",			adapter_list_connections, +		"",	"as"	}, +	{ "GetMajorClass",			adapter_get_major_class, +		"",	"s"	}, +	{ "ListAvailableMinorClasses",		adapter_list_minor_classes, +		"",	"as"	}, +	{ "GetMinorClass",			adapter_get_minor_class, +		"",	"s"	}, +	{ "SetMinorClass",			adapter_set_minor_class, +		"s",	""	}, +	{ "GetServiceClasses",			adapter_get_service_classes, +		"",	"as"	}, +	{ "GetName",				adapter_get_name, +		"",	"s"	}, +	{ "SetName",				adapter_set_name, +		"s",	""	}, + +	{ "GetRemoteInfo",			adapter_get_remote_info, +		"s",	"{sv}"	}, +	{ "GetRemoteServiceRecord",		adapter_get_remote_svc, +		"su",	"ay"	}, +	{ "GetRemoteServiceRecordAsXML",	adapter_get_remote_svc_xml, +		"su",	"s"	}, +	{ "GetRemoteServiceHandles",		adapter_get_remote_svc_handles, +		"ss",	"au"	}, +	{ "GetRemoteServiceIdentifiers",	adapter_get_remote_svc_identifiers, +		"s",	"as"	}, +	{ "FinishRemoteServiceTransaction",	adapter_finish_sdp_transact, +		"s",	""	}, + +	{ "GetRemoteVersion",			adapter_get_remote_version, +		"s",	"s"	}, +	{ "GetRemoteRevision",			adapter_get_remote_revision, +		"s",	"s"	}, +	{ "GetRemoteManufacturer",		adapter_get_remote_manufacturer, +		"s",	"s"	}, +	{ "GetRemoteCompany",			adapter_get_remote_company, +		"s",	"s"	}, +	{ "GetRemoteMajorClass",		adapter_get_remote_major_class, +		"s",	"s"	}, +	{ "GetRemoteMinorClass",		adapter_get_remote_minor_class, +		"s",	"s"	}, +	{ "GetRemoteServiceClasses",		adapter_get_remote_service_cls, +		"s",	"as"	}, +	{ "GetRemoteClass",			adapter_get_remote_class, +		"s",	"u"	}, +	{ "GetRemoteFeatures",			adapter_get_remote_features, +		"s",	"ay"	}, +	{ "GetRemoteName",			adapter_get_remote_name, +		"s",	"s"	}, +	{ "GetRemoteAlias",			adapter_get_remote_alias, +		"s",	"s"	}, +	{ "SetRemoteAlias",			adapter_set_remote_alias, +		"ss",	""	}, +	{ "ClearRemoteAlias",			adapter_clear_remote_alias, +		"s",	""	}, + +	{ "LastSeen",				adapter_last_seen, +		"s",	"s"	}, +	{ "LastUsed",				adapter_last_used, +		"s",	"s"	}, + +	{ "DisconnectRemoteDevice",		adapter_dc_remote_device, +		"s",	""	}, + +	{ "CreateBonding",			adapter_create_bonding, +		"s",	""	}, +	{ "CancelBondingProcess",		adapter_cancel_bonding, +		"s",	""	}, +	{ "RemoveBonding",			adapter_remove_bonding, +		"s",	""	}, +	{ "HasBonding",				adapter_has_bonding, +		"s",	"b"	}, +	{ "ListBondings",			adapter_list_bondings, +		"",	"as"	}, +	{ "GetPinCodeLength",			adapter_get_pin_code_length, +		"s",	"y"	}, +	{ "GetEncryptionKeySize",		adapter_get_encryption_key_size, +		"s",	"y"	}, + +	{ "StartPeriodicDiscovery",		adapter_start_periodic, +		"",	""	}, +	{ "StopPeriodicDiscovery",		adapter_stop_periodic, +		"",	""	}, +	{ "IsPeriodicDiscovery",		adapter_is_periodic, +		"",	"b"	}, +	{ "SetPeriodicDiscoveryNameResolving",	adapter_set_pdiscov_resolve, +		"b",	""	}, +	{ "GetPeriodicDiscoveryNameResolving",	adapter_get_pdiscov_resolve, +		"",	"b"	}, + +	{ "DiscoverDevices",			adapter_discover_devices, +		"",	""	}, +	{ "DiscoverDevicesWithoutNameResolving",	adapter_discover_devices, +		"",	""	}, +	{ "CancelDiscovery",			adapter_cancel_discovery, +		"",	""	}, + +	{ "ListRemoteDevices",			adapter_list_remote_devices, +		"",	"as"	}, +	{ "ListRecentRemoteDevices",		adapter_list_recent_remote_devices, +		"s",	"as"	}, + +	{ "SetTrusted",				adapter_set_trusted, +		"s",	""	}, +	{ "IsTrusted",				adapter_is_trusted, +		"s",	"b"	}, +	{ "RemoveTrust",			adapter_remove_trust, +		"s",	""	}, +	{ "ListTrusts",				adapter_list_trusts, +		"",	"as"	}, + +	{ NULL, NULL, NULL, NULL } +}; + +static DBusSignalVTable adapter_signals[] = { +	{ "ModeChanged",			"s"	}, +	{ "DiscoverableTimeoutChanged",		"u"	}, +	{ "MinorClassChanged",			"s"	}, +	{ "NameChanged",			"s"	}, +	{ "DiscoveryStarted",			""	}, +	{ "DiscoveryCompleted",			""	}, +	{ "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"	}, +	{ NULL, NULL } +}; + +dbus_bool_t adapter_init(DBusConnection *conn, const char *path) +{ +	return dbus_connection_register_interface(conn, path, ADAPTER_INTERFACE, +							adapter_methods, +							adapter_signals, NULL); +}  | 
