/*
 *
 *  neard - Near Field Communication manager
 *
 *  Copyright (C) 2011  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 version 2 as
 *  published by the Free Software Foundation.
 *
 *  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 <stdlib.h>
#include <string.h>
#include <errno.h>
#include <gdbus.h>

#include "near.h"

#define BLUEZ_SERVICE			"org.bluez"
#define MANAGER_INTF			BLUEZ_SERVICE ".Manager"
#define ADAPTER_INTF			BLUEZ_SERVICE ".Adapter"
#define OOB_INTF			BLUEZ_SERVICE ".OutOfBand"
#define DEFAULT_ADAPTER			"DefaultAdapter"
#define ADAPTER_REMOVED			"AdapterRemoved"
#define DEFAULT_ADAPTER_CHANGED		"DefaultAdapterChanged"
#define ADAPTER_PROPERTY_CHANGED	"PropertyChanged"
#define MANAGER_PATH			"/"
#define OOB_AGENT			"/org/neard/agent/neard_oob"

#define BT_NOINPUTOUTPUT		"NoInputNoOutput"
#define BT_DISPLAY_YESNO		"DisplayYesNo"

#define DBUS_MANAGER_INTF		"org.freedesktop.DBus.ObjectManager"
#define AGENT_REGISTER_TIMEOUT	2

/* BT EIR list */
#define EIR_UUID128_ALL		0x07 /* 128-bit UUID, all listed */
#define EIR_NAME_SHORT		0x08 /* shortened local name */
#define EIR_NAME_COMPLETE	0x09 /* complete local name */

/* Specific OOB EIRs */
#define EIR_CLASS_OF_DEVICE	0x0D  /* class of device */
#define EIR_SP_HASH		0x0E  /* simple pairing hash C */
#define EIR_SP_RANDOMIZER	0x0F  /* simple pairing randomizer R */
/* Optional EIRs */
#define EIR_DEVICE_ID		0x10  /* device ID */
#define EIR_SECURITY_MGR_FLAGS	0x11  /* security manager flags */

#define EIR_SIZE_LEN		1
#define EIR_HEADER_LEN		(EIR_SIZE_LEN + 1)
#define BT_ADDRESS_SIZE		6
#define COD_SIZE		3
#define OOB_SP_SIZE		16
#define EIR_SIZE_MAX		255

struct near_oob_data {
	char *def_adapter;

	char *bd_addr;		/* oob mandatory */

	/* optional */
	char *bt_name;			/* short or long name */
	uint8_t bt_name_len;
	int class_of_device;		/* Class of device */
	bool powered;
	bool pairable;
	bool discoverable;
	uint8_t *uuids;
	int uuids_len;

	uint8_t *spair_hash;			/* OOB hash Key */
	uint8_t *spair_randomizer;		/* OOB randomizer key */
	uint8_t authentication[OOB_SP_SIZE];	/* On BT 2.0 */
	uint8_t security_manager_oob_flags;	/* see BT Core 4.0 */
};

static DBusConnection *bt_conn;
static struct near_oob_data bt_def_oob_data;

static guint watch;
static guint removed_watch;
static guint adapter_watch;
static guint adapter_props_watch;

static guint register_bluez_timer;

static void __bt_eir_free(struct near_oob_data *oob)
{
	DBG("");

	if (oob->def_adapter) {
		g_free(oob->def_adapter);
		oob->def_adapter = NULL;
	}

	if (oob->bd_addr) {
		g_free(oob->bd_addr);
		oob->bd_addr = NULL;
	}

	if (oob->bt_name) {
		g_free(oob->bt_name);
		oob->bt_name = NULL;
	}

	if (oob->spair_hash) {
		g_free(oob->spair_hash);
		oob->spair_hash = NULL;
	}

	if (oob->spair_randomizer) {
		g_free(oob->spair_randomizer);
		oob->spair_randomizer = NULL;
	}
}

static void bt_eir_free(struct near_oob_data *oob)
{
	__bt_eir_free(oob);

	g_free(oob);
}

