/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2012  Intel Corporation. All rights reserved.
 *
 *
 *  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 <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <errno.h>

#include <glib.h>
#include <dbus/dbus.h>
#include <gdbus/gdbus.h>

#include <bluetooth/bluetooth.h>
#include <bluetooth/sdp.h>
#include <bluetooth/sdp_lib.h>

#include "btio/btio.h"
#include "lib/uuid.h"
#include "sdpd.h"
#include "log.h"
#include "error.h"
#include "glib-helper.h"
#include "dbus-common.h"
#include "sdp-client.h"
#include "sdp-xml.h"
#include "adapter.h"
#include "device.h"
#include "profile.h"
#include "service.h"

#define DUN_DEFAULT_CHANNEL	1
#define SPP_DEFAULT_CHANNEL	3
#define HFP_HF_DEFAULT_CHANNEL	7
#define OPP_DEFAULT_CHANNEL	9
#define FTP_DEFAULT_CHANNEL	10
#define BIP_DEFAULT_CHANNEL	11
#define HSP_AG_DEFAULT_CHANNEL	12
#define HFP_AG_DEFAULT_CHANNEL	13
#define SYNC_DEFAULT_CHANNEL	14
#define PBAP_DEFAULT_CHANNEL	15
#define MAS_DEFAULT_CHANNEL	16
#define MNS_DEFAULT_CHANNEL	17

#define BTD_PROFILE_PSM_AUTO	-1
#define BTD_PROFILE_CHAN_AUTO	-1

#define HFP_HF_RECORD							\
	"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>			\
	<record>							\
		<attribute id=\"0x0001\">				\
			<sequence>					\
				<uuid value=\"0x111e\" />		\
				<uuid value=\"0x1203\" />		\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0004\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"0x0100\" />	\
				</sequence>				\
				<sequence>				\
					<uuid value=\"0x0003\" />	\
					<uint8 value=\"0x%02x\" />	\
				</sequence>				\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0005\">				\
			<sequence>					\
				<uuid value=\"0x1002\" />		\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0009\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"0x111e\" />	\
					<uint16 value=\"0x%04x\" />	\
				</sequence>				\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0100\">				\
			<text value=\"%s\" />				\
		</attribute>						\
		<attribute id=\"0x0311\">				\
			<uint16 value=\"0x%04x\" />			\
		</attribute>						\
	</record>"

#define HFP_AG_RECORD							\
	"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>			\
	<record>							\
		<attribute id=\"0x0001\">				\
			<sequence>					\
				<uuid value=\"0x111f\" />		\
				<uuid value=\"0x1203\" />		\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0004\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"0x0100\" />	\
				</sequence>				\
				<sequence>				\
					<uuid value=\"0x0003\" />	\
					<uint8 value=\"0x%02x\" />	\
				</sequence>				\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0005\">				\
			<sequence>					\
				<uuid value=\"0x1002\" />		\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0009\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"0x111e\" />	\
					<uint16 value=\"0x%04x\" />	\
				</sequence>				\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0100\">				\
			<text value=\"%s\" />				\
		</attribute>						\
		<attribute id=\"0x0311\">				\
			<uint16 value=\"0x%04x\" />			\
		</attribute>						\
		<attribute id=\"0x0301\" >				\
			<uint8 value=\"0x01\" />			\
		</attribute>						\
	</record>"

#define SPP_RECORD							\
	"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>			\
	<record>							\
		<attribute id=\"0x0001\">				\
			<sequence>					\
				<uuid value=\"0x1101\" />		\
				%s					\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0004\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"0x0100\" />	\
				</sequence>				\
				<sequence>				\
					<uuid value=\"0x0003\" />	\
					<uint8 value=\"0x%02x\" />	\
				</sequence>				\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0005\">				\
			<sequence>					\
				<uuid value=\"0x1002\" />		\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0009\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"0x1101\" />	\
					<uint16 value=\"0x%04x\" />	\
				</sequence>				\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0100\">				\
			<text value=\"%s\" />				\
		</attribute>						\
	</record>"

#define DUN_RECORD							\
	"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>			\
	<record>							\
		<attribute id=\"0x0001\">				\
			<sequence>					\
				<uuid value=\"0x1103\" />		\
				<uuid value=\"0x1201\" />		\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0004\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"0x0100\" />	\
				</sequence>				\
				<sequence>				\
					<uuid value=\"0x0003\" />	\
					<uint8 value=\"0x%02x\" />	\
				</sequence>				\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0005\">				\
			<sequence>					\
				<uuid value=\"0x1002\" />		\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0009\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"0x1103\" />	\
					<uint16 value=\"0x%04x\" />	\
				</sequence>				\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0100\">				\
			<text value=\"%s\" />				\
		</attribute>						\
	</record>"

#define OPP_RECORD							\
	"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>			\
	<record>							\
		<attribute id=\"0x0001\">				\
			<sequence>					\
				<uuid value=\"0x1105\" />		\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0004\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"0x0100\" />	\
				</sequence>				\
				<sequence>				\
					<uuid value=\"0x0003\" />	\
					<uint8 value=\"0x%02x\" />	\
				</sequence>				\
				<sequence>				\
					<uuid value=\"0x0008\"/>	\
				</sequence>				\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0005\">				\
			<sequence>					\
				<uuid value=\"0x1002\" />		\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0009\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"0x1105\" />	\
					<uint16 value=\"0x%04x\" />	\
				</sequence>				\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0303\">				\
			<sequence>					\
				<uint8 value=\"0x01\"/>			\
				<uint8 value=\"0x02\"/>			\
				<uint8 value=\"0x03\"/>			\
				<uint8 value=\"0x04\"/>			\
				<uint8 value=\"0x05\"/>			\
				<uint8 value=\"0x06\"/>			\
				<uint8 value=\"0xff\"/>			\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0200\">				\
			<uint16 value=\"%u\" name=\"psm\"/>		\
		</attribute>						\
		<attribute id=\"0x0100\">				\
			<text value=\"%s\" />				\
		</attribute>						\
	</record>"

#define FTP_RECORD							\
	"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>			\
	<record>							\
		<attribute id=\"0x0001\">				\
			<sequence>					\
				<uuid value=\"0x1106\" />		\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0004\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"0x0100\" />	\
				</sequence>				\
				<sequence>				\
					<uuid value=\"0x0003\" />	\
					<uint8 value=\"0x%02x\" />	\
				</sequence>				\
				<sequence>				\
					<uuid value=\"0x0008\"/>	\
				</sequence>				\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0005\">				\
			<sequence>					\
				<uuid value=\"0x1002\" />		\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0009\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"0x1106\" />	\
					<uint16 value=\"0x%04x\" />	\
				</sequence>				\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0200\">				\
			<uint16 value=\"%u\" name=\"psm\"/>		\
		</attribute>						\
		<attribute id=\"0x0100\">				\
			<text value=\"%s\" />				\
		</attribute>						\
	</record>"

#define PCE_RECORD							\
	"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>			\
	<record>							\
		<attribute id=\"0x0001\">				\
			<sequence>					\
				<uuid value=\"0x112e\" />		\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0005\">				\
			<sequence>					\
				<uuid value=\"0x1002\" />		\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0009\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"0x1130\" />	\
					<uint16 value=\"0x%04x\" />	\
				</sequence>				\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0100\">				\
			<text value=\"%s\" />				\
		</attribute>						\
	</record>"

#define PSE_RECORD							\
	"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>			\
	<record>							\
		<attribute id=\"0x0001\">				\
			<sequence>					\
				<uuid value=\"0x112f\" />		\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0004\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"0x0100\" />	\
				</sequence>				\
				<sequence>				\
					<uuid value=\"0x0003\" />	\
					<uint8 value=\"0x%02x\" />	\
				</sequence>				\
				<sequence>				\
					<uuid value=\"0x0008\"/>	\
				</sequence>				\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0005\">				\
			<sequence>					\
				<uuid value=\"0x1002\" />		\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0009\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"0x1130\" />	\
					<uint16 value=\"0x%04x\" />	\
				</sequence>				\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0100\">				\
			<text value=\"%s\" />				\
		</attribute>						\
		<attribute id=\"0x0314\">				\
			<uint8 value=\"0x01\"/>				\
		</attribute>						\
	</record>"

