// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2006-2010  Nokia Corporation
 *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
 *
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/socket.h>
#include <sys/ioctl.h>

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

#include "lib/bluetooth.h"
#include "lib/sdp.h"

#include "gdbus/gdbus.h"

#include "log.h"
#include "error.h"
#include "btd.h"
#include "dbus-common.h"
#include "adapter.h"
#include "device.h"
#include "agent.h"
#include "shared/queue.h"

#define REQUEST_TIMEOUT (60 * 1000)		/* 60 seconds */
#define AGENT_INTERFACE "org.bluez.Agent1"

static GHashTable *agent_list;
struct queue *default_agents = NULL;

typedef enum {
	AGENT_REQUEST_PASSKEY,
	AGENT_REQUEST_CONFIRMATION,
	AGENT_REQUEST_AUTHORIZATION,
	AGENT_REQUEST_PINCODE,
	AGENT_REQUEST_AUTHORIZE_SERVICE,
	AGENT_REQUEST_DISPLAY_PINCODE,
} agent_request_type_t;

struct agent {
	int ref;
	char *owner;
	char *path;
	uint8_t capability;
	struct agent_request *request;
	guint watch;
};

struct agent_request {
	agent_request_type_t type;
	struct btd_device *device; /* Weak reference */
	struct agent *agent;
	DBusMessage *msg;
	DBusPendingCall *call;
	void *cb;
	void *user_data;
	GDestroyNotify destroy;
};

static void agent_release(struct agent *agent)
{
	DBusMessage *message;

	DBG("Releasing agent %s, %s", agent->owner, agent->path);

	if (agent->request)
		agent_cancel(agent);

	message = dbus_message_new_method_call(agent->owner, agent->path,
						AGENT_INTERFACE, "Release");
	if (message == NULL) {
		error("Couldn't allocate D-Bus message");
		return;
	}

	g_dbus_send_message(btd_get_dbus_connection(), message);
}

static int send_cancel_request(struct agent_request *req)
{
	DBusMessage *message;

	DBG("Sending Cancel request to %s, %s", req->agent->owner,
							req->agent->path);

	message = dbus_message_new_method_call(req->agent->owner, req->agent->path,
						AGENT_INTERFACE, "Cancel");
	if (message == NULL) {
		error("Couldn't allocate D-Bus message");
		return -ENOMEM;
	}

	g_dbus_send_message(btd_get_dbus_connection(), message);

	return 0;
}

static void agent_request_free(struct agent_request *req, gboolean destroy)
{
	if (req->msg)
		dbus_message_unref(req->msg);
	if (req->call)
		dbus_pending_call_unref(req->call);
	if (req->agent && req->agent->request)
		req->agent->request = NULL;
	if (destroy && req->destroy)
		req->destroy(req->user_data);
	g_free(req);
}

static void set_io_cap(struct btd_adapter *adapter, gpointer user_data)
{
	struct agent *agent = user_data;
	uint8_t io_cap;

	if (agent)
		io_cap = agent->capability;
	else
		io_cap = IO_CAPABILITY_INVALID;

	adapter_set_io_capability(adapter, io_cap);
}

static bool add_default_agent(struct agent *agent)
{
	if (queue_peek_head(default_agents) == agent)
		return true;

	queue_remove(default_agents, agent);

	if (!queue_push_head(default_agents, agent))
		return false;

	DBG("Default agent set to %s %s", agent->owner, agent->path);

	adapter_foreach(set_io_cap, agent);

	return true;
}

static void remove_default_agent(struct agent *agent)
{
	if (queue_peek_head(default_agents) != agent) {
		queue_remove(default_agents, agent);
		return;
	}

	queue_remove(default_agents, agent);

	agent = queue_peek_head(default_agents);
	if (agent)
		DBG("Default agent set to %s %s", agent->owner, agent->path);
	else
		DBG("Default agent cleared");

	adapter_foreach(set_io_cap, agent);
}

