/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2011  ST-Ericsson SA
 *
 *  Author: Szymon Janc <szymon.janc@tieto.com> for ST-Ericsson
 *
 *
 *  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 <errno.h>
#include <gdbus.h>

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

#include "plugin.h"
#include "log.h"
#include "adapter.h"
#include "device.h"
#include "manager.h"
#include "dbus-common.h"
#include "event.h"
#include "error.h"
#include "oob.h"

#define OOB_INTERFACE	"org.bluez.OutOfBand"

struct oob_request {
	struct btd_adapter *adapter;
	DBusMessage *msg;
};

static GSList *oob_requests = NULL;
static DBusConnection *connection = NULL;

static gint oob_request_cmp(gconstpointer a, gconstpointer b)
{
	const struct oob_request *data = a;
	const struct btd_adapter *adapter = b;

	return data->adapter != adapter;
}

static struct oob_request *find_oob_request(struct btd_adapter *adapter)
{
	GSList *match;

	match = g_slist_find_custom(oob_requests, adapter, oob_request_cmp);

	if (match)
		return match->data;

	return NULL;
}

static void read_local_data_complete(struct btd_adapter *adapter, uint8_t *hash,
				uint8_t *randomizer)
{
	struct DBusMessage *reply;
	struct oob_request *oob_request;

	oob_request = find_oob_request(adapter);
	if (!oob_request)
		return;

	if (hash && randomizer)
		reply = g_dbus_create_reply(oob_request->msg,
			DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &hash, 16,
			DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &randomizer, 16,
			DBUS_TYPE_INVALID);
	else
		reply = btd_error_failed(oob_request->msg,
					"Failed to read local OOB data.");

	oob_requests = g_slist_remove(oob_requests, oob_request);
	dbus_message_unref(oob_request->msg);
	g_free(oob_request);

	if (!reply) {
		error("Couldn't allocate D-Bus message");
		return;
	}

	if (!g_dbus_send_message(connection, reply))
		error("D-Bus send failed");
}

static DBusMessage *read_local_data(DBusConnection *conn, DBusMessage *msg,
								void *data)
{
	struct btd_adapter *adapter = data;
	struct oob_request *oob_request;

	if (find_oob_request(adapter))
		return btd_error_in_progress(msg);

	if (btd_adapter_read_local_oob_data(adapter))
		return btd_error_failed(msg, "Request failed.");

	oob_request = g_new(struct oob_request, 1);
	oob_request->adapter = adapter;
	oob_requests = g_slist_append(oob_requests, oob_request);
	oob_request->msg = dbus_message_ref(msg);

	return NULL;
}

static DBusMessage *add_remote_data(DBusConnection *conn, DBusMessage *msg,
								void *data)
{
	struct btd_adapter *adapter = data;
	uint8_t *hash, *randomizer;
	int32_t hlen, rlen;
	const char *addr;
	bdaddr_t bdaddr;

	if (!dbus_message_get_args(msg, NULL,
			DBUS_TYPE_STRING, &addr,
			DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &hash, &hlen,
			DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &randomizer, &rlen,
			DBUS_TYPE_INVALID))
		return btd_error_invalid_args(msg);

	if (hlen != 16 || rlen != 16 || bachk(addr))
		return btd_error_invalid_args(msg);

	str2ba(addr, &bdaddr);

	if (btd_adapter_add_remote_oob_data(adapter, &bdaddr, hash, randomizer))
		return btd_error_failed(msg, "Request failed");

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static DBusMessage *remove_remote_data(DBusConnection *conn, DBusMessage *msg,
								void *data)
{
	struct btd_adapter *adapter = data;
	const char *addr;
	bdaddr_t bdaddr;

	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &addr,
			DBUS_TYPE_INVALID))
		return btd_error_invalid_args(msg);

	if (bachk(addr))
		return btd_error_invalid_args(msg);

	str2ba(addr, &bdaddr);

	if (btd_adapter_remove_remote_oob_data(adapter, &bdaddr))
		return btd_error_failed(msg, "Request failed");

	return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}

static GDBusMethodTable oob_methods[] = {
	{"AddRemoteData",	"sayay",	"",	add_remote_data},
	{"RemoveRemoteData",	"s",		"",	remove_remote_data},
	{"ReadLocalData",	"",		"ayay",	read_local_data,
						G_DBUS_METHOD_FLAG_ASYNC},
	{}
};

static int oob_probe(struct btd_adapter *adapter)
{
	const char *path = adapter_get_path(adapter);

	if (!g_dbus_register_interface(connection, path, OOB_INTERFACE,
				oob_methods, NULL, NULL, adapter, NULL)) {
			error("OOB interface init failed on path %s", path);
			return -EIO;
		}

	return 0;
}

static void oob_remove(struct btd_adapter *adapter)
{
	read_local_data_complete(adapter, NULL, NULL);

	g_dbus_unregister_interface(connection, adapter_get_path(adapter),
							OOB_INTERFACE);
}

static struct btd_adapter_driver oob_driver = {
	.name	= "oob",
	.probe	= oob_probe,
	.remove	= oob_remove,
};

static int dbusoob_init(void)
{
	DBG("Setup dbusoob plugin");

	connection = get_dbus_connection();

	oob_register_cb(read_local_data_complete);

	return btd_register_adapter_driver(&oob_driver);
}

static void dbusoob_exit(void)
{
	DBG("Cleanup dbusoob plugin");

	manager_foreach_adapter((adapter_cb) oob_remove, NULL);

	btd_unregister_adapter_driver(&oob_driver);
}

BLUETOOTH_PLUGIN_DEFINE(dbusoob, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
						dbusoob_init, dbusoob_exit)