#define MAS_RECORD							\
	"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>			\
	<record>							\
		<attribute id=\"0x0001\">				\
			<sequence>					\
				<uuid value=\"0x1132\"/>		\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0004\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"0x0100\"/>	\
				</sequence>				\
				<sequence>				\
					<uuid value=\"0x0003\"/>	\
					<uint8 value=\"0x%02x\"/>	\
				</sequence>				\
				<sequence>				\
					<uuid value=\"0x0008\"/>	\
				</sequence>				\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0009\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"0x1134\"/>	\
					<uint16 value=\"0x%04x\" />	\
				</sequence>				\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0100\">				\
			<text value=\"%s\"/>				\
		</attribute>						\
		<attribute id=\"0x0315\">				\
			<uint8 value=\"0x00\"/>				\
		</attribute>						\
		<attribute id=\"0x0316\">				\
			<uint8 value=\"0x0F\"/>				\
		</attribute>						\
	</record>"

#define MNS_RECORD							\
	"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>			\
	<record>							\
		<attribute id=\"0x0001\">				\
			<sequence>					\
				<uuid value=\"0x1133\"/>		\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0004\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"0x0100\"/>	\
				</sequence>				\
				<sequence>				\
					<uuid value=\"0x0003\"/>	\
					<uint8 value=\"0x%02x\"/>	\
				</sequence>				\
				<sequence>				\
					<uuid value=\"0x0008\"/>	\
				</sequence>				\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0009\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"0x1134\"/>	\
					<uint16 value=\"0x%04x\"/>	\
				</sequence>				\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0100\">				\
			<text value=\"%s\"/>				\
		</attribute>						\
		<attribute id=\"0x0200\">				\
			<uint16 value=\"%u\" name=\"psm\"/>		\
		</attribute>						\
	</record>"

#define SYNC_RECORD							\
	"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>			\
	<record>							\
		<attribute id=\"0x0001\">				\
			<sequence>					\
				<uuid value=\"0x1104\"/>		\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0004\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"0x0100\"/>	\
				</sequence>				\
				<sequence>				\
					<uuid value=\"0x0003\"/>	\
					<uint8 value=\"0x%02x\"/>	\
				</sequence>				\
				<sequence>				\
					<uuid value=\"0x0008\"/>	\
				</sequence>				\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0009\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"0x1104\"/>	\
					<uint16 value=\"0x%04x\" />	\
				 </sequence>				\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0100\">				\
			<text value=\"%s\"/>				\
		</attribute>						\
		<attribute id=\"0x0301\">				\
			<sequence>					\
				<uint8 value=\"0x01\"/>			\
			</sequence>					\
		</attribute>						\
	</record>"

#define GENERIC_RECORD							\
	"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>			\
	<record>							\
		<attribute id=\"0x0001\">				\
			<sequence>					\
				<uuid value=\"%s\" />			\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0004\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"0x0100\" />	\
					%s				\
				</sequence>				\
				%s					\
			</sequence>					\
		</attribute>						\
		<attribute id=\"0x0005\">				\
			<sequence>					\
				<uuid value=\"0x1002\" />		\
			</sequence>					\
		</attribute>						\
		%s							\
		<attribute id=\"0x0100\">				\
			<text value=\"%s\" />				\
		</attribute>						\
	</record>"

struct ext_io;

struct ext_profile {
	struct btd_profile p;

	char *name;
	char *owner;
	char *path;
	char *uuid;
	char *service;
	char *role;

	char *record;
	char *(*get_record)(struct ext_profile *ext, struct ext_io *l2cap,
							struct ext_io *rfcomm);

	char *remote_uuid;

	guint id;

	BtIOMode mode;
	BtIOSecLevel sec_level;
	bool authorize;

	bool enable_client;
	bool enable_server;

	int local_psm;
	int local_chan;

	uint16_t remote_psm;
	uint8_t remote_chan;

	uint16_t version;
	uint16_t features;

	GSList *records;
	GSList *servers;
	GSList *conns;

	GSList *connects;
};

struct ext_io {
	struct ext_profile *ext;
	int proto;
	GIOChannel *io;
	guint io_id;
	struct btd_adapter *adapter;
	struct btd_device *device;
	struct btd_service *service;

	bool resolving;
	bool connected;

	uint16_t version;
	uint16_t features;

	uint16_t psm;
	uint8_t chan;

	guint auth_id;
	unsigned int svc_id;
	DBusPendingCall *pending;
};

struct ext_record {
	struct btd_adapter *adapter;
	uint32_t handle;
};

struct btd_profile_custom_property {
	char *uuid;
	char *type;
	char *name;
	btd_profile_prop_exists exists;
	btd_profile_prop_get get;
	void *user_data;
};

static GSList *custom_props = NULL;

static GSList *profiles = NULL;
static GSList *ext_profiles = NULL;

void btd_profile_foreach(void (*func)(struct btd_profile *p, void *data),
								void *data)
{
	GSList *l, *next;

	for (l = profiles; l != NULL; l = next) {
		struct btd_profile *profile = l->data;

		next = g_slist_next(l);

		func(profile, data);
	}

	for (l = ext_profiles; l != NULL; l = next) {
		struct ext_profile *profile = l->data;

		next = g_slist_next(l);

		func(&profile->p, data);
	}
}

int btd_profile_register(struct btd_profile *profile)
{
	profiles = g_slist_append(profiles, profile);
	return 0;
}

void btd_profile_unregister(struct btd_profile *profile)
{
	profiles = g_slist_remove(profiles, profile);
}

static struct ext_profile *find_ext_profile(const char *owner,
						const char *path)
{
	GSList *l;

	for (l = ext_profiles; l != NULL; l = g_slist_next(l)) {
		struct ext_profile *ext = l->data;

		if (!g_str_equal(ext->owner, owner))
			continue;

		if (g_str_equal(ext->path, path))
			return ext;
	}

	return NULL;
}

static void ext_io_destroy(gpointer p)
{
	struct ext_io *ext_io = p;

	if (ext_io->io_id > 0)
		g_source_remove(ext_io->io_id);

	if (ext_io->io) {
		g_io_channel_shutdown(ext_io->io, FALSE, NULL);
		g_io_channel_unref(ext_io->io);
	}

	if (ext_io->auth_id != 0)
		btd_cancel_authorization(ext_io->auth_id);

	if (ext_io->svc_id != 0)
		device_remove_svc_complete_callback(ext_io->device,
							ext_io->svc_id);

	if (ext_io->pending) {
		dbus_pending_call_cancel(ext_io->pending);
		dbus_pending_call_unref(ext_io->pending);
	}

	if (ext_io->resolving)
		bt_cancel_discovery(adapter_get_address(ext_io->adapter),
					device_get_address(ext_io->device));

	if (ext_io->adapter)
		btd_adapter_unref(ext_io->adapter);

	if (ext_io->device)
		btd_device_unref(ext_io->device);

	if (ext_io->service)
		btd_service_unref(ext_io->service);

	g_free(ext_io);
}

static gboolean ext_io_disconnected(GIOChannel *io, GIOCondition cond,
							gpointer user_data)
{
	struct ext_io *conn = user_data;
	struct ext_profile *ext = conn->ext;
	GError *gerr = NULL;
	char addr[18];

	if (cond & G_IO_NVAL)
		return FALSE;

	bt_io_get(io, &gerr, BT_IO_OPT_DEST, addr, BT_IO_OPT_INVALID);
	if (gerr != NULL) {
		error("Unable to get io data for %s: %s",
						ext->name, gerr->message);
		g_error_free(gerr);
		goto drop;
	}

	DBG("%s disconnected from %s", ext->name, addr);
drop:
	btd_service_disconnecting_complete(conn->service, 0);
	ext->conns = g_slist_remove(ext->conns, conn);
	ext_io_destroy(conn);
	return FALSE;
}

static void new_conn_reply(DBusPendingCall *call, void *user_data)
{
	struct ext_io *conn = user_data;
	struct ext_profile *ext = conn->ext;
	DBusMessage *reply = dbus_pending_call_steal_reply(call);
	DBusError err;

	dbus_error_init(&err);
	dbus_set_error_from_message(&err, reply);

	dbus_message_unref(reply);

	dbus_pending_call_unref(conn->pending);
	conn->pending = NULL;

	if (!dbus_error_is_set(&err)) {
		btd_service_connecting_complete(conn->service, 0);
		conn->connected = true;
		return;
	}

	error("%s replied with an error: %s, %s", ext->name,
						err.name, err.message);

	btd_service_connecting_complete(conn->service, -ECONNREFUSED);

	dbus_error_free(&err);

	ext->conns = g_slist_remove(ext->conns, conn);
	ext_io_destroy(conn);
}