static void agent_disconnect(DBusConnection *conn, void *user_data)
{
	struct agent *agent = user_data;

	DBG("Agent %s disconnected", agent->owner);

	if (agent->watch > 0) {
		g_dbus_remove_watch(conn, agent->watch);
		agent->watch = 0;
	}

	remove_default_agent(agent);

	g_hash_table_remove(agent_list, agent->owner);
}

struct agent *agent_ref(struct agent *agent)
{
	agent->ref++;

	DBG("%p: ref=%d", agent, agent->ref);

	return agent;
}

void agent_unref(struct agent *agent)
{
	agent->ref--;

	DBG("%p: ref=%d", agent, agent->ref);

	if (agent->ref > 0)
		return;

	if (agent->request) {
		DBusError err;
		agent_pincode_cb pincode_cb;
		agent_passkey_cb passkey_cb;
		agent_cb cb;

		dbus_error_init(&err);
		dbus_set_error_const(&err, ERROR_INTERFACE ".Failed",
								"Canceled");

		switch (agent->request->type) {
		case AGENT_REQUEST_PINCODE:
			pincode_cb = agent->request->cb;
			pincode_cb(agent, &err, NULL, agent->request->user_data);
			break;
		case AGENT_REQUEST_PASSKEY:
			passkey_cb = agent->request->cb;
			passkey_cb(agent, &err, 0, agent->request->user_data);
			break;
		case AGENT_REQUEST_CONFIRMATION:
		case AGENT_REQUEST_AUTHORIZATION:
		case AGENT_REQUEST_AUTHORIZE_SERVICE:
		case AGENT_REQUEST_DISPLAY_PINCODE:
		default:
			cb = agent->request->cb;
			cb(agent, &err, agent->request->user_data);
		}

		dbus_error_free(&err);

		agent_cancel(agent);
	}

	g_free(agent->owner);
	g_free(agent->path);

	g_free(agent);
}

struct agent *agent_get(const char *owner)
{
	struct agent *agent;

	if (owner) {
		agent = g_hash_table_lookup(agent_list, owner);
		if (agent)
			return agent_ref(agent);
	}

	if (!queue_isempty(default_agents))
		return agent_ref(queue_peek_head(default_agents));

	return NULL;
}

static struct agent *agent_create( const char *name, const char *path,
							uint8_t capability)
{
	struct agent *agent;

	agent = g_new0(struct agent, 1);

	agent->owner = g_strdup(name);
	agent->path = g_strdup(path);
	agent->capability = capability;

	agent->watch = g_dbus_add_disconnect_watch(btd_get_dbus_connection(),
							name, agent_disconnect,
							agent, NULL);

	if (queue_isempty(default_agents))
		add_default_agent(agent);
	else
		queue_push_tail(default_agents, agent);

	return agent_ref(agent);
}

static struct agent_request *agent_request_new(struct agent *agent,
						struct btd_device *device,
						agent_request_type_t type,
						void *cb,
						void *user_data,
						GDestroyNotify destroy)
{
	struct agent_request *req;

	req = g_new0(struct agent_request, 1);

	req->agent = agent;
	req->device = device;
	req->type = type;
	req->cb = cb;
	req->user_data = user_data;
	req->destroy = destroy;

	return req;
}

int agent_cancel(struct agent *agent)
{
	if (!agent->request)
		return -EINVAL;

	if (agent->request->call) {
		dbus_pending_call_cancel(agent->request->call);
		send_cancel_request(agent->request);
	}

	agent_request_free(agent->request, TRUE);
	agent->request = NULL;

	return 0;
}

static void simple_agent_reply(DBusPendingCall *call, void *user_data)
{
	struct agent_request *req = user_data;
	struct agent *agent = req->agent;
	DBusMessage *message;
	DBusError err;
	agent_cb cb = req->cb;

	/* steal_reply will always return non-NULL since the callback
	 * is only called after a reply has been received */
	message = dbus_pending_call_steal_reply(call);

	/* Protect from the callback freeing the agent */
	agent_ref(agent);

	dbus_error_init(&err);
	if (dbus_set_error_from_message(&err, message)) {
		DBG("agent error reply: %s, %s", err.name, err.message);

		cb(agent, &err, req->user_data);

		if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY)) {
			error("Timed out waiting for reply from agent");
			agent_cancel(agent);
			dbus_message_unref(message);
			dbus_error_free(&err);
			agent_unref(agent);
			return;
		}

		dbus_error_free(&err);
		goto done;
	}

	if (!dbus_message_get_args(message, &err, DBUS_TYPE_INVALID)) {
		error("Wrong reply signature: %s", err.message);
		cb(agent, &err, req->user_data);
		dbus_error_free(&err);
		goto done;
	}

	cb(agent, NULL, req->user_data);