/* D-Bus helper functions */
static int bt_generic_call(DBusConnection *conn,
		struct near_oob_data *oob,		/* user data */
		const char *dest,			/* method call */
		const char *path,
		const char *interface,
		const char *method,
		DBusPendingCallNotifyFunction bt_cb,	/* callback */
		int type, ...)				/* params */
{
	DBusMessage *msg;
	DBusPendingCall *pending;
	va_list args;
	int err;

	DBG("%s", method);

	msg = dbus_message_new_method_call(dest, path, interface, method);

	if (!msg) {
		near_error("Unable to allocate new D-Bus %s message", method);
		return -ENOMEM;
	}

	va_start(args, type);

	if (!dbus_message_append_args_valist(msg, type, args)) {
		va_end(args);
		err = -EIO;
		goto error_done;
	}
	va_end(args);

	if (!dbus_connection_send_with_reply(conn, msg, &pending, -1)) {
		near_error("Sending %s failed", method);
		err = -EIO;
		goto error_done;
	}

	if (!pending) {
		near_error("D-Bus connection not available");
		err = -EIO;
		goto error_done;
	}

	/* Prepare for notification */
	dbus_pending_call_set_notify(pending, bt_cb, oob, NULL);
	err = 0 ;

error_done:
	dbus_message_unref(msg);
	return err;
}

static void bt_create_paired_device_cb(DBusPendingCall *pending,
					void *user_data)
{
	DBusMessage *reply;
	DBusError   error;
	struct near_oob_data *oob = user_data;

	DBG("");

	reply = dbus_pending_call_steal_reply(pending);
	if (!reply)
		return;

	dbus_error_init(&error);

	if (dbus_set_error_from_message(&error, reply)) {
		near_error("%s", error.message);
		dbus_error_free(&error);
		goto cb_done;
	}

	DBG("Successful pairing");

cb_done:
	/* task completed - clean memory*/
	bt_eir_free(oob);

	dbus_message_unref(reply);
	dbus_pending_call_unref(pending);
}

static int bt_create_paired_device(DBusConnection *conn,
						struct near_oob_data *oob,
						const char *capabilities)
{
	const char *agent_path = OOB_AGENT;

	return bt_generic_call(bt_conn, oob, BLUEZ_SERVICE,
			oob->def_adapter, ADAPTER_INTF, "CreatePairedDevice",
			bt_create_paired_device_cb,
			/* params */
			DBUS_TYPE_STRING, &oob->bd_addr,
			DBUS_TYPE_OBJECT_PATH, &agent_path,
			DBUS_TYPE_STRING, &capabilities,
			DBUS_TYPE_INVALID);
}

static void bt_oob_add_remote_data_cb(DBusPendingCall *pending, void *user_data)
{
	DBusMessage *reply;
	DBusError   error;
	struct near_oob_data *oob = user_data;

	DBG("");

	reply = dbus_pending_call_steal_reply(pending);
	if (!reply)
		return;

	dbus_error_init(&error);

	if (dbus_set_error_from_message(&error, reply))
		goto cb_fail;

	near_info("OOB data added");

	dbus_message_unref(reply);
	dbus_pending_call_unref(pending);

	/* Jump to the next: Pairing !!!*/
	DBG("Try to pair devices...");
	bt_create_paired_device(bt_conn, oob, BT_DISPLAY_YESNO);
	return;

cb_fail:
	near_error("%s", error.message);
	dbus_error_free(&error);

	bt_eir_free(oob);

	dbus_message_unref(reply);
	dbus_pending_call_unref(pending);
}

static int bt_oob_add_remote_data(DBusConnection *conn,
						struct near_oob_data *oob)
{
	int16_t hash_len = 16;
	int16_t rdm_len = 16;

	return bt_generic_call(bt_conn, oob, BLUEZ_SERVICE,
			oob->def_adapter, OOB_INTF, "AddRemoteData",
			bt_oob_add_remote_data_cb,
			/* params */
			DBUS_TYPE_STRING, &oob->bd_addr,
			DBUS_TYPE_ARRAY,
				DBUS_TYPE_BYTE, &oob->spair_hash, hash_len,
			DBUS_TYPE_ARRAY,
				DBUS_TYPE_BYTE, &oob->spair_randomizer, rdm_len,
			DBUS_TYPE_INVALID);
}