static void disconn_reply(DBusPendingCall *call, void *user_data)
{
	struct ext_io *conn = user_data;
	struct ext_profile *ext = conn->ext;
	DBusMessage *reply = dbus_pending_call_steal_reply(call);
	DBusError err;

	dbus_error_init(&err);
	dbus_set_error_from_message(&err, reply);

	dbus_message_unref(reply);

	dbus_pending_call_unref(conn->pending);
	conn->pending = NULL;

	if (!dbus_error_is_set(&err)) {
		btd_service_disconnecting_complete(conn->service, 0);
		goto disconnect;
	}

	error("%s replied with an error: %s, %s", ext->name,
						err.name, err.message);

	btd_service_disconnecting_complete(conn->service, -ECONNREFUSED);

	dbus_error_free(&err);

disconnect:
	ext->conns = g_slist_remove(ext->conns, conn);
	ext_io_destroy(conn);
}

struct prop_append_data {
	DBusMessageIter *dict;
	struct ext_io *io;
};

static void append_prop(gpointer a, gpointer b)
{
	struct btd_profile_custom_property *p = a;
	struct prop_append_data *data = b;
	DBusMessageIter entry, value, *dict = data->dict;
	struct btd_device *dev = data->io->device;
	struct ext_profile *ext = data->io->ext;
	const char *uuid = ext->service ? ext->service : ext->uuid;

	if (strcasecmp(p->uuid, uuid) != 0)
		return;

	if (p->exists && !p->exists(p->uuid, dev, p->user_data))
		return;

	dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL,
								&entry);
	dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &p->name);
	dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, p->type,
								&value);

	p->get(p->uuid, dev, &value, p->user_data);

	dbus_message_iter_close_container(&entry, &value);
	dbus_message_iter_close_container(dict, &entry);
}

static uint16_t get_supported_features(const sdp_record_t *rec)
{
	sdp_data_t *data;

	data = sdp_data_get(rec, SDP_ATTR_SUPPORTED_FEATURES);
	if (!data || data->dtd != SDP_UINT16)
		return 0;

	return data->val.uint16;
}

static uint16_t get_profile_version(const sdp_record_t *rec)
{
	sdp_list_t *descs;
	uint16_t version;

	if (sdp_get_profile_descs(rec, &descs) < 0)
		return 0;

	if (descs && descs->data) {
		sdp_profile_desc_t *desc = descs->data;
		version = desc->version;
	} else {
		version = 0;
	}

	sdp_list_free(descs, free);

	return version;
}

static bool send_new_connection(struct ext_profile *ext, struct ext_io *conn)
{
	DBusMessage *msg;
	DBusMessageIter iter, dict;
	struct prop_append_data data = { &dict, conn };
	const char *remote_uuid = ext->remote_uuid;
	const sdp_record_t *rec;
	const char *path;
	int fd;

	msg = dbus_message_new_method_call(ext->owner, ext->path,
							"org.bluez.Profile1",
							"NewConnection");
	if (!msg) {
		error("Unable to create NewConnection call for %s", ext->name);
		return false;
	}

	if (remote_uuid) {
		rec = btd_device_get_record(conn->device, remote_uuid);
		if (rec) {
			conn->features = get_supported_features(rec);
			conn->version = get_profile_version(rec);
		}
	}

	dbus_message_iter_init_append(msg, &iter);

	path = device_get_path(conn->device);
	dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path);

	fd = g_io_channel_unix_get_fd(conn->io);
	dbus_message_iter_append_basic(&iter, DBUS_TYPE_UNIX_FD, &fd);

	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &dict);

	if (conn->version)
		dict_append_entry(&dict, "Version", DBUS_TYPE_UINT16,
							&conn->version);

	if (conn->features)
		dict_append_entry(&dict, "Features", DBUS_TYPE_UINT16,
							&conn->features);

	g_slist_foreach(custom_props, append_prop, &data);

	dbus_message_iter_close_container(&iter, &dict);

	if (!g_dbus_send_message_with_reply(btd_get_dbus_connection(),
						msg, &conn->pending, -1)) {
		error("%s: sending NewConnection failed", ext->name);
		dbus_message_unref(msg);
		return false;
	}

	dbus_message_unref(msg);

	dbus_pending_call_set_notify(conn->pending, new_conn_reply, conn, NULL);

	return true;
}

static void ext_connect(GIOChannel *io, GError *err, gpointer user_data)
{
	struct ext_io *conn = user_data;
	struct ext_profile *ext = conn->ext;
	GError *io_err = NULL;
	char addr[18];

	if (!bt_io_get(io, &io_err,
				BT_IO_OPT_DEST, addr,
				BT_IO_OPT_INVALID)) {
		error("Unable to get connect data for %s: %s", ext->name,
							io_err->message);
		if (err) {
			g_error_free(io_err);
			io_err = NULL;
		} else {
			err = io_err;
		}
		goto drop;
	}

	if (err != NULL) {
		error("%s failed to connect to %s: %s", ext->name, addr,
								err->message);
		goto drop;
	}

	DBG("%s connected to %s", ext->name, addr);

	if (conn->io_id == 0) {
		GIOCondition cond = G_IO_HUP | G_IO_ERR | G_IO_NVAL;
		conn->io_id = g_io_add_watch(io, cond, ext_io_disconnected,
									conn);
	}

	if (send_new_connection(ext, conn))
		return;

drop:
	btd_service_connecting_complete(conn->service, err ? -err->code : -EIO);
	if (io_err)
		g_error_free(io_err);
	ext->conns = g_slist_remove(ext->conns, conn);
	ext_io_destroy(conn);
}

static void ext_auth(DBusError *err, void *user_data)
{
	struct ext_io *conn = user_data;
	struct ext_profile *ext = conn->ext;
	GError *gerr = NULL;
	char addr[18];

	conn->auth_id = 0;

	bt_io_get(conn->io, &gerr, BT_IO_OPT_DEST, addr, BT_IO_OPT_INVALID);
	if (gerr != NULL) {
		error("Unable to get connect data for %s: %s",
						ext->name, gerr->message);
		g_error_free(gerr);
		goto drop;
	}

	if (err && dbus_error_is_set(err)) {
		error("%s rejected %s: %s", ext->name, addr, err->message);
		goto drop;
	}

	if (conn->svc_id > 0) {
		DBG("Connection from %s authorized but still waiting for SDP",
									addr);
		return;
	}

	if (!bt_io_accept(conn->io, ext_connect, conn, NULL, &gerr)) {
		error("bt_io_accept: %s", gerr->message);
		g_error_free(gerr);
		goto drop;
	}

	DBG("%s authorized to connect to %s", addr, ext->name);

	return;

drop:
	ext->conns = g_slist_remove(ext->conns, conn);
	ext_io_destroy(conn);
}

static struct ext_io *create_conn(struct ext_io *server, GIOChannel *io,
						bdaddr_t *src, bdaddr_t *dst)
{
	struct btd_device *device;
	struct btd_service *service;
	struct ext_io *conn;
	GIOCondition cond;
	char addr[18];

	device = adapter_find_device(server->adapter, dst);
	if (device == NULL) {
		ba2str(dst, addr);
		error("%s device %s not found", server->ext->name, addr);
		return NULL;
	}

	btd_device_add_uuid(device, server->ext->remote_uuid);
	service = btd_device_get_service(device, server->ext->remote_uuid);
	if (service == NULL) {
		ba2str(dst, addr);
		error("%s service not found for device %s", server->ext->name,
									addr);
		return NULL;
	}

	conn = g_new0(struct ext_io, 1);
	conn->io = g_io_channel_ref(io);
	conn->proto = server->proto;
	conn->ext = server->ext;
	conn->adapter = btd_adapter_ref(server->adapter);
	conn->device = btd_device_ref(device);
	conn->service = btd_service_ref(service);

	cond = G_IO_HUP | G_IO_ERR | G_IO_NVAL;
	conn->io_id = g_io_add_watch(io, cond, ext_io_disconnected, conn);

	return conn;
}

