diff options
Diffstat (limited to 'src/device.c')
| -rw-r--r-- | src/device.c | 1100 | 
1 files changed, 1100 insertions, 0 deletions
diff --git a/src/device.c b/src/device.c new file mode 100644 index 00000000..9d9e03b8 --- /dev/null +++ b/src/device.c @@ -0,0 +1,1100 @@ +/* + * + *  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 + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <stdarg.h> +#include <unistd.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/sdp.h> +#include <bluetooth/sdp_lib.h> + +#include <glib.h> +#include <dbus/dbus.h> +#include <gdbus.h> + +#include "hcid.h" +#include "sdpd.h" + +#include "logging.h" +#include "textfile.h" + +#include "adapter.h" +#include "device.h" +#include "dbus-common.h" +#include "dbus-hci.h" +#include "dbus-service.h" +#include "error.h" +#include "glib-helper.h" +#include "agent.h" +#include "sdp-xml.h" + +#define DEFAULT_XML_BUF_SIZE	1024 +#define DISCONNECT_TIMER	2 + +#define DEVICE_INTERFACE "org.bluez.Device" + +struct btd_driver_data { +	struct btd_device_driver *driver; +	void *priv; +}; + +struct btd_device { +	gchar		*address; +	gchar		*path; +	struct adapter	*adapter; +	GSList		*uuids; +	GSList		*drivers;		/* List of driver_data */ +	gboolean	temporary; +	struct agent	*agent; +	guint		disconn_timer; +	int		discov_active;		/* Service discovery active */ +	char		*discov_requestor;	/* discovery requestor unique name */ +	guint		discov_listener; + +	/* For Secure Simple Pairing */ +	uint8_t		cap; +	uint8_t		auth; +}; + +struct browse_req { +	DBusConnection *conn; +	DBusMessage *msg; +	struct btd_device *device; +	GSList *uuids_added; +	GSList *uuids_removed; +	int search_uuid; +	gboolean browse; +}; + +static GSList *drivers = NULL; + +static uint16_t uuid_list[] = { +	PUBLIC_BROWSE_GROUP, +	HID_SVCLASS_ID, +	GENERIC_AUDIO_SVCLASS_ID, +	ADVANCED_AUDIO_SVCLASS_ID, +	AV_REMOTE_SVCLASS_ID, +	0 +}; + +static void device_free(gpointer user_data) +{ +	struct btd_device *device = user_data; + +	if (device->agent) +		agent_destroy(device->agent, FALSE); + +	g_slist_foreach(device->uuids, (GFunc) g_free, NULL); +	g_slist_free(device->uuids); + +	if (device->disconn_timer) +		g_source_remove(device->disconn_timer); + +	g_free(device->address); +	g_free(device->path); +	g_free(device); +} + +static gboolean device_is_paired(struct btd_device *device) +{ +	struct adapter *adapter = device->adapter; +	char filename[PATH_MAX + 1], *str; +	gboolean ret; +	const gchar *source = adapter_get_address(adapter); + +	create_name(filename, PATH_MAX, STORAGEDIR, +			source, "linkkeys"); +	str = textfile_caseget(filename, device->address); +	ret = str ? TRUE : FALSE; +	g_free(str); + +	return ret; +} + +static char *device_get_name(struct btd_device *device) +{ +	struct adapter *adapter = device->adapter; +	char filename[PATH_MAX + 1]; +	const gchar *source = adapter_get_address(adapter); + +	create_name(filename, PATH_MAX, STORAGEDIR, source, "names"); +	return textfile_caseget(filename, device->address); +} + +static DBusMessage *get_properties(DBusConnection *conn, +				DBusMessage *msg, void *user_data) +{ +	struct btd_device *device = user_data; +	struct adapter *adapter = device->adapter; +	DBusMessage *reply; +	DBusMessageIter iter; +	DBusMessageIter dict; +	bdaddr_t src, dst; +	char path[MAX_PATH_LENGTH]; +	char buf[64]; +	const char *ptr; +	char *name, *ppath, **uuids; +	dbus_bool_t boolean; +	uint32_t class; +	int i; +	GSList *l; +	uint16_t dev_id = adapter_get_dev_id(adapter); +	const gchar *source = adapter_get_address(adapter); + +	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 */ +	dbus_message_iter_append_dict_entry(&dict, "Address", DBUS_TYPE_STRING, +			&device->address); + +	/* Name */ +	name = device_get_name(device); +	if (name) { +		dbus_message_iter_append_dict_entry(&dict, "Name", +				DBUS_TYPE_STRING, &name); +	} + +	str2ba(source, &src); +	str2ba(device->address, &dst); + +	/* Class */ +	if (read_remote_class(&src, &dst, &class) == 0) { +		dbus_message_iter_append_dict_entry(&dict, "Class", +				DBUS_TYPE_UINT32, &class); +	} + +	/* Alias */ +	if (get_device_alias(dev_id, &dst, buf, sizeof(buf)) > 0) { +		ptr = buf; +		dbus_message_iter_append_dict_entry(&dict, "Alias", +				DBUS_TYPE_STRING, &ptr); +	} else if (name) { +		dbus_message_iter_append_dict_entry(&dict, "Alias", +				DBUS_TYPE_STRING, &name); +		free(name); +	} + +	/* Paired */ +	boolean = device_is_paired(device); +	dbus_message_iter_append_dict_entry(&dict, "Paired", +			DBUS_TYPE_BOOLEAN, &boolean); + +	/* Trusted */ +	boolean = read_trust(&src, device->address, 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); + +	/* UUIDs */ +	uuids = g_new0(char *, g_slist_length(device->uuids) + 1); +	for (i = 0, l = device->uuids; l; l = l->next, i++) +		uuids[i] = l->data; +	dbus_message_iter_append_dict_entry(&dict, "UUIDs", +			DBUS_TYPE_ARRAY, &uuids); +	g_free(uuids); + +	/* Adapter */ +	snprintf(path, sizeof(path), "/hci%d", dev_id); +	ppath = path; +	dbus_message_iter_append_dict_entry(&dict, "Adapter", +			DBUS_TYPE_OBJECT_PATH, &ppath); + +	dbus_message_iter_close_container(&iter, &dict); + +	return reply; +} + +static int remove_device_alias(const char *source, const char *destination) +{ +	char filename[PATH_MAX + 1]; + +	create_name(filename, PATH_MAX, STORAGEDIR, source, "aliases"); +	create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + +	return textfile_del(filename, destination); +} + +static DBusMessage *set_alias(DBusConnection *conn, DBusMessage *msg, +					const char *alias, void *data) +{ +	struct btd_device *device = data; +	struct adapter *adapter = device->adapter; +	bdaddr_t bdaddr; +	int ecode; +	char *str, filename[PATH_MAX + 1]; +	uint16_t dev_id = adapter_get_dev_id(adapter); +	const gchar *source = adapter_get_address(adapter); + +	str2ba(device->address, &bdaddr); + +	/* Remove alias if empty string */ +	if (g_str_equal(alias, "")) { +		create_name(filename, PATH_MAX, STORAGEDIR, source, +				"names"); +		str = textfile_caseget(filename, device->address); +		ecode = remove_device_alias(source, device->address); +	} else { +		str = g_strdup(alias); +		ecode = set_device_alias(dev_id, &bdaddr, alias); +	} + +	if (ecode < 0) +		return g_dbus_create_error(msg, +				ERROR_INTERFACE ".Failed", +				strerror(-ecode)); + +	dbus_connection_emit_property_changed(conn, dbus_message_get_path(msg), +					DEVICE_INTERFACE, "Alias", +					DBUS_TYPE_STRING, &str); + +	g_free(str); + +	return dbus_message_new_method_return(msg); +} + +static DBusMessage *set_trust(DBusConnection *conn, DBusMessage *msg, +					dbus_bool_t value, void *data) +{ +	struct btd_device *device = data; +	struct adapter *adapter = device->adapter; +	bdaddr_t local; +	const gchar *source = adapter_get_address(adapter); + +	str2ba(source, &local); + +	write_trust(&local, device->address, GLOBAL_TRUST, value); + +	dbus_connection_emit_property_changed(conn, dbus_message_get_path(msg), +					DEVICE_INTERFACE, "Trusted", +					DBUS_TYPE_BOOLEAN, &value); + +	return dbus_message_new_method_return(msg); +} + +static inline DBusMessage *invalid_args(DBusMessage *msg) +{ +	return g_dbus_create_error(msg, +			ERROR_INTERFACE ".InvalidArguments", +			"Invalid arguments in method call"); +} + +static DBusMessage *set_property(DBusConnection *conn, +				DBusMessage *msg, void *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("Trusted", 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); + +		return set_trust(conn, msg, value, data); +	} else if (g_str_equal("Alias", property)) { +		char *alias; + +		if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) +			return invalid_args(msg); +		dbus_message_iter_get_basic(&sub, &alias); + +		return set_alias(conn, msg, alias, data); +	} + +	return invalid_args(msg); +} + +static void discover_services_req_exit(void *user_data) +{ +	struct btd_device *device = user_data; +	struct adapter *adapter = device->adapter; +	bdaddr_t src, dst; +	const gchar *source = adapter_get_address(adapter); + +	debug("DiscoverDevices requestor exited"); + +	str2ba(source, &src); +	str2ba(device->address, &dst); + +	bt_cancel_discovery(&src, &dst); +} + +static DBusMessage *discover_services(DBusConnection *conn, +					DBusMessage *msg, void *user_data) +{ +	struct btd_device *device = user_data; +	const char *pattern; +	int err; + +	if (device->discov_active) +		return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress", +							"Discover in progress"); + +	if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern, +						DBUS_TYPE_INVALID) == FALSE) +		goto fail; + +	if (strlen(pattern) == 0) { +		err = device_browse(device, conn, msg, NULL); +		if (err < 0) +			goto fail; +	} else { +		uuid_t uuid; + +		if (bt_string2uuid(&uuid, pattern) < 0) +			return invalid_args(msg); + +		err = device_browse(device, conn, msg, &uuid); +		if (err < 0) +			goto fail; +	} + +	return NULL; + +fail: +	return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", +					"Discovery Failed"); +} + +static DBusMessage *cancel_discover(DBusConnection *conn, +					DBusMessage *msg, void *user_data) +{ +	struct btd_device *device = user_data; +	struct adapter *adapter = device->adapter; +	bdaddr_t src, dst; +	const gchar *source = adapter_get_address(adapter); + +	if (!device->discov_active) +		return g_dbus_create_error(msg, +				ERROR_INTERFACE ".Failed", +				"No pending discovery"); + +	/* only the discover requestor can cancel the inquiry process */ +	if (!device->discov_requestor || +			strcmp(device->discov_requestor, dbus_message_get_sender(msg))) +		return g_dbus_create_error(msg, +				ERROR_INTERFACE ".NotAuthorized", +				"Not Authorized"); + + 	str2ba(source, &src); +	str2ba(device->address, &dst); + +	if (bt_cancel_discovery(&src, &dst) < 0) +		return g_dbus_create_error(msg, +				ERROR_INTERFACE ".Failed", +				"No pending discover"); + +	return dbus_message_new_method_return(msg); +} + +static gboolean disconnect_timeout(gpointer user_data) +{ +	struct btd_device *device = user_data; +	struct active_conn_info *ci; +	GSList *l; +	disconnect_cp cp; +	bdaddr_t bda; +	int dd; +	uint16_t dev_id = adapter_get_dev_id(device->adapter); + +	device->disconn_timer = 0; + +	str2ba(device->address, &bda); +	l = g_slist_find_custom(device->adapter->active_conn, +				&bda, active_conn_find_by_bdaddr); +	if (!l) +		return FALSE; + +	ci = l->data; +	dd = hci_open_dev(dev_id); +	if (dd < 0) +		goto fail; + +	memset(&cp, 0, sizeof(cp)); +	cp.handle = htobs(ci->handle); +	cp.reason = HCI_OE_USER_ENDED_CONNECTION; + +	hci_send_cmd(dd, OGF_LINK_CTL, OCF_DISCONNECT, +			DISCONNECT_CP_SIZE, &cp); + +	close(dd); + +fail: +	return FALSE; +} + +static DBusMessage *disconnect(DBusConnection *conn, +					DBusMessage *msg, void *user_data) +{ +	struct btd_device *device = user_data; +	GSList *l; +	bdaddr_t bda; + +	str2ba(device->address, &bda); +	l = g_slist_find_custom(device->adapter->active_conn, +				&bda, active_conn_find_by_bdaddr); +	if (!l) +		return g_dbus_create_error(msg, +				ERROR_INTERFACE ".NotConnected", +				"Device is not connected"); + +	g_dbus_emit_signal(conn, device->path, +			DEVICE_INTERFACE, "DisconnectRequested", +			DBUS_TYPE_INVALID); + +	device->disconn_timer = g_timeout_add_seconds(DISCONNECT_TIMER, +						disconnect_timeout, device); + +	return dbus_message_new_method_return(msg); +} + +static GDBusMethodTable device_methods[] = { +	{ "GetProperties",	"",	"a{sv}",	get_properties	}, +	{ "SetProperty",	"sv",	"",		set_property	}, +	{ "DiscoverServices",	"s",	"a{us}",	discover_services, +						G_DBUS_METHOD_FLAG_ASYNC}, +	{ "CancelDiscovery",	"",	"",		cancel_discover	}, +	{ "Disconnect",		"",	"",		disconnect	}, +	{ } +}; + +static GDBusSignalTable device_signals[] = { +	{ "PropertyChanged",		"sv"	}, +	{ "DisconnectRequested",	""	}, +	{ } +}; + +struct btd_device *device_create(DBusConnection *conn, struct adapter *adapter, +					const gchar *address) +{ +	gchar *address_up; +	struct btd_device *device; +	uint16_t dev_id = adapter_get_dev_id(adapter); + +	device = g_try_malloc0(sizeof(struct btd_device)); +	if (device == NULL) +		return NULL; + +	address_up = g_ascii_strup(address, -1); +	device->path = g_strdup_printf("/hci%d/dev_%s", +				dev_id, address_up); +	g_strdelimit(device->path, ":", '_'); +	g_free(address_up); + +	debug("Creating device %s", device->path); + +	if (g_dbus_register_interface(conn, device->path, DEVICE_INTERFACE, +				device_methods, device_signals, NULL, +				device, device_free) == FALSE) { +		device_free(device); +		return NULL; +	} + +	device->address = g_strdup(address); +	device->adapter = adapter; + +	return device; +} + +void device_remove(DBusConnection *conn, struct btd_device *device) +{ +	GSList *list; +	struct btd_device_driver *driver; +	gchar *path = g_strdup(device->path); + +	debug("Removing device %s", path); + +	for (list = device->drivers; list; list = list->next) { +		struct btd_driver_data *driver_data = list->data; +		driver = driver_data->driver; + +		driver->remove(driver, device); +		g_free(driver_data); +	} + +	g_dbus_unregister_interface(conn, path, DEVICE_INTERFACE); + +	g_free(path); +} + +gint device_address_cmp(struct btd_device *device, const gchar *address) +{ +	return strcasecmp(device->address, address); +} + +sdp_record_t *get_record(sdp_list_t *recs, const char *uuid) +{ +	sdp_list_t *seq; + +	for (seq = recs; seq; seq = seq->next) { +		sdp_record_t *rec = (sdp_record_t *) seq->data; +		sdp_list_t *svcclass = NULL; +		char *uuid_str; + +		if (sdp_get_service_classes(rec, &svcclass) < 0) +			continue; + +		/* Extract the uuid */ +		uuid_str = bt_uuid2string(svcclass->data); +		if (!uuid_str) +			continue; + +		if (!strcasecmp(uuid_str, uuid)) { +			sdp_list_free(svcclass, free); +			free(uuid_str); +			return rec; +		} +		sdp_list_free(svcclass, free); +		free(uuid_str); +	} +	return NULL; +} + +void device_probe_drivers(struct btd_device *device, GSList *uuids, sdp_list_t *recs) +{ +	GSList *list; +	const char **uuid; +	int err; + +	debug("Probe drivers for %s", device->path); + +	for (list = drivers; list; list = list->next) { +		struct btd_device_driver *driver = list->data; +		GSList *records = NULL; + +		for (uuid = driver->uuids; *uuid; uuid++) { +			GSList *match = g_slist_find_custom(uuids, *uuid, +					(GCompareFunc) strcasecmp); +			if (match) { +				sdp_record_t *rec = get_record(recs, *uuid); + +				records = g_slist_append(records, rec); +			} +		} + +		if (records) { +			struct btd_driver_data *driver_data = g_new0(struct btd_driver_data, 1); + +			err = driver->probe(driver, device, records); +			if (err < 0) { +				error("probe failed for driver %s", +							driver->name); + +				g_free(driver_data); +				continue; +			} + +			driver_data->driver = driver; +			device->drivers = g_slist_append(device->drivers, +								driver_data); +		} +	} + +	for (list = uuids; list; list = list->next) +		device->uuids = g_slist_insert_sorted(device->uuids, +				list->data, (GCompareFunc) strcmp); +} + +void device_remove_drivers(struct btd_device *device, GSList *uuids, sdp_list_t *recs) +{ +	struct adapter *adapter = device_get_adapter(device); +	const gchar *src = adapter_get_address(adapter); +	const gchar *dst = device_get_address(device); +	GSList *list; + +	debug("Remove drivers for %s", device->path); + +	for (list = device->drivers; list; list = list->next) { +		struct btd_driver_data *driver_data = list->data; +		struct btd_device_driver *driver = driver_data->driver; +		const char **uuid; + +		for (uuid = driver->uuids; *uuid; uuid++) { +			GSList *match = g_slist_find_custom(uuids, *uuid, +					(GCompareFunc) strcasecmp); + +			if (!match) +				continue; + +			driver->remove(driver, device); +			device->drivers = g_slist_remove(device->drivers, +								driver_data); + +			g_free(driver_data); + +			sdp_record_t *rec = get_record(recs, *uuid); +			delete_record(src, dst, rec->handle); +		} +	} + +	for (list = uuids; list; list = list->next) +		device->uuids = g_slist_remove(device->uuids, list->data); +} + +static void iter_append_record(DBusMessageIter *dict, uint32_t handle, +							const char *record) +{ +	DBusMessageIter entry; + +	dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, +							NULL, &entry); + +	dbus_message_iter_append_basic(&entry, DBUS_TYPE_UINT32, &handle); + +	dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &record); + +	dbus_message_iter_close_container(dict, &entry); +} + +void append_and_grow_string(void *data, const char *str) +{ +	sdp_buf_t *buff = data; +	int len; + +	len = strlen(str); + +	if (!buff->data) { +		buff->data = malloc(DEFAULT_XML_BUF_SIZE); +		if (!buff->data) +			return; +		buff->buf_size = DEFAULT_XML_BUF_SIZE; +	} + +	/* Grow string */ +	while (buff->buf_size < (buff->data_size + len + 1)) { +		void *tmp; +		uint32_t new_size; + +		/* Grow buffer by a factor of 2 */ +		new_size = (buff->buf_size << 1); + +		tmp = realloc(buff->data, new_size); +		if (!tmp) +			return; + +		buff->data = tmp; +		buff->buf_size = new_size; +	} + +	/* Include the NULL character */ +	memcpy(buff->data + buff->data_size, str, len + 1); +	buff->data_size += len; +} + +static void discover_device_reply(struct browse_req *req, sdp_list_t *recs) +{ +	DBusMessage *reply; +	DBusMessageIter iter, dict; +	sdp_list_t *seq; + +	reply = dbus_message_new_method_return(req->msg); +	if (!reply) +		return; + +	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_UINT32_AS_STRING DBUS_TYPE_STRING_AS_STRING +			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + +	for (seq = recs; seq; seq = seq->next) { +		sdp_record_t *rec = (sdp_record_t *) seq->data; +		sdp_buf_t result; + +		if (!rec) +			break; + +		memset(&result, 0, sizeof(sdp_buf_t)); + +		convert_sdp_record_to_xml(rec, &result, +				append_and_grow_string); + +		if (result.data) { +			const char *val = (char *) result.data; +			iter_append_record(&dict, rec->handle, val); +			free(result.data); +		} +	} + +	dbus_message_iter_close_container(&iter, &dict); + +	dbus_connection_send(req->conn, reply, NULL); +	dbus_message_unref(reply); +} + +static void services_changed(struct browse_req *req) +{ +	struct btd_device *device = req->device; +	char **uuids; +	GSList *l; +	int i; + +	uuids = g_new0(char *, g_slist_length(device->uuids) + 1); +	for (i = 0, l = device->uuids; l; l = l->next, i++) +		uuids[i] = l->data; + +	dbus_connection_emit_property_changed(req->conn, device->path, +					DEVICE_INTERFACE, "UUIDs", +					DBUS_TYPE_ARRAY, &uuids); + +	g_free(uuids); +} + +static void update_services(struct browse_req *req, sdp_list_t *recs) +{ +	struct btd_device *device = req->device; +	struct adapter *adapter = device_get_adapter(device); +	const gchar *src = adapter_get_address(adapter); +	const gchar *dst = device_get_address(device); +	sdp_list_t *seq; + +	for (seq = recs; seq; seq = seq->next) { +		sdp_record_t *rec = (sdp_record_t *) seq->data; +		sdp_list_t *svcclass = NULL; +		gchar *uuid_str; +		GSList *l; + +		if (!rec) +			break; + +		if (sdp_get_service_classes(rec, &svcclass) < 0) +			continue; +		store_record(src, dst, rec); + +		/* Extract the first element and skip the remainning */ +		uuid_str = bt_uuid2string(svcclass->data); +		if (!uuid_str) +			continue; + +		l = g_slist_find_custom(device->uuids, uuid_str, +				(GCompareFunc) strcmp); +		if (!l) +			req->uuids_added = g_slist_append(req->uuids_added, +					uuid_str); +		else { +			req->uuids_removed = g_slist_remove(req->uuids_removed, +					l->data); +			g_free(uuid_str); +		} + +		sdp_list_free(svcclass, free); +	} +} + +static void store(struct btd_device *device) +{ +	struct adapter *adapter = device->adapter; +	bdaddr_t src, dst; +	char *str; +	const gchar *source = adapter_get_address(adapter); + +	str2ba(source, &src); +	str2ba(device->address, &dst); + +	if (!device->uuids) { +		write_device_profiles(&src, &dst, ""); +		return; +	} + +	str = bt_list2string(device->uuids); +	write_device_profiles(&src, &dst, str); +	g_free(str); +} + +static void browse_cb(sdp_list_t *recs, int err, gpointer user_data) +{ +	struct browse_req *req = user_data; +	struct btd_device *device = req->device; +	struct adapter *adapter = device->adapter; +	bdaddr_t src, dst; +	uuid_t uuid; +	DBusMessage *reply; +	const gchar *source = adapter_get_address(adapter); + +	if (err < 0) +		goto proceed; + +	update_services(req, recs); + +	/* Public browsing successful or Single record requested */ +	if (req->browse == FALSE || (!req->search_uuid && recs)) +		goto probe; + +	if (uuid_list[++req->search_uuid]) { +		sdp_uuid16_create(&uuid, uuid_list[req->search_uuid]); +		str2ba(source, &src); +		str2ba(device->address, &dst); +		bt_search_service(&src, &dst, &uuid, browse_cb, user_data, NULL); +		return; +	} + +probe: + +	if (!req->uuids_added && !req->uuids_removed) +		goto proceed; + +	/* Probe matching drivers for services added */ +	if (req->uuids_added) +		device_probe_drivers(device, req->uuids_added, recs); + +	/* Remove drivers for services removed */ +	if (req->uuids_removed) +		device_remove_drivers(device, req->uuids_removed, recs); + +	/* Store the device's profiles in the filesystem */ +	store(device); + +	/* Propagate services changes */ +	services_changed(req); + +proceed: +	if (dbus_message_is_method_call(req->msg, DEVICE_INTERFACE, +			"DiscoverServices")) { +		discover_device_reply(req, recs); +		goto cleanup; +	} + +	g_dbus_emit_signal(req->conn, dbus_message_get_path(req->msg), +				ADAPTER_INTERFACE, "DeviceCreated", +				DBUS_TYPE_OBJECT_PATH, &device->path, +				DBUS_TYPE_INVALID); + +	/* Reply create device request */ +	reply = dbus_message_new_method_return(req->msg); +	if (!reply) +		goto cleanup; + +	dbus_message_append_args(reply, DBUS_TYPE_OBJECT_PATH, &device->path, +							DBUS_TYPE_INVALID); + +	dbus_connection_send(req->conn, reply, NULL); +	dbus_message_unref(reply); + +cleanup: +	device->discov_active = 0; + +	if (device->discov_requestor) { +		g_dbus_remove_watch(req->conn, device->discov_listener); +		device->discov_listener = 0; +		g_free(device->discov_requestor); +		device->discov_requestor = NULL; +	} + +	if (recs != NULL) +		sdp_list_free(recs, (sdp_free_func_t) sdp_record_free); + +	dbus_message_unref(req->msg); +	dbus_connection_unref(req->conn); +	g_slist_free(req->uuids_added); +	g_slist_free(req->uuids_removed); +	g_free(req); +} + +int device_browse(struct btd_device *device, DBusConnection *conn, +			DBusMessage *msg, uuid_t *search) +{ +	struct adapter *adapter = device->adapter; +	struct browse_req *req; +	bdaddr_t src, dst; +	uuid_t uuid; +	GSList *l; +	const gchar *source = adapter_get_address(adapter); + +	req = g_new0(struct browse_req, 1); +	req->conn = dbus_connection_ref(conn); +	req->msg = dbus_message_ref(msg); +	req->device = device; + +	str2ba(source, &src); +	str2ba(device->address, &dst); + +	if (search) { +		memcpy(&uuid, search, sizeof(uuid_t)); +		req->browse = FALSE; +	} else { +		sdp_uuid16_create(&uuid, uuid_list[req->search_uuid]); +		req->browse = TRUE; +		for (l = device->uuids; l; l = l->next) +			req->uuids_removed = g_slist_append(req->uuids_removed, +						l->data); +	} + +	device->discov_active = 1; +	device->discov_requestor = g_strdup(dbus_message_get_sender(msg)); +	/* Track the request owner to cancel it +	 * automatically if the owner exits */ +	device->discov_listener = g_dbus_add_disconnect_watch(conn, +						dbus_message_get_sender(msg), +						discover_services_req_exit, +						device, NULL); + +	return bt_search_service(&src, &dst, &uuid, browse_cb, req, NULL); +} + +struct adapter *device_get_adapter(struct btd_device *device) +{ +	if (!device) +		return NULL; + +	return device->adapter; +} + +const gchar *device_get_address(struct btd_device *device) +{ +	if (!device) +		return NULL; + +	return device->address; +} + +const gchar *device_get_path(struct btd_device *device) +{ +	if (!device) +		return NULL; + +	return device->path; +} + +struct agent *device_get_agent(struct btd_device *device) +{ +	if (!device) +		return NULL; + +	return  device->agent; +} + +void device_set_agent(struct btd_device *device, struct agent *agent) +{ +	if (!device) +		return; + +	device->agent = agent; +} + +gboolean device_is_busy(struct btd_device *device) +{ +	return device->discov_active ? TRUE : FALSE; +} + +gboolean device_is_temporary(struct btd_device *device) +{ +	return device->temporary; +} + +void device_set_temporary(struct btd_device *device, gboolean temporary) +{ +	if (!device) +		return; + +	device->temporary = temporary; +} + +void device_set_cap(struct btd_device *device, uint8_t cap) +{ +	if (!device) +		return; + +	device->cap = cap; +} + +void device_set_auth(struct btd_device *device, uint8_t auth) +{ +	if (!device) +		return; + +	device->auth = auth; +} + +uint8_t device_get_auth(struct btd_device *device) +{ +	return device->auth; +} + +int btd_register_device_driver(struct btd_device_driver *driver) +{ +	const char **uuid; + +	/* FIXME: hack to make hci to resolve service_req_auth symbol*/ +	service_req_auth(NULL, NULL, NULL, NULL, NULL); +	drivers = g_slist_append(drivers, driver); + +	for (uuid = driver->uuids; *uuid; uuid++) { +		debug("name %s uuid %s", driver->name, *uuid); +	} + +	return 0; +} + +void btd_unregister_device_driver(struct btd_device_driver *driver) +{ +	drivers = g_slist_remove(drivers, driver); +}  | 