done:
	dbus_message_unref(message);

	agent->request = NULL;
	agent_request_free(req, TRUE);
	agent_unref(agent);
}

static int agent_call_authorize_service(struct agent_request *req,
						const char *uuid)
{
	struct agent *agent = req->agent;
	const char *path;

	req->msg = dbus_message_new_method_call(agent->owner, agent->path,
					AGENT_INTERFACE, "AuthorizeService");
	if (!req->msg) {
		error("Couldn't allocate D-Bus message");
		return -ENOMEM;
	}

	path = device_get_path(req->device);

	dbus_message_append_args(req->msg,
				DBUS_TYPE_OBJECT_PATH, &path,
				DBUS_TYPE_STRING, &uuid,
				DBUS_TYPE_INVALID);

	if (g_dbus_send_message_with_reply(btd_get_dbus_connection(),
						req->msg, &req->call,
						REQUEST_TIMEOUT) == FALSE) {
		error("D-Bus send failed");
		return -EIO;
	}

	dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL);

	DBG("authorize service request was sent for %s", path);

	return 0;
}

static int agent_has_request(struct agent *agent, struct btd_device *device,
						agent_request_type_t type)
{
	if (!agent->request)
		return 0;

	if (agent->request->type != type)
		return -EBUSY;

	/* Check if request pending is for the same address */
	if (bacmp(device_get_address(agent->request->device),
			btd_adapter_get_address(device_get_adapter(device))))
		return -EBUSY;

	/* It must match in either direction */
	if (bacmp(device_get_address(device),
			btd_adapter_get_address(
			device_get_adapter(agent->request->device))))
		return -EBUSY;

	return -EINPROGRESS;
}

int agent_authorize_service(struct agent *agent, struct btd_device *device,
				const char *uuid, agent_cb cb,
				void *user_data, GDestroyNotify destroy)
{
	struct agent_request *req;
	int err;

	err = agent_has_request(agent, device, AGENT_REQUEST_AUTHORIZE_SERVICE);
	if (err)
		return err;

	req = agent_request_new(agent, device, AGENT_REQUEST_AUTHORIZE_SERVICE,
						cb, user_data, destroy);

	err = agent_call_authorize_service(req, uuid);
	if (err < 0) {
		agent_request_free(req, FALSE);
		return -ENOMEM;
	}

	agent->request = req;

	return 0;
}

static void pincode_reply(DBusPendingCall *call, void *user_data)
{
	struct agent_request *req = user_data;
	struct agent *agent = req->agent;
	agent_pincode_cb cb = req->cb;
	DBusMessage *message;
	DBusError err;
	size_t len;
	char *pin;

	/* steal_reply will always return non-NULL since the callback
	 * is only called after a reply has been received */
	message = dbus_pending_call_steal_reply(call);

	/* Protect from the callback freeing the agent */
	agent_ref(agent);

	dbus_error_init(&err);
	if (dbus_set_error_from_message(&err, message)) {
		error("Agent %s replied with an error: %s, %s",
				agent->path, err.name, err.message);

		cb(agent, &err, NULL, req->user_data);
		dbus_error_free(&err);
		goto done;
	}

	if (!dbus_message_get_args(message, &err,
				DBUS_TYPE_STRING, &pin,
				DBUS_TYPE_INVALID)) {
		error("Wrong passkey reply signature: %s", err.message);
		cb(agent, &err, NULL, req->user_data);
		dbus_error_free(&err);
		goto done;
	}

	len = strlen(pin);

	if (len > 16 || len < 1) {
		error("Invalid PIN length (%zu) from agent", len);
		dbus_set_error_const(&err, ERROR_INTERFACE ".InvalidArgs",
					"Invalid passkey length");
		cb(agent, &err, NULL, req->user_data);
		dbus_error_free(&err);
		goto done;
	}

	cb(agent, NULL, pin, req->user_data);

done:
	if (message)
		dbus_message_unref(message);

	dbus_pending_call_cancel(req->call);
	agent->request = NULL;
	agent_request_free(req, TRUE);
	agent_unref(agent);
}