/* Pairing: JustWorks or OOB  */
static int bt_do_pairing(struct near_oob_data *oob)
{
	int err = 0;

	DBG("%s", oob->bd_addr);

	/* Is this a *real* oob pairing or a "JustWork" */
	if ((oob->spair_hash) && (oob->spair_randomizer))
		err = bt_oob_add_remote_data(bt_conn, oob);
	else
		err = bt_create_paired_device(bt_conn, oob,
				BT_NOINPUTOUTPUT);

	if (err < 0)
		near_error("Pairing failed. Err[%d]", err);

	return err;
}

/*
 */
static int extract_properties(DBusMessage *reply, struct near_oob_data *oob)
{
	char *data = NULL;
	int idata;
	int i, j;

	DBusMessageIter array, dict;

	if (!dbus_message_iter_init(reply, &array))
		return -1;

	if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_ARRAY)
		return -1;

	dbus_message_iter_recurse(&array, &dict);

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

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

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

		if (g_str_equal(key, "Address")) {
			dbus_message_iter_get_basic(&value, &data);

			/* Now, fill the local struct */
			oob->bd_addr = g_try_malloc0(BT_ADDRESS_SIZE);
			if (!oob->bd_addr)
				return -ENOMEM;

			/* Address is like: "ff:ee:dd:cc:bb:aa" */
			for (i = 5, j = 0 ; i >= 0; i--, j += 3)
				oob->bd_addr[i] = strtol(data + j, NULL, 16);
			DBG("local address: %s", data);

		} else if (g_str_equal(key, "Name")) {
			dbus_message_iter_get_basic(&value, &data);
			oob->bt_name = g_strdup(data);
			if (oob->bt_name) {
				oob->bt_name_len = strlen(oob->bt_name);
				DBG("local name: %s", oob->bt_name);
			}

		} else if (g_str_equal(key, "Class")) {
			dbus_message_iter_get_basic(&value, &idata);
			oob->class_of_device = idata;

		} else if (g_str_equal(key, "Powered")) {
			dbus_message_iter_get_basic(&value, &idata);
			oob->powered = idata;

		} else if (g_str_equal(key, "Discoverable")) {
			dbus_message_iter_get_basic(&value, &idata);
			oob->discoverable = idata;

		} else if (g_str_equal(key, "Pairable")) {
			dbus_message_iter_get_basic(&value, &idata);
			oob->pairable = idata;

		} else if (g_str_equal(key, "UUIDs")) {
			oob->uuids_len = sizeof(value);
			oob->uuids = g_try_malloc0(oob->uuids_len);
			if (!oob->uuids)
				return -ENOMEM;
			memcpy(oob->uuids, &value, oob->uuids_len);
		}

		dbus_message_iter_next(&dict);
	}

	return 0;
}

static int bt_parse_properties(DBusMessage *reply, void *user_data)
{
	struct near_oob_data *bt_props = user_data;

	DBG("");

	/* Free datas */
	g_free(bt_props->bd_addr);
	g_free(bt_props->bt_name);

	/* Grab properties from dbus */
	if (extract_properties(reply, bt_props) < 0)
		goto fail;

	return 0;

fail:
	g_free(bt_props->bd_addr);
	bt_props->bd_addr = NULL;

	g_free(bt_props->bt_name);
	bt_props->bt_name = NULL;

	return -ENOMEM;
}

static gboolean bt_adapter_property_changed(DBusConnection *conn,
							DBusMessage *message,
							void *user_data)
{
	DBusMessageIter iter;
	DBusMessageIter var;
	const char *property;

	if (!dbus_message_iter_init(message, &iter))
		return TRUE;

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

	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
		return TRUE;

	dbus_message_iter_recurse(&iter, &var);

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

		if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_STRING)
			return TRUE;

		dbus_message_iter_get_basic(&var, &name);

		g_free(bt_def_oob_data.bt_name);
		bt_def_oob_data.bt_name = g_strdup(name);

		if (bt_def_oob_data.bt_name)
			bt_def_oob_data.bt_name_len = strlen(name);
		else
			bt_def_oob_data.bt_name_len = 0;

		DBG("%s: %s", property, name);
	} else if (g_str_equal(property, "Class")) {
		int class;

		if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_UINT32)
			return TRUE;

		dbus_message_iter_get_basic(&var, &class);
		bt_def_oob_data.class_of_device = class;

		DBG("%s: %x", property, (unsigned int)bt_def_oob_data.class_of_device);
	} else if (g_str_equal(property, "Powered")) {
		dbus_bool_t powered;

		if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_BOOLEAN)
			return TRUE;

		dbus_message_iter_get_basic(&var, &powered);
		bt_def_oob_data.powered = powered;

		DBG("%s: %d", property, bt_def_oob_data.powered);
	}

	return TRUE;
}