static void ext_svc_complete(struct btd_device *dev, int err, void *user_data)
{
	struct ext_io *conn = user_data;
	struct ext_profile *ext = conn->ext;
	const bdaddr_t *bdaddr;
	GError *gerr = NULL;
	char addr[18];

	conn->svc_id = 0;

	bdaddr = device_get_address(dev);
	ba2str(bdaddr, addr);

	if (err < 0) {
		error("Service resolving failed for %s: %s (%d)",
						addr, strerror(-err), -err);
		goto drop;
	}

	DBG("Services resolved for %s", addr);

	if (conn->auth_id > 0) {
		DBG("Services resolved but still waiting for authorization");
		return;
	}

	if (!bt_io_accept(conn->io, ext_connect, conn, NULL, &gerr)) {
		error("bt_io_accept: %s", gerr->message);
		g_error_free(gerr);
		goto drop;
	}

	DBG("%s authorized to connect to %s", addr, ext->name);

	return;

drop:
	ext->conns = g_slist_remove(ext->conns, conn);
	ext_io_destroy(conn);
}

static void ext_confirm(GIOChannel *io, gpointer user_data)
{
	struct ext_io *server = user_data;
	struct ext_profile *ext = server->ext;
	const char *uuid = ext->service ? ext->service : ext->uuid;
	struct ext_io *conn;
	GError *gerr = NULL;
	bdaddr_t src, dst;
	char addr[18];

	bt_io_get(io, &gerr,
			BT_IO_OPT_SOURCE_BDADDR, &src,
			BT_IO_OPT_DEST_BDADDR, &dst,
			BT_IO_OPT_DEST, addr,
			BT_IO_OPT_INVALID);
	if (gerr != NULL) {
		error("%s failed to get connect data: %s", ext->name,
								gerr->message);
		g_error_free(gerr);
		return;
	}

	DBG("incoming connect from %s", addr);

	conn = create_conn(server, io, &src, &dst);
	if (conn == NULL)
		return;

	conn->auth_id = btd_request_authorization(&src, &dst, uuid, ext_auth,
									conn);
	if (conn->auth_id == 0) {
		error("%s authorization failure", ext->name);
		ext_io_destroy(conn);
		return;
	}

	ext->conns = g_slist_append(ext->conns, conn);

	conn->svc_id = device_wait_for_svc_complete(conn->device,
							ext_svc_complete,
							conn);

	DBG("%s authorizing connection from %s", ext->name, addr);
}

static void ext_direct_connect(GIOChannel *io, GError *err, gpointer user_data)
{
	struct ext_io *server = user_data;
	struct ext_profile *ext = server->ext;
	GError *gerr = NULL;
	struct ext_io *conn;
	bdaddr_t src, dst;

	bt_io_get(io, &gerr,
			BT_IO_OPT_SOURCE_BDADDR, &src,
			BT_IO_OPT_DEST_BDADDR, &dst,
			BT_IO_OPT_INVALID);
	if (gerr != NULL) {
		error("%s failed to get connect data: %s", ext->name,
								gerr->message);
		g_error_free(gerr);
		return;
	}

	conn = create_conn(server, io, &src, &dst);
	if (conn == NULL)
		return;

	ext->conns = g_slist_append(ext->conns, conn);

	ext_connect(io, err, conn);
}

static uint32_t ext_register_record(struct ext_profile *ext,
							struct ext_io *l2cap,
							struct ext_io *rfcomm,
							const bdaddr_t *src)
{
	sdp_record_t *rec;
	char *dyn_record = NULL;
	const char *record = ext->record;

	if (!record && ext->get_record) {
		dyn_record = ext->get_record(ext, l2cap, rfcomm);
		record = dyn_record;
	}

	if (!record)
		return 0;

	rec = sdp_xml_parse_record(record, strlen(record));

	g_free(dyn_record);

	if (!rec) {
		error("Unable to parse record for %s", ext->name);
		return 0;
	}

	if (add_record_to_server(src, rec) < 0) {
		error("Failed to register service record");
		sdp_record_free(rec);
		return 0;
	}

	return rec->handle;
}

static uint32_t ext_start_servers(struct ext_profile *ext,
						struct btd_adapter *adapter)
{
	struct ext_io *l2cap = NULL;
	struct ext_io *rfcomm = NULL;
	BtIOConfirm confirm;
	BtIOConnect connect;
	GError *err = NULL;
	GIOChannel *io;

	if (ext->authorize) {
		confirm = ext_confirm;
		connect = NULL;
	} else {
		confirm = NULL;
		connect = ext_direct_connect;
	}

	if (ext->local_psm) {
		uint16_t psm;

		if (ext->local_psm > 0)
			psm = ext->local_psm;
		else
			psm = 0;

		l2cap = g_new0(struct ext_io, 1);
		l2cap->ext = ext;

		io = bt_io_listen(connect, confirm, l2cap, NULL, &err,
					BT_IO_OPT_SOURCE_BDADDR,
					adapter_get_address(adapter),
					BT_IO_OPT_MODE, ext->mode,
					BT_IO_OPT_PSM, psm,
					BT_IO_OPT_SEC_LEVEL, ext->sec_level,
					BT_IO_OPT_INVALID);
		if (err != NULL) {
			error("L2CAP server failed for %s: %s",
						ext->name, err->message);
			g_free(l2cap);
			l2cap = NULL;
			g_clear_error(&err);
			goto failed;
		} else {
			if (psm == 0)
				bt_io_get(io, NULL, BT_IO_OPT_PSM, &psm,
							BT_IO_OPT_INVALID);
			l2cap->io = io;
			l2cap->proto = BTPROTO_L2CAP;
			l2cap->psm = psm;
			l2cap->adapter = btd_adapter_ref(adapter);
			ext->servers = g_slist_append(ext->servers, l2cap);
			DBG("%s listening on PSM %u", ext->name, psm);
		}
	}

	if (ext->local_chan) {
		uint8_t chan;

		if (ext->local_chan > 0)
			chan = ext->local_chan;
		else
			chan = 0;

		rfcomm = g_new0(struct ext_io, 1);
		rfcomm->ext = ext;

		io = bt_io_listen(connect, confirm, rfcomm, NULL, &err,
					BT_IO_OPT_SOURCE_BDADDR,
					adapter_get_address(adapter),
					BT_IO_OPT_CHANNEL, chan,
					BT_IO_OPT_SEC_LEVEL, ext->sec_level,
					BT_IO_OPT_INVALID);
		if (err != NULL) {
			error("RFCOMM server failed for %s: %s",
						ext->name, err->message);
			g_free(rfcomm);
			rfcomm = NULL;
			g_clear_error(&err);
			goto failed;
		} else {
			if (chan == 0)
				bt_io_get(io, NULL, BT_IO_OPT_CHANNEL, &chan,
							BT_IO_OPT_INVALID);
			rfcomm->io = io;
			rfcomm->proto = BTPROTO_RFCOMM;
			rfcomm->chan = chan;
			rfcomm->adapter = btd_adapter_ref(adapter);
			ext->servers = g_slist_append(ext->servers, rfcomm);
			DBG("%s listening on chan %u", ext->name, chan);
		}
	}

	return ext_register_record(ext, l2cap, rfcomm,
						adapter_get_address(adapter));

failed:
	if (l2cap) {
		ext->servers = g_slist_remove(ext->servers, l2cap);
		ext_io_destroy(l2cap);
	}
	if (rfcomm) {
		ext->servers = g_slist_remove(ext->servers, rfcomm);
		ext_io_destroy(rfcomm);
	}

	return 0;
}

static struct ext_profile *find_ext(struct btd_profile *p)
{
	GSList *l;

	l = g_slist_find(ext_profiles, p);
	if (!l)
		return NULL;

	return l->data;
}

static int ext_adapter_probe(struct btd_profile *p,
						struct btd_adapter *adapter)
{
	struct ext_profile *ext;
	struct ext_record *rec;
	uint32_t handle;

	ext = find_ext(p);
	if (!ext)
		return -ENOENT;

	DBG("\"%s\" probed", ext->name);

	handle = ext_start_servers(ext, adapter);
	if (!handle)
		return 0;