static int pincode_request_new(struct agent_request *req, dbus_bool_t secure)
{
	struct agent *agent = req->agent;
	const char *path;

	/* TODO: Add a new method or a new param to Agent interface to request
		secure pin. */

	req->msg = dbus_message_new_method_call(agent->owner, agent->path,
					AGENT_INTERFACE, "RequestPinCode");
	if (req->msg == NULL) {
		error("Couldn't allocate D-Bus message");
		return -ENOMEM;
	}

	path = device_get_path(req->device);

	dbus_message_append_args(req->msg, DBUS_TYPE_OBJECT_PATH, &path,
					DBUS_TYPE_INVALID);

	if (g_dbus_send_message_with_reply(btd_get_dbus_connection(), req->msg,
					&req->call, REQUEST_TIMEOUT) == FALSE) {
		error("D-Bus send failed");
		return -EIO;
	}

	dbus_pending_call_set_notify(req->call, pincode_reply, req, NULL);
	return 0;
}

int agent_request_pincode(struct agent *agent, struct btd_device *device,
				agent_pincode_cb cb, gboolean secure,
				void *user_data, GDestroyNotify destroy)
{
	struct agent_request *req;
	int err;

	if (agent->request)
		return -EBUSY;

	req = agent_request_new(agent, device, AGENT_REQUEST_PINCODE, cb,
							user_data, destroy);

	err = pincode_request_new(req, secure);
	if (err < 0)
		goto failed;

	agent->request = req;

	return 0;

failed:
	agent_request_free(req, FALSE);
	return err;
}

static void passkey_reply(DBusPendingCall *call, void *user_data)
{
	struct agent_request *req = user_data;
	struct agent *agent = req->agent;
	agent_passkey_cb cb = req->cb;
	DBusMessage *message;
	DBusError err;
	uint32_t passkey;

	/* steal_reply will always return non-NULL since the callback
	 * is only called after a reply has been received */
	message = dbus_pending_call_steal_reply(call);

	dbus_error_init(&err);
	if (dbus_set_error_from_message(&err, message)) {
		error("Agent replied with an error: %s, %s",
						err.name, err.message);
		cb(agent, &err, 0, req->user_data);
		dbus_error_free(&err);
		goto done;
	}

	if (!dbus_message_get_args(message, &err,
				DBUS_TYPE_UINT32, &passkey,
				DBUS_TYPE_INVALID)) {
		error("Wrong passkey reply signature: %s", err.message);
		cb(agent, &err, 0, req->user_data);
		dbus_error_free(&err);
		goto done;
	}

	cb(agent, NULL, passkey, req->user_data);

done:
	if (message)
		dbus_message_unref(message);

	dbus_pending_call_cancel(req->call);
	agent->request = NULL;
	agent_request_free(req, TRUE);
}

static int passkey_request_new(struct agent_request *req)
{
	struct agent *agent = req->agent;
	const char *path;

	req->msg = dbus_message_new_method_call(agent->owner, agent->path,
					AGENT_INTERFACE, "RequestPasskey");
	if (req->msg == NULL) {
		error("Couldn't allocate D-Bus message");
		return -ENOMEM;
	}

	path = device_get_path(req->device);

	dbus_message_append_args(req->msg, DBUS_TYPE_OBJECT_PATH, &path,
					DBUS_TYPE_INVALID);

	if (g_dbus_send_message_with_reply(btd_get_dbus_connection(), req->msg,
					&req->call, REQUEST_TIMEOUT) == FALSE) {
		error("D-Bus send failed");
		return -EIO;
	}

	dbus_pending_call_set_notify(req->call, passkey_reply, req, NULL);
	return 0;
}