/* Get default local adapter properties */
static void bt_get_properties_cb(DBusPendingCall *pending, void *user_data)
{
	struct near_oob_data *bt_props = user_data;
	DBusMessage *reply;
	DBusError   error;
	int err;

	DBG("");

	reply = dbus_pending_call_steal_reply(pending);
	if (!reply)
		return;

	dbus_error_init(&error);

	if (dbus_set_error_from_message(&error, reply))
		goto cb_fail;

	err = bt_parse_properties(reply, bt_props);
	if (err < 0)
		near_error("Problem parsing local properties %d", err);
	else
		DBG("Get Properties complete: %s", bt_props->def_adapter);

	adapter_props_watch = g_dbus_add_signal_watch(bt_conn, NULL, NULL,
						ADAPTER_INTF,
						ADAPTER_PROPERTY_CHANGED,
						bt_adapter_property_changed,
						NULL, NULL);

	/* clean */
	dbus_message_unref(reply);
	dbus_pending_call_unref(pending);
	return;

cb_fail:
	near_error("%s", error.message);
	dbus_error_free(&error);

	dbus_message_unref(reply);
	dbus_pending_call_unref(pending);
}

static void bt_get_default_adapter_cb(DBusPendingCall *pending, void *user_data)
{
	struct near_oob_data *bt_props = user_data;
	DBusMessage *reply;
	DBusError   error;
	gchar *path;

	DBG("");

	reply = dbus_pending_call_steal_reply(pending);
	if (!reply)
		return;

	dbus_error_init(&error);

	if (dbus_set_error_from_message(&error, reply))
		goto cb_fail;

	if (!dbus_message_get_args(reply, NULL, DBUS_TYPE_OBJECT_PATH,
					&path, DBUS_TYPE_INVALID))
		goto cb_fail;

	/* Save the default adapter */
	bt_props->def_adapter = g_strdup(path);
	DBG("Using default adapter %s", bt_props->def_adapter);

	/* clean */
	dbus_message_unref(reply);
	dbus_pending_call_unref(pending);

	/* Jump on getAdapterProperties */
	bt_generic_call(bt_conn, bt_props,
			BLUEZ_SERVICE,
			bt_props->def_adapter,
			ADAPTER_INTF, "GetProperties",
			bt_get_properties_cb,
			DBUS_TYPE_INVALID);
	return;

cb_fail:
	near_error("Could not get Bluetooth default adapter %s", error.message);
	dbus_error_free(&error);

	dbus_message_unref(reply);
	dbus_pending_call_unref(pending);
}

static int bt_refresh_adapter_props(DBusConnection *conn, void *user_data)
{
	DBG("%p %p", conn, user_data);

	return bt_generic_call(conn, user_data,
			BLUEZ_SERVICE,
			MANAGER_PATH, MANAGER_INTF,
			DEFAULT_ADAPTER,
			bt_get_default_adapter_cb,
			DBUS_TYPE_INVALID);
}

/* Parse and fill the bluetooth oob information block */
static void bt_parse_eir(uint8_t *eir_data, uint16_t eir_data_len,
				struct near_oob_data *oob, uint16_t *props)
{
	char *tmp;
	uint16_t len = 0;

	DBG("total len: %u", eir_data_len);