	rec = g_new0(struct ext_record, 1);
	rec->adapter = btd_adapter_ref(adapter);
	rec->handle = handle;

	ext->records = g_slist_append(ext->records, rec);

	return 0;
}

static void ext_remove_records(struct ext_profile *ext,
						struct btd_adapter *adapter)
{
	GSList *l, *next;

	for (l = ext->records; l != NULL; l = next) {
		struct ext_record *r = l->data;

		next = g_slist_next(l);

		if (adapter && r->adapter != adapter)
			continue;

		ext->records = g_slist_remove(ext->records, r);

		remove_record_from_server(r->handle);
		btd_adapter_unref(r->adapter);
		g_free(r);
	}
}

static void ext_adapter_remove(struct btd_profile *p,
						struct btd_adapter *adapter)
{
	struct ext_profile *ext;
	GSList *l, *next;

	ext = find_ext(p);
	if (!ext)
		return;

	DBG("\"%s\" removed", ext->name);

	ext_remove_records(ext, adapter);

	for (l = ext->servers; l != NULL; l = next) {
		struct ext_io *server = l->data;

		next = g_slist_next(l);

		if (server->adapter != adapter)
			continue;

		ext->servers = g_slist_remove(ext->servers, server);
		ext_io_destroy(server);
	}
}

static int ext_device_probe(struct btd_service *service)
{
	struct btd_profile *p = btd_service_get_profile(service);
	struct ext_profile *ext;

	ext = find_ext(p);
	if (!ext)
		return -ENOENT;

	DBG("%s probed with UUID %s", ext->name, p->remote_uuid);

	return 0;
}

static struct ext_io *find_connection(struct ext_profile *ext,
							struct btd_device *dev)
{
	GSList *l;

	for (l = ext->conns; l != NULL; l = g_slist_next(l)) {
		struct ext_io *conn = l->data;

		if (conn->device == dev)
			return conn;
	}

	return NULL;
}

static void ext_device_remove(struct btd_service *service)
{
	struct btd_profile *p = btd_service_get_profile(service);
	struct btd_device *dev = btd_service_get_device(service);
	struct ext_profile *ext;
	struct ext_io *conn;

	ext = find_ext(p);
	if (!ext)
		return;

	DBG("%s", ext->name);

	conn = find_connection(ext, dev);
	if (conn) {
		ext->conns = g_slist_remove(ext->conns, conn);
		ext_io_destroy(conn);
	}
}

static int connect_io(struct ext_io *conn, const bdaddr_t *src,
							const bdaddr_t *dst)
{
	struct ext_profile *ext = conn->ext;
	GError *gerr = NULL;
	GIOChannel *io;

	if (conn->psm) {
		conn->proto = BTPROTO_L2CAP;
		io = bt_io_connect(ext_connect, conn, NULL, &gerr,
					BT_IO_OPT_SOURCE_BDADDR, src,
					BT_IO_OPT_DEST_BDADDR, dst,
					BT_IO_OPT_SEC_LEVEL, ext->sec_level,
					BT_IO_OPT_PSM, conn->psm,
					BT_IO_OPT_INVALID);
	} else {
		conn->proto = BTPROTO_RFCOMM;
		io = bt_io_connect(ext_connect, conn, NULL, &gerr,
					BT_IO_OPT_SOURCE_BDADDR, src,
					BT_IO_OPT_DEST_BDADDR, dst,
					BT_IO_OPT_SEC_LEVEL, ext->sec_level,
					BT_IO_OPT_CHANNEL, conn->chan,
					BT_IO_OPT_INVALID);
	}

	if (gerr != NULL) {
		error("Unable to connect %s: %s", ext->name, gerr->message);
		g_error_free(gerr);
		return -EIO;
	}

	conn->io = io;

	return 0;
}

static uint16_t get_goep_l2cap_psm(sdp_record_t *rec)
{
	sdp_data_t *data;

	data = sdp_data_get(rec, SDP_ATTR_GOEP_L2CAP_PSM);
	if (!data)
		return 0;

	if (data->dtd != SDP_UINT16)
		return 0;

	/* PSM must be odd and lsb of upper byte must be 0 */
	if ((data->val.uint16 & 0x0101) != 0x0001)
		return 0;

	return data->val.uint16;
}

static void record_cb(sdp_list_t *recs, int err, gpointer user_data)
{
	struct ext_io *conn = user_data;
	struct ext_profile *ext = conn->ext;
	sdp_list_t *r;

	conn->resolving = false;

	if (err < 0) {
		error("Unable to get %s SDP record: %s", ext->name,
							strerror(-err));
		goto failed;
	}

	if (!recs || !recs->data) {
		error("No SDP records found for %s", ext->name);
		goto failed;
	}

	for (r = recs; r != NULL; r = r->next) {
		sdp_record_t *rec = r->data;
		sdp_list_t *protos;
		int port;

		if (sdp_get_access_protos(rec, &protos) < 0) {
			error("Unable to get proto list from %s record",
								ext->name);
			goto failed;
		}

		port = sdp_get_proto_port(protos, L2CAP_UUID);
		if (port > 0)
			conn->psm = port;

		port = sdp_get_proto_port(protos, RFCOMM_UUID);
		if (port > 0)
			conn->chan = port;

		if (conn->psm == 0 && sdp_get_proto_desc(protos, OBEX_UUID))
			conn->psm = get_goep_l2cap_psm(rec);

		conn->features = get_supported_features(rec);
		conn->version = get_profile_version(rec);

		sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free,
									NULL);
		sdp_list_free(protos, NULL);

		if (conn->chan || conn->psm)
			break;
	}

	if (!conn->chan && !conn->psm) {
		error("Failed to find L2CAP PSM or RFCOMM channel for %s",
								ext->name);
		goto failed;
	}

	err = connect_io(conn, adapter_get_address(conn->adapter),
					device_get_address(conn->device));
	if (err < 0) {
		error("Connecting %s failed: %s", ext->name, strerror(-err));
		goto failed;
	}

	return;

failed:
	btd_service_connecting_complete(conn->service, err);
	ext->conns = g_slist_remove(ext->conns, conn);
	ext_io_destroy(conn);
}

static int resolve_service(struct ext_io *conn, const bdaddr_t *src,
							const bdaddr_t *dst)
{
	struct ext_profile *ext = conn->ext;
	uuid_t uuid;
	int err;

	bt_string2uuid(&uuid, ext->remote_uuid);
	sdp_uuid128_to_uuid(&uuid);

	err = bt_search_service(src, dst, &uuid, record_cb, conn, NULL);
	if (err == 0)
		conn->resolving = true;

	return err;
}

static int ext_connect_dev(struct btd_service *service)
{
	struct btd_device *dev = btd_service_get_device(service);
	struct btd_profile *profile = btd_service_get_profile(service);
	struct btd_adapter *adapter;
	struct ext_io *conn;
	struct ext_profile *ext;
	int err;

	ext = find_ext(profile);
	if (!ext)
		return -ENOENT;

	conn = find_connection(ext, dev);
	if (conn)
		return -EALREADY;

	adapter = device_get_adapter(dev);

	conn = g_new0(struct ext_io, 1);
	conn->ext = ext;

	if (ext->remote_psm || ext->remote_chan) {
		conn->psm = ext->remote_psm;
		conn->chan = ext->remote_chan;
		err = connect_io(conn, adapter_get_address(adapter),
						device_get_address(dev));
	} else {
		err = resolve_service(conn, adapter_get_address(adapter),
						device_get_address(dev));
	}

	if (err < 0)
		goto failed;

	conn->adapter = btd_adapter_ref(adapter);
	conn->device = btd_device_ref(dev);
	conn->service = btd_service_ref(service);

	ext->conns = g_slist_append(ext->conns, conn);

	return 0;

failed:
	g_free(conn);
	return err;
}

static int send_disconn_req(struct ext_profile *ext, struct ext_io *conn)
{
	DBusMessage *msg;
	const char *path;

	msg = dbus_message_new_method_call(ext->owner, ext->path,
						"org.bluez.Profile1",
						"RequestDisconnection");
	if (!msg) {
		error("Unable to create RequestDisconnection call for %s",
								ext->name);
		return -ENOMEM;
	}

	path = device_get_path(conn->device);
	dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &path,
							DBUS_TYPE_INVALID);

	if (!g_dbus_send_message_with_reply(btd_get_dbus_connection(),
						msg, &conn->pending, -1)) {
		error("%s: sending RequestDisconnection failed", ext->name);
		dbus_message_unref(msg);
		return -EIO;
	}

	dbus_message_unref(msg);

	dbus_pending_call_set_notify(conn->pending, disconn_reply, conn, NULL);

	return 0;
}