int agent_request_passkey(struct agent *agent, struct btd_device *device,
				agent_passkey_cb cb, void *user_data,
				GDestroyNotify destroy)
{
	struct agent_request *req;
	int err;

	if (agent->request)
		return -EBUSY;

	DBG("Calling Agent.RequestPasskey: name=%s, path=%s",
			agent->owner, agent->path);

	req = agent_request_new(agent, device, AGENT_REQUEST_PASSKEY, cb,
							user_data, destroy);

	err = passkey_request_new(req);
	if (err < 0)
		goto failed;

	agent->request = req;

	return 0;

failed:
	agent_request_free(req, FALSE);
	return err;
}

static int confirmation_request_new(struct agent_request *req,
					uint32_t passkey)
{
	struct agent *agent = req->agent;
	const char *path;

	req->msg = dbus_message_new_method_call(agent->owner, agent->path,
				AGENT_INTERFACE, "RequestConfirmation");
	if (req->msg == NULL) {
		error("Couldn't allocate D-Bus message");
		return -ENOMEM;
	}

	path = device_get_path(req->device);

	dbus_message_append_args(req->msg,
				DBUS_TYPE_OBJECT_PATH, &path,
				DBUS_TYPE_UINT32, &passkey,
				DBUS_TYPE_INVALID);

	if (g_dbus_send_message_with_reply(btd_get_dbus_connection(), req->msg,
				&req->call, REQUEST_TIMEOUT) == FALSE) {
		error("D-Bus send failed");
		return -EIO;
	}

	dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL);

	return 0;
}

int agent_request_confirmation(struct agent *agent, struct btd_device *device,
				uint32_t passkey, agent_cb cb,
				void *user_data, GDestroyNotify destroy)
{
	struct agent_request *req;
	int err;

	err = agent_has_request(agent, device, AGENT_REQUEST_CONFIRMATION);
	if (err)
		return err;

	DBG("Calling Agent.RequestConfirmation: name=%s, path=%s, passkey=%06u",
			agent->owner, agent->path, passkey);

	req = agent_request_new(agent, device, AGENT_REQUEST_CONFIRMATION, cb,
				user_data, destroy);

	err = confirmation_request_new(req, passkey);
	if (err < 0)
		goto failed;

	agent->request = req;

	return 0;

failed:
	agent_request_free(req, FALSE);
	return err;
}

static int authorization_request_new(struct agent_request *req)
{
	struct agent *agent = req->agent;
	const char *path;

	req->msg = dbus_message_new_method_call(agent->owner, agent->path,
				AGENT_INTERFACE, "RequestAuthorization");
	if (req->msg == NULL) {
		error("Couldn't allocate D-Bus message");
		return -ENOMEM;
	}

	path = device_get_path(req->device);

	dbus_message_append_args(req->msg,
				DBUS_TYPE_OBJECT_PATH, &path,
				DBUS_TYPE_INVALID);

	if (g_dbus_send_message_with_reply(btd_get_dbus_connection(), req->msg,
				&req->call, REQUEST_TIMEOUT) == FALSE) {
		error("D-Bus send failed");
		return -EIO;
	}

	dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL);

	return 0;
}

int agent_request_authorization(struct agent *agent, struct btd_device *device,
						agent_cb cb, void *user_data,
						GDestroyNotify destroy)
{
	struct agent_request *req;
	int err;

	err = agent_has_request(agent, device, AGENT_REQUEST_AUTHORIZATION);
	if (err)
		return err;

	DBG("Calling Agent.RequestAuthorization: name=%s, path=%s",
						agent->owner, agent->path);

	req = agent_request_new(agent, device, AGENT_REQUEST_AUTHORIZATION, cb,
				user_data, destroy);

	err = authorization_request_new(req);
	if (err < 0)
		goto failed;

	agent->request = req;

	return 0;

failed:
	agent_request_free(req, FALSE);
	return err;
}