	while (len < eir_data_len - 1) {
		uint8_t eir_len = eir_data[0];	/* EIR field length */
		uint8_t eir_code;		/* EIR field type*/
		uint8_t data_len;		/* EIR data length */
		uint8_t *data;

		/* check for early termination */
		if (eir_len == 0)
			break;

		len += eir_len + 1;

		/* Do not continue EIR Data parsing if got incorrect length */
		if (len > eir_data_len)
			break;

		data_len = eir_len - 1;

		eir_code = eir_data[1]; /* EIR code */
		data = &eir_data[2];

		DBG("type 0x%.2X data_len %u", eir_code, data_len);

		switch (eir_code) {
		case EIR_NAME_SHORT:
		case EIR_NAME_COMPLETE:
			oob->bt_name = g_try_malloc0(data_len + 1); /* eos */
			if (oob->bt_name) {
				oob->bt_name_len = data_len;
				memcpy(oob->bt_name, data, oob->bt_name_len);
				oob->bt_name[data_len] = 0;	/* end str*/
			}
			break;

		case EIR_CLASS_OF_DEVICE:
			tmp = g_strdup_printf("%02X%02X%02X",
					*data, *(data + 1), *(data + 2));
			if (tmp) {
				oob->class_of_device = strtol(tmp, NULL, 16);
				*props |= OOB_PROPS_COD;
			}
			g_free(tmp);
			break;

		case EIR_SP_HASH:
			oob->spair_hash = g_try_malloc0(OOB_SP_SIZE);
			if (oob->spair_hash) {
				memcpy(oob->spair_hash, data, OOB_SP_SIZE);
				*props |= OOB_PROPS_SP_HASH;
			}
			break;

		case EIR_SP_RANDOMIZER:
			oob->spair_randomizer = g_try_malloc0(OOB_SP_SIZE);
			if (oob->spair_randomizer) {
				memcpy(oob->spair_randomizer,
						data, OOB_SP_SIZE);
				*props |= OOB_PROPS_SP_RANDOM;
			}
			break;

		case EIR_SECURITY_MGR_FLAGS:
			oob->security_manager_oob_flags = *data;
			break;

		case EIR_UUID128_ALL:
			/* TODO: Process uuids128
			 * */
			break;

		default:	/* ignore and skip */
			near_error("Unknown EIR x%02x (len: %d)", eir_code,
								eir_len);
			break;
		}
		/* Next eir */
		eir_data += eir_len + 1;
	}
}

/*
 * Because of some "old" implementation, "version" will help
 * to determine the record data structure.
 * Some specifications are proprietary (eg. "short mode")
 * and are not fully documented.
 * mime_properties is a bitmask and should reflect the fields found in
 * the incoming oob.
 */
int __near_bluetooth_parse_oob_record(struct carrier_data *data,
						uint16_t *mime_properties,
						bool pair)
{
	struct near_oob_data *oob;
	uint16_t bt_oob_data_size;
	uint8_t *ptr = data->data;
	uint8_t	marker;
	char *tmp;

	DBG("");

	oob = g_try_malloc0(sizeof(struct near_oob_data));

	if (data->type == BT_MIME_V2_1) {
		/*
		 * Total OOB data size (including size bytes)
		 * Some implementations (e.g. Android 4.1) stores
		 * the data_size in big endian but NDEF forum spec (BT Secure
		 * Simple Pairing) requires a little endian. At the same time,
		 * the NDEF forum NDEF spec define a payload length as single
		 * byte (and the payload size IS the oob data size).
		 */
		bt_oob_data_size = near_get_le16(ptr);
		if (bt_oob_data_size > 0xFF)	/* Big Endian */
			bt_oob_data_size = GUINT16_FROM_BE(bt_oob_data_size);

		bt_oob_data_size -= 2 ; /* remove oob datas size len */

		/* First item: BD_ADDR (mandatory) */
		ptr = &data->data[2];
		oob->bd_addr = g_strdup_printf("%02X:%02X:%02X:%02X:%02X:%02X",
				ptr[5],	ptr[4], ptr[3], ptr[2], ptr[1], ptr[0]);

		/* Skip to the next element (optional) */
		ptr += BT_ADDRESS_SIZE;
		bt_oob_data_size -= BT_ADDRESS_SIZE ;

		if (bt_oob_data_size)
			bt_parse_eir(ptr, bt_oob_data_size, oob,
							mime_properties);
	} else if (data->type == BT_MIME_V2_0) {
		marker = *ptr++;	/* could be '$' */

		oob->bd_addr = g_strdup_printf(
				"%02X:%02X:%02X:%02X:%02X:%02X",
				ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5]);
		ptr = ptr + BT_ADDRESS_SIZE;

		/* Class of device */
		tmp = g_strdup_printf("%02X%02X%02X",
				*ptr, *(ptr + 1), *(ptr + 2));
		if (tmp)
			oob->class_of_device = strtol(tmp, NULL, 16);
		g_free(tmp);

		ptr = ptr + 3;

		/* "Short mode" seems to use a 4 bytes code
		 * instead of 16 bytes...
		 */
		if (marker == '$') {   /* Short NFC */
			memcpy(oob->authentication, ptr, 4);
			ptr = ptr + 4;
		} else {
			memcpy(oob->authentication, ptr, 16);
			ptr = ptr + 16;
		}

		/* get the device name */
		oob->bt_name_len = *ptr++;
		oob->bt_name = g_try_malloc0(oob->bt_name_len+1);
		if (oob->bt_name) {
			memcpy(oob->bt_name, ptr, oob->bt_name_len);
			oob->bt_name[oob->bt_name_len+1] = 0;
		}
		ptr = ptr + oob->bt_name_len;
	} else {
		bt_eir_free(oob);
		return -EINVAL;
	}

	if (!pair) {
		bt_eir_free(oob);
		return 0;
	}

	/* check and get the default adapter */
	oob->def_adapter = g_strdup(bt_def_oob_data.def_adapter);
	if (!oob->def_adapter) {
		near_error("bt_get_default_adapter failed");
		bt_eir_free(oob);
		return -EIO;
	}

	return  bt_do_pairing(oob);
}