static int ext_disconnect_dev(struct btd_service *service)
{
	struct btd_device *dev = btd_service_get_device(service);
	struct btd_profile *profile = btd_service_get_profile(service);
	struct ext_profile *ext;
	struct ext_io *conn;
	int err;

	ext = find_ext(profile);
	if (!ext)
		return -ENOENT;

	conn = find_connection(ext, dev);
	if (!conn || !conn->connected)
		return -ENOTCONN;

	if (conn->pending)
		return -EBUSY;

	err = send_disconn_req(ext, conn);
	if (err < 0)
		return err;

	return 0;
}

static char *get_hfp_hf_record(struct ext_profile *ext, struct ext_io *l2cap,
							struct ext_io *rfcomm)
{
	return g_strdup_printf(HFP_HF_RECORD, rfcomm->chan, ext->version,
						ext->name, ext->features);
}

static char *get_hfp_ag_record(struct ext_profile *ext, struct ext_io *l2cap,
							struct ext_io *rfcomm)
{
	return g_strdup_printf(HFP_AG_RECORD, rfcomm->chan, ext->version,
						ext->name, ext->features);
}

static char *get_spp_record(struct ext_profile *ext, struct ext_io *l2cap,
							struct ext_io *rfcomm)
{
	char *svc, *rec;

	if (ext->service)
		svc = g_strdup_printf("<uuid value=\"%s\" />", ext->service);
	else
		svc = g_strdup("");

	rec = g_strdup_printf(SPP_RECORD, svc, rfcomm->chan, ext->version,
								ext->name);
	g_free(svc);
	return rec;
}

static char *get_dun_record(struct ext_profile *ext, struct ext_io *l2cap,
							struct ext_io *rfcomm)
{
	return g_strdup_printf(DUN_RECORD, rfcomm->chan, ext->version,
								ext->name);
}

static char *get_pce_record(struct ext_profile *ext, struct ext_io *l2cap,
							struct ext_io *rfcomm)
{
	return g_strdup_printf(PCE_RECORD, ext->version, ext->name);
}

static char *get_pse_record(struct ext_profile *ext, struct ext_io *l2cap,
							struct ext_io *rfcomm)
{
	return g_strdup_printf(PSE_RECORD, rfcomm->chan, ext->version,
								ext->name);
}

static char *get_mas_record(struct ext_profile *ext, struct ext_io *l2cap,
							struct ext_io *rfcomm)
{
	return g_strdup_printf(MAS_RECORD, rfcomm->chan, ext->version,
								ext->name);
}

static char *get_mns_record(struct ext_profile *ext, struct ext_io *l2cap,
							struct ext_io *rfcomm)
{
	uint16_t psm = 0;
	uint8_t chan = 0;

	if (l2cap)
		psm = l2cap->psm;
	if (rfcomm)
		chan = rfcomm->chan;

	return g_strdup_printf(MNS_RECORD, chan, ext->version, ext->name, psm);
}

static char *get_sync_record(struct ext_profile *ext, struct ext_io *l2cap,
							struct ext_io *rfcomm)
{
	return g_strdup_printf(SYNC_RECORD, rfcomm->chan, ext->version,
								ext->name);
}

static char *get_opp_record(struct ext_profile *ext, struct ext_io *l2cap,
							struct ext_io *rfcomm)
{
	uint16_t psm = 0;
	uint8_t chan = 0;

	if (l2cap)
		psm = l2cap->psm;
	if (rfcomm)
		chan = rfcomm->chan;

	return g_strdup_printf(OPP_RECORD, chan, ext->version, psm, ext->name);
}

static char *get_ftp_record(struct ext_profile *ext, struct ext_io *l2cap,
							struct ext_io *rfcomm)
{
	uint16_t psm = 0;
	uint8_t chan = 0;

	if (l2cap)
		psm = l2cap->psm;
	if (rfcomm)
		chan = rfcomm->chan;

	return g_strdup_printf(FTP_RECORD, chan, ext->version, psm, ext->name);
}

#define RFCOMM_SEQ	"<sequence>				\
				<uuid value=\"0x0003\" />	\
				<uint8 value=\"0x%02x\" />	\
			</sequence>"

#define VERSION_ATTR							\
		"<attribute id=\"0x0009\">				\
			<sequence>					\
				<sequence>				\
					<uuid value=\"%s\" />		\
					<uint16 value=\"0x%04x\" />	\
				</sequence>				\
			</sequence>					\
		</attribute>"

static char *get_generic_record(struct ext_profile *ext, struct ext_io *l2cap,
							struct ext_io *rfcomm)
{
	char uuid_str[MAX_LEN_UUID_STR], svc_str[MAX_LEN_UUID_STR], psm[30];
	char *rf_seq, *ver_attr, *rec;
	uuid_t uuid;

	bt_string2uuid(&uuid, ext->uuid);
	sdp_uuid2strn(&uuid, uuid_str, sizeof(uuid_str));

	if (ext->service) {
		bt_string2uuid(&uuid, ext->service);
		sdp_uuid2strn(&uuid, svc_str, sizeof(svc_str));
	} else {
		strncpy(svc_str, uuid_str, sizeof(svc_str));
	}

	if (l2cap)
		snprintf(psm, sizeof(psm), "<uint16 value=\"0x%04x\" />",
								l2cap->psm);
	else
		psm[0] = '\0';

	if (rfcomm)
		rf_seq = g_strdup_printf(RFCOMM_SEQ, rfcomm->chan);
	else
		rf_seq = g_strdup("");

	if (ext->version)
		ver_attr = g_strdup_printf(VERSION_ATTR, uuid_str,
								ext->version);
	else
		ver_attr = g_strdup("");

	rec = g_strdup_printf(GENERIC_RECORD, svc_str, psm, rf_seq, ver_attr,
								ext->name);

	g_free(rf_seq);
	g_free(ver_attr);

	return rec;
}