int agent_display_passkey(struct agent *agent, struct btd_device *device,
				uint32_t passkey, uint16_t entered)
{
	DBusMessage *message;
	const char *dev_path = device_get_path(device);

	message = dbus_message_new_method_call(agent->owner, agent->path,
					AGENT_INTERFACE, "DisplayPasskey");
	if (!message) {
		error("Couldn't allocate D-Bus message");
		return -1;
	}

	dbus_message_append_args(message,
				DBUS_TYPE_OBJECT_PATH, &dev_path,
				DBUS_TYPE_UINT32, &passkey,
				DBUS_TYPE_UINT16, &entered,
				DBUS_TYPE_INVALID);

	if (!g_dbus_send_message(btd_get_dbus_connection(), message)) {
		error("D-Bus send failed");
		return -1;
	}

	return 0;
}

static void display_pincode_reply(DBusPendingCall *call, void *user_data)
{
	struct agent_request *req = user_data;
	struct agent *agent = req->agent;
	DBusMessage *message;
	DBusError err;
	agent_cb cb = req->cb;

	/* clear agent->request early; our callback will likely try
	 * another request */
	agent->request = NULL;

	/* steal_reply will always return non-NULL since the callback
	 * is only called after a reply has been received */
	message = dbus_pending_call_steal_reply(call);

	dbus_error_init(&err);
	if (dbus_set_error_from_message(&err, message)) {
		error("Agent replied with an error: %s, %s",
						err.name, err.message);

		cb(agent, &err, req->user_data);

		if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY)) {
			agent_cancel(agent);
			dbus_message_unref(message);
			dbus_error_free(&err);
			return;
		}

		dbus_error_free(&err);
		goto done;
	}

	if (!dbus_message_get_args(message, &err, DBUS_TYPE_INVALID)) {
		error("Wrong reply signature: %s", err.message);
		cb(agent, &err, req->user_data);
		dbus_error_free(&err);
		goto done;
	}

	cb(agent, NULL, req->user_data);
done:
	dbus_message_unref(message);

	agent_request_free(req, TRUE);
}

static int display_pincode_request_new(struct agent_request *req,
					const char *pincode)
{
	struct agent *agent = req->agent;
	const char *path;

	req->msg = dbus_message_new_method_call(agent->owner, agent->path,
					AGENT_INTERFACE, "DisplayPinCode");
	if (req->msg == NULL) {
		error("Couldn't allocate D-Bus message");
		return -ENOMEM;
	}

	path = device_get_path(req->device);

	dbus_message_append_args(req->msg,
					DBUS_TYPE_OBJECT_PATH, &path,
					DBUS_TYPE_STRING, &pincode,
					DBUS_TYPE_INVALID);

	if (g_dbus_send_message_with_reply(btd_get_dbus_connection(), req->msg,
				&req->call, REQUEST_TIMEOUT) == FALSE) {
		error("D-Bus send failed");
		return -EIO;
	}

	dbus_pending_call_set_notify(req->call, display_pincode_reply,
								req, NULL);

	return 0;
}

int agent_display_pincode(struct agent *agent, struct btd_device *device,
				const char *pincode, agent_cb cb,
				void *user_data, GDestroyNotify destroy)
{
	struct agent_request *req;
	int err;

	err = agent_has_request(agent, device, AGENT_REQUEST_DISPLAY_PINCODE);
	if (err)
		return err;

	DBG("Calling Agent.DisplayPinCode: name=%s, path=%s, pincode=%s",
					agent->owner, agent->path, pincode);

	req = agent_request_new(agent, device, AGENT_REQUEST_DISPLAY_PINCODE,
				cb, user_data, destroy);

	err = display_pincode_request_new(req, pincode);
	if (err < 0)
		goto failed;

	agent->request = req;

	return 0;

failed:
	agent_request_free(req, FALSE);
	return err;
}

uint8_t agent_get_io_capability(struct agent *agent)
{
	return agent->capability;
}

static void agent_destroy(gpointer data)
{
	struct agent *agent = data;

	DBG("agent %s", agent->owner);

	if (agent->watch > 0) {
		g_dbus_remove_watch(btd_get_dbus_connection(), agent->watch);
		agent->watch = 0;
		agent_release(agent);
	}

	remove_default_agent(agent);

	agent_unref(agent);
}