int __near_bluetooth_pair(void *data)
{
	struct near_oob_data *oob = data;

	/* check and get the default adapter */
	oob->def_adapter = g_strdup(bt_def_oob_data.def_adapter);
	if (!oob->bt_name) {
		near_error("bt_get_default_adapter failed: %d", -EIO);
		bt_eir_free(oob);
		return -EIO;
	}

	return bt_do_pairing(oob);
}

/* This function is synchronous as oob datas change on each session */
static int bt_sync_oob_readlocaldata(DBusConnection *conn, char *adapter_path,
							char *spair_hash,
							char *spair_randomizer)
{
	DBusMessage *message, *reply;
	DBusError error;
	int hash_len, rndm_len;

	message = dbus_message_new_method_call(BLUEZ_SERVICE, adapter_path,
			OOB_INTF, "ReadLocalData");
	if (!message)
		return 0;

	dbus_error_init(&error);

	reply = dbus_connection_send_with_reply_and_block(conn,
			message, -1, &error);

	dbus_message_unref(message);

	if (!reply) {
		if (dbus_error_is_set(&error)) {
			near_error("%s", error.message);
			dbus_error_free(&error);
		} else {
			near_error("Failed to set property");
		}
		return 0;
	}

	if (!dbus_message_get_args(reply, NULL, DBUS_TYPE_ARRAY,
					DBUS_TYPE_BYTE, spair_hash,
					&hash_len, DBUS_TYPE_ARRAY,
					DBUS_TYPE_BYTE, spair_randomizer,
					&rndm_len, DBUS_TYPE_INVALID))
		goto done;

	if ((hash_len != OOB_SP_SIZE) || (rndm_len != OOB_SP_SIZE)) {
		DBG("no OOB data found !");
		goto done;
	}

	dbus_message_unref(reply);
	DBG("OOB data found");
	return hash_len;

done:
	dbus_message_unref(reply);
	return 0;
}

/*
 * External API to get bt properties
 * Prepare a "real" oob datas block
 * mime_props is a bitmask we use to add or not specific fields in the
 * oob frame (e.g.: OOB keys)
 * */
struct carrier_data *__near_bluetooth_local_get_properties(uint16_t mime_props)
{
	struct carrier_data *data = NULL;
	uint8_t offset;

	char hash[OOB_SP_SIZE];
	char random[OOB_SP_SIZE];

	/* Check adapter datas */
	if (!bt_def_oob_data.def_adapter) {
		near_error("No bt adapter info");
		goto fail;
	}

	data = g_try_malloc0(sizeof(*data));
	if (!data)
		goto fail;

	data->size = sizeof(uint16_t)	/* stored oob size */
			+ BT_ADDRESS_SIZE;	/* device address */

	offset = sizeof(uint16_t); /* Skip size...will be filled later */