static struct default_settings {
	const char	*uuid;
	const char	*name;
	int		priority;
	const char	*remote_uuid;
	int		channel;
	int		psm;
	BtIOMode	mode;
	BtIOSecLevel	sec_level;
	bool		authorize;
	bool		auto_connect;
	char *		(*get_record)(struct ext_profile *ext,
					struct ext_io *l2cap,
					struct ext_io *rfcomm);
	uint16_t	version;
	uint16_t	features;
} defaults[] = {
	{
		.uuid		= SPP_UUID,
		.name		= "Serial Port",
		.channel	= SPP_DEFAULT_CHANNEL,
		.authorize	= true,
		.get_record	= get_spp_record,
		.version	= 0x0102,
	}, {
		.uuid		= DUN_GW_UUID,
		.name		= "Dial-Up Networking",
		.channel	= DUN_DEFAULT_CHANNEL,
		.authorize	= true,
		.get_record	= get_dun_record,
		.version	= 0x0102,
	}, {
		.uuid		= HFP_HS_UUID,
		.name		= "Hands-Free unit",
		.priority	= BTD_PROFILE_PRIORITY_HIGH,
		.remote_uuid	= HFP_AG_UUID,
		.channel	= HFP_HF_DEFAULT_CHANNEL,
		.authorize	= true,
		.auto_connect	= true,
		.get_record	= get_hfp_hf_record,
		.version	= 0x0105,
	}, {
		.uuid		= HFP_AG_UUID,
		.name		= "Hands-Free Voice gateway",
		.priority	= BTD_PROFILE_PRIORITY_HIGH,
		.remote_uuid	= HFP_HS_UUID,
		.channel	= HFP_AG_DEFAULT_CHANNEL,
		.authorize	= true,
		.auto_connect	= true,
		.get_record	= get_hfp_ag_record,
		.version	= 0x0105,
	}, {
		.uuid		= HSP_AG_UUID,
		.name		= "Headset Voice gateway",
		.priority	= BTD_PROFILE_PRIORITY_HIGH,
		.remote_uuid	= HSP_HS_UUID,
		.channel	= HSP_AG_DEFAULT_CHANNEL,
		.authorize	= true,
		.auto_connect	= true,
	}, {
		.uuid		= OBEX_OPP_UUID,
		.name		= "Object Push",
		.channel	= OPP_DEFAULT_CHANNEL,
		.psm		= BTD_PROFILE_PSM_AUTO,
		.mode		= BT_IO_MODE_ERTM,
		.sec_level	= BT_IO_SEC_LOW,
		.authorize	= false,
		.get_record	= get_opp_record,
		.version	= 0x0102,
	}, {
		.uuid		= OBEX_FTP_UUID,
		.name		= "File Transfer",
		.channel	= FTP_DEFAULT_CHANNEL,
		.psm		= BTD_PROFILE_PSM_AUTO,
		.mode		= BT_IO_MODE_ERTM,
		.authorize	= true,
		.get_record	= get_ftp_record,
		.version	= 0x0102,
	}, {
		.uuid		= OBEX_SYNC_UUID,
		.name		= "Synchronization",
		.channel	= SYNC_DEFAULT_CHANNEL,
		.authorize	= true,
		.get_record	= get_sync_record,
		.version	= 0x0100,
	}, {
		.uuid		= OBEX_PSE_UUID,
		.name		= "Phone Book Access",
		.channel	= PBAP_DEFAULT_CHANNEL,
		.authorize	= true,
		.get_record	= get_pse_record,
		.version	= 0x0101,
	}, {
		.uuid		= OBEX_PCE_UUID,
		.name		= "Phone Book Access Client",
		.remote_uuid	= OBEX_PSE_UUID,
		.authorize	= true,
		.get_record	= get_pce_record,
		.version	= 0x0101,
	}, {
		.uuid		= OBEX_MAS_UUID,
		.name		= "Message Access",
		.channel	= MAS_DEFAULT_CHANNEL,
		.authorize	= true,
		.get_record	= get_mas_record,
		.version	= 0x0100
	}, {
		.uuid		= OBEX_MNS_UUID,
		.name		= "Message Notification",
		.channel	= MNS_DEFAULT_CHANNEL,
		.psm		= BTD_PROFILE_PSM_AUTO,
		.authorize	= true,
		.get_record	= get_mns_record,
		.version	= 0x0100
	},
};

static void ext_set_defaults(struct ext_profile *ext)
{
	unsigned int i;

	ext->mode = BT_IO_MODE_BASIC;
	ext->sec_level = BT_IO_SEC_MEDIUM;
	ext->authorize = true;
	ext->enable_client = true;
	ext->enable_server = true;
	ext->remote_uuid = NULL;

	for (i = 0; i < G_N_ELEMENTS(defaults); i++) {
		struct default_settings *settings = &defaults[i];
		const char *remote_uuid;

		if (strcasecmp(ext->uuid, settings->uuid) != 0)
			continue;

		if (settings->remote_uuid)
			remote_uuid = settings->remote_uuid;
		else
			remote_uuid = ext->uuid;

		ext->remote_uuid = g_strdup(remote_uuid);

		if (settings->channel)
			ext->local_chan = settings->channel;

		if (settings->psm)
			ext->local_psm = settings->psm;

		if (settings->sec_level)
			ext->sec_level = settings->sec_level;

		if (settings->mode)
			ext->mode = settings->mode;

		ext->authorize = settings->authorize;

		if (settings->auto_connect)
			ext->p.auto_connect = true;

		if (settings->priority)
			ext->p.priority = settings->priority;

		if (settings->get_record)
			ext->get_record = settings->get_record;

		if (settings->version)
			ext->version = settings->version;

		if (settings->features)
			ext->features = settings->features;

		if (settings->name)
			ext->name = g_strdup(settings->name);
	}
}

static int parse_ext_opt(struct ext_profile *ext, const char *key,
							DBusMessageIter *value)
{
	int type = dbus_message_iter_get_arg_type(value);
	const char *str;
	uint16_t u16;
	dbus_bool_t b;

	if (strcasecmp(key, "Name") == 0) {
		if (type != DBUS_TYPE_STRING)
			return -EINVAL;
		dbus_message_iter_get_basic(value, &str);
		g_free(ext->name);
		ext->name = g_strdup(str);
	} else if (strcasecmp(key, "AutoConnect") == 0) {
		if (type != DBUS_TYPE_BOOLEAN)
			return -EINVAL;
		dbus_message_iter_get_basic(value, &b);
		ext->p.auto_connect = b;
	} else if (strcasecmp(key, "PSM") == 0) {
		if (type != DBUS_TYPE_UINT16)
			return -EINVAL;
		dbus_message_iter_get_basic(value, &u16);
		ext->local_psm = u16 ? u16 : BTD_PROFILE_PSM_AUTO;
	} else if (strcasecmp(key, "Channel") == 0) {
		if (type != DBUS_TYPE_UINT16)
			return -EINVAL;

		dbus_message_iter_get_basic(value, &u16);
		if (u16 > 31)
			return -EINVAL;
		ext->local_chan = u16 ? u16 : BTD_PROFILE_CHAN_AUTO;
	} else if (strcasecmp(key, "RequireAuthentication") == 0) {
		if (type != DBUS_TYPE_BOOLEAN)
			return -EINVAL;

		dbus_message_iter_get_basic(value, &b);
		if (b)
			ext->sec_level = BT_IO_SEC_MEDIUM;
		else
			ext->sec_level = BT_IO_SEC_LOW;
	} else if (strcasecmp(key, "RequireAuthorization") == 0) {
		if (type != DBUS_TYPE_BOOLEAN)
			return -EINVAL;
		dbus_message_iter_get_basic(value, &b);
		ext->authorize = b;
	} else if (strcasecmp(key, "Role") == 0) {
		if (type != DBUS_TYPE_STRING)
			return -EINVAL;
		dbus_message_iter_get_basic(value, &str);
		g_free(ext->role);
		ext->role = g_strdup(str);

		if (g_str_equal(ext->role, "client")) {
			ext->enable_server = false;
			ext->enable_client = true;
		} else if (g_str_equal(ext->role, "server")) {
			ext->enable_server = true;
			ext->enable_client = false;
		}
	} else if (strcasecmp(key, "ServiceRecord") == 0) {
		if (type != DBUS_TYPE_STRING)
			return -EINVAL;
		dbus_message_iter_get_basic(value, &str);
		g_free(ext->record);
		ext->record = g_strdup(str);
		ext->enable_server = true;
	} else if (strcasecmp(key, "Version") == 0) {
		uint16_t ver;

		if (type != DBUS_TYPE_UINT16)
			return -EINVAL;

		dbus_message_iter_get_basic(value, &ver);
		ext->version = ver;
	} else if (strcasecmp(key, "Features") == 0) {
		uint16_t feat;

		if (type != DBUS_TYPE_UINT16)
			return -EINVAL;

		dbus_message_iter_get_basic(value, &feat);
		ext->features = feat;
	} else if (strcasecmp(key, "Service") == 0) {
		if (type != DBUS_TYPE_STRING)
			return -EINVAL;
		dbus_message_iter_get_basic(value, &str);
		g_free(ext->service);
		ext->service = bt_name2string(str);
	}

	return 0;
}

static void set_service(struct ext_profile *ext)
{
	if (strcasecmp(ext->uuid, HSP_HS_UUID) == 0) {
		ext->service = g_strdup(ext->uuid);
	} else if (strcasecmp(ext->uuid, HSP_AG_UUID) == 0) {
		ext->service = ext->uuid;
		ext->uuid = g_strdup(HSP_HS_UUID);
	} else if (strcasecmp(ext->uuid, HFP_HS_UUID) == 0) {
		ext->service = g_strdup(ext->uuid);
	} else if (strcasecmp(ext->uuid, HFP_AG_UUID) == 0) {
		ext->service = ext->uuid;
		ext->uuid = g_strdup(HFP_HS_UUID);
	} else if (strcasecmp(ext->uuid, OBEX_SYNC_UUID) == 0 ||
			strcasecmp(ext->uuid, OBEX_OPP_UUID) == 0 ||
			strcasecmp(ext->uuid, OBEX_FTP_UUID) == 0) {
		ext->service = g_strdup(ext->uuid);
	} else if (strcasecmp(ext->uuid, OBEX_PSE_UUID) == 0 ||
			strcasecmp(ext->uuid, OBEX_PCE_UUID) ==  0) {
		ext->service = ext->uuid;
		ext->uuid = g_strdup(OBEX_PBAP_UUID);
	} else if (strcasecmp(ext->uuid, OBEX_MAS_UUID) == 0 ||
			strcasecmp(ext->uuid, OBEX_MNS_UUID) == 0) {
		ext->service = ext->uuid;
		ext->uuid = g_strdup(OBEX_MAP_UUID);
	}
}