static uint8_t parse_io_capability(const char *capability)
{
	if (g_str_equal(capability, ""))
		return IO_CAPABILITY_KEYBOARDDISPLAY;
	if (g_str_equal(capability, "DisplayOnly"))
		return IO_CAPABILITY_DISPLAYONLY;
	if (g_str_equal(capability, "DisplayYesNo"))
		return IO_CAPABILITY_DISPLAYYESNO;
	if (g_str_equal(capability, "KeyboardOnly"))
		return IO_CAPABILITY_KEYBOARDONLY;
	if (g_str_equal(capability, "NoInputNoOutput"))
		return IO_CAPABILITY_NOINPUTNOOUTPUT;
	if (g_str_equal(capability, "KeyboardDisplay"))
		return IO_CAPABILITY_KEYBOARDDISPLAY;
	return IO_CAPABILITY_INVALID;
}

static DBusMessage *register_agent(DBusConnection *conn,
					DBusMessage *msg, void *user_data)
{
	struct agent *agent;
	const char *sender, *path, *capability;
	uint8_t cap;

	sender = dbus_message_get_sender(msg);

	agent = g_hash_table_lookup(agent_list, sender);
	if (agent)
		return btd_error_already_exists(msg);

	if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
						DBUS_TYPE_STRING, &capability,
						DBUS_TYPE_INVALID) == FALSE)
		return btd_error_invalid_args(msg);

	cap = parse_io_capability(capability);
	if (cap == IO_CAPABILITY_INVALID)
		return btd_error_invalid_args(msg);

	agent = agent_create(sender, path, cap);
	if (!agent)
		return btd_error_invalid_args(msg);

	DBG("agent %s", agent->owner);

	g_hash_table_replace(agent_list, agent->owner, agent);

	return dbus_message_new_method_return(msg);
}

static DBusMessage *unregister_agent(DBusConnection *conn,
					DBusMessage *msg, void *user_data)
{
	struct agent *agent;
	const char *sender, *path;

	sender = dbus_message_get_sender(msg);

	agent = g_hash_table_lookup(agent_list, sender);
	if (!agent)
		return btd_error_does_not_exist(msg);

	DBG("agent %s", agent->owner);

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

	if (g_str_equal(path, agent->path) == FALSE)
		return btd_error_does_not_exist(msg);

	agent_disconnect(conn, agent);

	return dbus_message_new_method_return(msg);
}

static DBusMessage *request_default(DBusConnection *conn, DBusMessage *msg,
							void *user_data)
{
	struct agent *agent;
	const char *sender, *path;

	sender = dbus_message_get_sender(msg);

	agent = g_hash_table_lookup(agent_list, sender);
	if (!agent)
		return btd_error_does_not_exist(msg);

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

	if (g_str_equal(path, agent->path) == FALSE)
		return btd_error_does_not_exist(msg);

	if (!add_default_agent(agent))
		return btd_error_failed(msg, "Failed to set as default");

	return dbus_message_new_method_return(msg);
}

static const GDBusMethodTable methods[] = {
	{ GDBUS_METHOD("RegisterAgent",
			GDBUS_ARGS({ "agent", "o"}, { "capability", "s" }),
			NULL, register_agent) },
	{ GDBUS_METHOD("UnregisterAgent", GDBUS_ARGS({ "agent", "o" }),
			NULL, unregister_agent) },
	{ GDBUS_METHOD("RequestDefaultAgent", GDBUS_ARGS({ "agent", "o" }),
			NULL, request_default ) },
	{ }
};

void btd_agent_init(void)
{
	agent_list = g_hash_table_new_full(g_str_hash, g_str_equal,
						NULL, agent_destroy);

	default_agents = queue_new();

	g_dbus_register_interface(btd_get_dbus_connection(),
				"/org/bluez", "org.bluez.AgentManager1",
				methods, NULL, NULL, NULL, NULL);
}

void btd_agent_cleanup(void)
{
	g_dbus_unregister_interface(btd_get_dbus_connection(),
				"/org/bluez", "org.bluez.AgentManager1");

	g_hash_table_destroy(agent_list);
	queue_destroy(default_agents, NULL);
}