	/* Now prepare data frame */
	memcpy(data->data + offset, bt_def_oob_data.bd_addr, BT_ADDRESS_SIZE);
	offset += BT_ADDRESS_SIZE;

	/* CoD */
	data->size += COD_SIZE +  EIR_HEADER_LEN;

	data->data[offset++] = COD_SIZE + EIR_SIZE_LEN;
	data->data[offset++] = EIR_CLASS_OF_DEVICE;

	memcpy(data->data + offset,
			(uint8_t *)&bt_def_oob_data.class_of_device, COD_SIZE);
	offset += COD_SIZE;

	/*
	 * The following data are generated dynamically so we have to read the
	 * local oob data. Only add OOB pairing keys if needed.
	 */
	if ((mime_props & OOB_PROPS_SP) != 0 &&
			bt_sync_oob_readlocaldata(bt_conn,
					bt_def_oob_data.def_adapter,
					hash, random) == OOB_SP_SIZE) {
		data->size += 2 * (OOB_SP_SIZE + EIR_HEADER_LEN);

		/* OOB datas */
		data->data[offset++] = OOB_SP_SIZE + EIR_SIZE_LEN;
		data->data[offset++] = EIR_SP_HASH;
		memcpy(data->data + offset, hash, OOB_SP_SIZE);
		offset += OOB_SP_SIZE;

		data->data[offset++] = OOB_SP_SIZE + EIR_SIZE_LEN;
		data->data[offset++] = EIR_SP_RANDOMIZER;
		memcpy(data->data + offset, random, OOB_SP_SIZE);
		offset += OOB_SP_SIZE;
	}

	/* bt name */
	if (bt_def_oob_data.bt_name) {
		int name_len;

		data->size += EIR_HEADER_LEN;

		if (data->size + bt_def_oob_data.bt_name_len
				> EIR_SIZE_MAX) {
			name_len = EIR_SIZE_MAX - data->size;
			data->data[offset++] = name_len + EIR_SIZE_LEN;
			/* EIR data type */
			data->data[offset++] = EIR_NAME_COMPLETE;
		} else {
			name_len = bt_def_oob_data.bt_name_len;
			data->data[offset++] = name_len + EIR_SIZE_LEN;
			/* EIR data type */
			data->data[offset++] = EIR_NAME_SHORT;
		}

		data->size += name_len;
		memcpy(data->data + offset, bt_def_oob_data.bt_name, name_len);
		offset += name_len;
	}

	data->data[0] = data->size ;

	if (bt_def_oob_data.powered)
		data->state = CPS_ACTIVE;
	else
		data->state = CPS_INACTIVE;

	return data;

fail:
	g_free(data);
	return NULL;
}

/* BT adapter removed handler */
static gboolean bt_adapter_removed(DBusConnection *conn, DBusMessage *message,
							void *user_data)
{
	DBusMessageIter iter;
	struct near_oob_data *bt_props = user_data;
	const char *adapter_path;

	DBG("");

	if (!bt_props->def_adapter)
		return TRUE;

	g_dbus_remove_watch(bt_conn, adapter_props_watch);
	adapter_props_watch = 0;

	if (!dbus_message_iter_init(message, &iter))
		return TRUE;

	dbus_message_iter_get_basic(&iter, &adapter_path);

	if (g_strcmp0(adapter_path, bt_props->def_adapter) == 0) {
		near_info("Remove the default adapter [%s]", adapter_path);

		__bt_eir_free(bt_props);
		bt_props->def_adapter = NULL;
	}

	return TRUE;
}

/* BT default adapter changed handler */
static gboolean bt_default_adapter_changed(DBusConnection *conn,
					DBusMessage *message,
					void *user_data)
{
	struct near_oob_data *bt_props = user_data;
	DBusMessageIter iter;
	const char *adapter_path;

	DBG("");

	if (!dbus_message_iter_init(message, &iter))
		return TRUE;

	g_dbus_remove_watch(bt_conn, adapter_props_watch);
	adapter_props_watch = 0;

	dbus_message_iter_get_basic(&iter, &adapter_path);
	DBG("New default adapter [%s]", adapter_path);

	/* Disable the old one */
	__bt_eir_free(bt_props);
	bt_props->def_adapter = NULL;

	/* Refresh */
	bt_refresh_adapter_props(conn, user_data);

	return TRUE;
}