static struct ext_profile *create_ext(const char *owner, const char *path,
					const char *uuid,
					DBusMessageIter *opts)
{
	struct btd_profile *p;
	struct ext_profile *ext;

	ext = g_new0(struct ext_profile, 1);

	ext->uuid = bt_name2string(uuid);
	if (ext->uuid == NULL) {
		g_free(ext);
		return NULL;
	}

	ext->owner = g_strdup(owner);
	ext->path = g_strdup(path);

	ext_set_defaults(ext);

	while (dbus_message_iter_get_arg_type(opts) == DBUS_TYPE_DICT_ENTRY) {
		DBusMessageIter value, entry;
		const char *key;

		dbus_message_iter_recurse(opts, &entry);
		dbus_message_iter_get_basic(&entry, &key);

		dbus_message_iter_next(&entry);
		dbus_message_iter_recurse(&entry, &value);

		if (parse_ext_opt(ext, key, &value) < 0)
			error("Invalid value for profile option %s", key);

		dbus_message_iter_next(opts);
	}

	if (!ext->service)
		set_service(ext);

	if (ext->enable_server && !(ext->record || ext->get_record))
		ext->get_record = get_generic_record;

	if (!ext->name)
		ext->name = g_strdup_printf("%s%s/%s", owner, path, uuid);

	if (!ext->remote_uuid) {
		if (ext->service)
			ext->remote_uuid = g_strdup(ext->service);
		else
			ext->remote_uuid = g_strdup(ext->uuid);
	}

	p = &ext->p;

	p->name = ext->name;
	p->local_uuid = ext->service ? ext->service : ext->uuid;
	p->remote_uuid = ext->remote_uuid;

	if (ext->enable_server || ext->record || ext->get_record) {
		p->adapter_probe = ext_adapter_probe;
		p->adapter_remove = ext_adapter_remove;
	}

	if (ext->enable_client) {
		p->device_probe = ext_device_probe;
		p->device_remove = ext_device_remove;
		p->connect = ext_connect_dev;
		p->disconnect = ext_disconnect_dev;
	}

	DBG("Created \"%s\"", ext->name);

	ext_profiles = g_slist_append(ext_profiles, ext);

	adapter_foreach(adapter_add_profile, &ext->p);

	return ext;
}

static void remove_ext(struct ext_profile *ext)
{
	adapter_foreach(adapter_remove_profile, &ext->p);

	ext_profiles = g_slist_remove(ext_profiles, ext);

	DBG("Removed \"%s\"", ext->name);

	ext_remove_records(ext, NULL);

	g_slist_free_full(ext->servers, ext_io_destroy);
	g_slist_free_full(ext->conns, ext_io_destroy);

	g_free(ext->remote_uuid);
	g_free(ext->name);
	g_free(ext->owner);
	g_free(ext->uuid);
	g_free(ext->service);
	g_free(ext->role);
	g_free(ext->path);
	g_free(ext->record);

	g_free(ext);
}

static void ext_exited(DBusConnection *conn, void *user_data)
{
	struct ext_profile *ext = user_data;

	DBG("\"%s\" exited", ext->name);

	remove_ext(ext);
}

static DBusMessage *register_profile(DBusConnection *conn,
					DBusMessage *msg, void *user_data)
{
	const char *path, *sender, *uuid;
	DBusMessageIter args, opts;
	struct ext_profile *ext;

	sender = dbus_message_get_sender(msg);

	DBG("sender %s", sender);

	dbus_message_iter_init(msg, &args);

	dbus_message_iter_get_basic(&args, &path);
	dbus_message_iter_next(&args);

	ext = find_ext_profile(sender, path);
	if (ext)
		return btd_error_already_exists(msg);

	dbus_message_iter_get_basic(&args, &uuid);
	dbus_message_iter_next(&args);

	if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY)
		return btd_error_invalid_args(msg);

	dbus_message_iter_recurse(&args, &opts);

	ext = create_ext(sender, path, uuid, &opts);
	if (!ext)
		return btd_error_invalid_args(msg);

	ext->id = g_dbus_add_disconnect_watch(conn, sender, ext_exited, ext,
									NULL);

	return dbus_message_new_method_return(msg);
}

static DBusMessage *unregister_profile(DBusConnection *conn,
					DBusMessage *msg, void *user_data)
{
	const char *path, *sender;
	struct ext_profile *ext;

	sender = dbus_message_get_sender(msg);

	DBG("sender %s", sender);

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

	ext = find_ext_profile(sender, path);
	if (!ext)
		return btd_error_does_not_exist(msg);

	g_dbus_remove_watch(conn, ext->id);
	remove_ext(ext);

	return dbus_message_new_method_return(msg);
}

static const GDBusMethodTable methods[] = {
	{ GDBUS_METHOD("RegisterProfile",
			GDBUS_ARGS({ "profile", "o"}, { "UUID", "s" },
						{ "options", "a{sv}" }),
			NULL, register_profile) },
	{ GDBUS_METHOD("UnregisterProfile", GDBUS_ARGS({ "profile", "o" }),
			NULL, unregister_profile) },
	{ }
};

static struct btd_profile_custom_property *find_custom_prop(const char *uuid,
							const char *name)
{
	GSList *l;

	for (l = custom_props; l; l = l->next) {
		struct btd_profile_custom_property *prop = l->data;

		if (strcasecmp(prop->uuid, uuid) != 0)
			continue;

		if (g_strcmp0(prop->name, name) == 0)
			return prop;
	}

	return NULL;
}

bool btd_profile_add_custom_prop(const char *uuid, const char *type,
					const char *name,
					btd_profile_prop_exists exists,
					btd_profile_prop_get get,
					void *user_data)
{
	struct btd_profile_custom_property *prop;

	prop = find_custom_prop(uuid, name);
	if (prop != NULL)
		return false;

	prop = g_new0(struct btd_profile_custom_property, 1);

	prop->uuid = g_strdup(uuid);
	prop->type = g_strdup(type);
	prop->name = g_strdup(name);
	prop->exists = exists;
	prop->get = get;
	prop->user_data = user_data;

	custom_props = g_slist_append(custom_props, prop);

	return true;
}

static void free_property(gpointer data)
{
	struct btd_profile_custom_property *p = data;

	g_free(p->uuid);
	g_free(p->type);
	g_free(p->name);

	g_free(p);
}

bool btd_profile_remove_custom_prop(const char *uuid, const char *name)
{
	struct btd_profile_custom_property *prop;

	prop = find_custom_prop(uuid, name);
	if (prop == NULL)
		return false;

	custom_props = g_slist_remove(custom_props, prop);
	free_property(prop);

	return false;
}

void btd_profile_init(void)
{
	g_dbus_register_interface(btd_get_dbus_connection(),
				"/org/bluez", "org.bluez.ProfileManager1",
				methods, NULL, NULL, NULL, NULL);
}

void btd_profile_cleanup(void)
{
	while (ext_profiles) {
		struct ext_profile *ext = ext_profiles->data;
		DBusConnection *conn = btd_get_dbus_connection();
		DBusMessage *msg;

		DBG("Releasing \"%s\"", ext->name);

		g_slist_free_full(ext->conns, ext_io_destroy);
		ext->conns = NULL;

		msg = dbus_message_new_method_call(ext->owner, ext->path,
							"org.bluez.Profile1",
							"Release");
		if (msg)
			g_dbus_send_message(conn, msg);

		g_dbus_remove_watch(conn, ext->id);
		remove_ext(ext);

	}

	g_slist_free_full(custom_props, free_property);
	custom_props = NULL;

	g_dbus_unregister_interface(btd_get_dbus_connection(),
				"/org/bluez", "org.bluez.ProfileManager1");
}