static void bt_dbus_disconnect_cb(DBusConnection *conn, void *user_data)
{
	near_error("D-Bus disconnect (BT)");
	bt_conn = NULL;
}

static gboolean register_bluez(gpointer user_data)
{
	DBG("");

	register_bluez_timer = 0;

	removed_watch = g_dbus_add_signal_watch(bt_conn, NULL, NULL,
						MANAGER_INTF,
						ADAPTER_REMOVED,
						bt_adapter_removed,
						&bt_def_oob_data, NULL);


	adapter_watch = g_dbus_add_signal_watch(bt_conn, NULL, NULL,
						MANAGER_INTF,
						DEFAULT_ADAPTER_CHANGED,
						bt_default_adapter_changed,
						&bt_def_oob_data, NULL);

	if (removed_watch == 0 || adapter_watch == 0) {
		near_error("BlueZ event handlers failed to register.");
		g_dbus_remove_watch(bt_conn, removed_watch);
		g_dbus_remove_watch(bt_conn, adapter_watch);

		return FALSE;
	}

	if (bt_refresh_adapter_props(bt_conn, user_data) < 0)
		near_error("Failed to get BT adapter properties");

	return FALSE;
}

static void bt_connect(DBusConnection *conn, void *data)
{
	DBG("connection %p with %p", conn, data);

	if (__near_agent_handover_registered(HO_AGENT_BT)) {
		DBG("Agent already registered");
		return;
	}

	/*
	 * BlueZ 5 will register itself as HandoverAgent, give it some time
	 * to do it before going legacy way.
	 */
	register_bluez_timer = g_timeout_add_seconds(AGENT_REGISTER_TIMEOUT,
							register_bluez, data);
}

static void bt_disconnect(DBusConnection *conn, void *user_data)
{
	DBG("%p", conn);

	/* If timer is running no BlueZ watchers were registered yet */
	if (register_bluez_timer > 0) {
		g_source_remove(register_bluez_timer);
		register_bluez_timer = 0;
		return;
	}

	__bt_eir_free(user_data);

	g_dbus_remove_watch(bt_conn, removed_watch);
	removed_watch = 0;

	g_dbus_remove_watch(bt_conn, adapter_watch);
	adapter_watch = 0;

	g_dbus_remove_watch(bt_conn, adapter_props_watch);
	adapter_props_watch = 0;
}

static int bt_prepare_handlers(DBusConnection *conn)
{
	if (__near_agent_handover_registered(HO_AGENT_BT))
		return 0;

	watch = g_dbus_add_service_watch(bt_conn, BLUEZ_SERVICE,
						bt_connect,
						bt_disconnect,
						&bt_def_oob_data, NULL);
	if (watch == 0) {
		near_error("BlueZ service watch handler failed to register.");
		g_dbus_remove_watch(bt_conn, watch);
		return -EIO;
	}

	return 0;
}

void __near_bluetooth_legacy_start(void)
{
	DBG("");

	bt_prepare_handlers(bt_conn);
}

void __near_bluetooth_legacy_stop(void)
{
	DBG("");

	g_dbus_remove_watch(bt_conn, watch);
	watch = 0;

	bt_disconnect(bt_conn, &bt_def_oob_data);
}

/* Bluetooth exiting function */
void __near_bluetooth_cleanup(void)
{
	DBG("");

	if (!bt_conn)
		return;

	__near_bluetooth_legacy_stop();

	dbus_connection_unref(bt_conn);
}

/*
 * Bluetooth initialization function.
 *	Allocate bt local settings storage
 *	and setup event handlers
 */
int __near_bluetooth_init(void)
{
	DBusError err;

	DBG("");

	dbus_error_init(&err);

	/* save the dbus connection */
	bt_conn = near_dbus_get_connection();
	if (!bt_conn) {
		if (dbus_error_is_set(&err)) {
			near_error("%s", err.message);
			dbus_error_free(&err);
		} else
			near_error("Can't register with system bus\n");
		return -EIO;
	}

	/* dbus disconnect callback */
	g_dbus_set_disconnect_function(bt_conn, bt_dbus_disconnect_cb,
						NULL, NULL);

	/* Set bluez event handlers */
	return bt_prepare_handlers(bt_conn);
}
