/*
 * Embedded Linux library
 * Copyright (C) 2011-2014  Intel Corporation
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

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

#define _GNU_SOURCE
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netdb.h>
#include <errno.h>

#include "util.h"
#include "io.h"
#include "idle.h"
#include "queue.h"
#include "hashmap.h"
#include "dbus.h"
#include "private.h"
#include "useful.h"
#include "dbus-private.h"

#define DEFAULT_SYSTEM_BUS_ADDRESS "unix:path=/var/run/dbus/system_bus_socket"

#define DBUS_SERVICE_DBUS	"org.freedesktop.DBus"

#define DBUS_PATH_DBUS		"/org/freedesktop/DBus"

#define DBUS_MAXIMUM_MATCH_RULE_LENGTH	1024

enum auth_state {
	WAITING_FOR_OK,
	WAITING_FOR_AGREE_UNIX_FD,
	SETUP_DONE
};

struct l_dbus_ops {
	char version;
	bool (*send_message)(struct l_dbus *bus,
				struct l_dbus_message *message);
	struct l_dbus_message *(*recv_message)(struct l_dbus *bus);
	void (*free)(struct l_dbus *bus);
	struct _dbus_name_ops name_ops;
	struct _dbus_filter_ops filter_ops;
	uint32_t (*name_acquire)(struct l_dbus *dbus, const char *name,
				bool allow_replacement, bool replace_existing,
				bool queue, l_dbus_name_acquire_func_t callback,
				void *user_data);
};

struct l_dbus {
	struct l_io *io;
	char *guid;
	bool negotiate_unix_fd;
	bool support_unix_fd;
	bool is_ready;
	char *unique_name;
	unsigned int next_id;
	uint32_t next_serial;
	struct l_queue *message_queue;
	struct l_hashmap *message_list;
	struct l_hashmap *signal_list;
	l_dbus_ready_func_t ready_handler;
	l_dbus_destroy_func_t ready_destroy;
	void *ready_data;
	l_dbus_disconnect_func_t disconnect_handler;
	l_dbus_destroy_func_t disconnect_destroy;
	void *disconnect_data;
	l_dbus_debug_func_t debug_handler;
	l_dbus_destroy_func_t debug_destroy;
	void *debug_data;
	struct _dbus_object_tree *tree;
	struct _dbus_name_cache *name_cache;
	struct _dbus_filter *filter;
	bool name_notify_enabled;

	const struct l_dbus_ops *driver;
};

struct l_dbus_classic {
	struct l_dbus super;
	void *auth_command;
	enum auth_state auth_state;
	bool skip_hello;
	struct l_hashmap *match_strings;
	int *fd_buf;
	unsigned int num_fds;
};

struct message_callback {
	uint32_t serial;
	struct l_dbus_message *message;
	l_dbus_message_func_t callback;
	l_dbus_destroy_func_t destroy;
	void *user_data;
};

struct signal_callback {
	unsigned int id;
	l_dbus_message_func_t callback;
	l_dbus_destroy_func_t destroy;
	void *user_data;
};

static void message_queue_destroy(void *data)
{
	struct message_callback *callback = data;

	l_dbus_message_unref(callback->message);

	if (callback->destroy)
		callback->destroy(callback->user_data);

	l_free(callback);
}

static void message_list_destroy(void *value)
{
	message_queue_destroy(value);
}

static void signal_list_destroy(void *value)
{
	struct signal_callback *callback = value;

	if (callback->destroy)
		callback->destroy(callback->user_data);

	l_free(callback);
}

static bool message_write_handler(struct l_io *io, void *user_data)
{
	struct l_dbus *dbus = user_data;
	struct l_dbus_message *message;
	struct message_callback *callback;
	const void *header, *body;
	size_t header_size, body_size;

	callback = l_queue_pop_head(dbus->message_queue);
	if (!callback)
		return false;

	message = callback->message;
	if (_dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL &&
			callback->callback == NULL)
		l_dbus_message_set_no_reply(message, true);

	_dbus_message_set_serial(message, callback->serial);

	if (!dbus->driver->send_message(dbus, message)) {
		message_queue_destroy(callback);
		return false;
	}

	header = _dbus_message_get_header(message, &header_size);
	body = _dbus_message_get_body(message, &body_size);
	l_util_hexdump_two(false, header, header_size, body, body_size,
				dbus->debug_handler, dbus->debug_data);

	if (callback->callback == NULL) {
		message_queue_destroy(callback);
		goto done;
	}

	l_hashmap_insert(dbus->message_list,
				L_UINT_TO_PTR(callback->serial), callback);

done:
	if (l_queue_isempty(dbus->message_queue))
		return false;

	/* Only continue sending messages if the connection is ready */
	return dbus->is_ready;
}

static void handle_method_return(struct l_dbus *dbus,
					struct l_dbus_message *message)
{
	struct message_callback *callback;
	uint32_t reply_serial;

	reply_serial = _dbus_message_get_reply_serial(message);
	if (reply_serial == 0)
		return;

	callback = l_hashmap_remove(dbus->message_list,
					L_UINT_TO_PTR(reply_serial));
	if (!callback)
		return;

	if (callback->callback)
		callback->callback(message, callback->user_data);

	message_queue_destroy(callback);
}

static void handle_error(struct l_dbus *dbus, struct l_dbus_message *message)
{
	struct message_callback *callback;
	uint32_t reply_serial;

	reply_serial = _dbus_message_get_reply_serial(message);
	if (reply_serial == 0)
		return;

	callback = l_hashmap_remove(dbus->message_list,
					L_UINT_TO_PTR(reply_serial));
	if (!callback)
		return;

	if (callback->callback)
		callback->callback(message, callback->user_data);

	message_queue_destroy(callback);
}

static void process_signal(const void *key, void *value, void *user_data)
{
	struct signal_callback *callback = value;
	struct l_dbus_message *message = user_data;

	if (callback->callback)
		callback->callback(message, callback->user_data);
}

static void handle_signal(struct l_dbus *dbus, struct l_dbus_message *message)
{
	l_hashmap_foreach(dbus->signal_list, process_signal, message);
}

static bool message_read_handler(struct l_io *io, void *user_data)
{
	struct l_dbus *dbus = user_data;
	struct l_dbus_message *message;
	const void *header, *body;
	size_t header_size, body_size;
	enum dbus_message_type msgtype;

	message = dbus->driver->recv_message(dbus);
	if (!message)
		return true;

	header = _dbus_message_get_header(message, &header_size);
	body = _dbus_message_get_body(message, &body_size);
	l_util_hexdump_two(true, header, header_size, body, body_size,
				dbus->debug_handler, dbus->debug_data);

	msgtype = _dbus_message_get_type(message);

	switch (msgtype) {
	case DBUS_MESSAGE_TYPE_METHOD_RETURN:
		handle_method_return(dbus, message);
		break;
	case DBUS_MESSAGE_TYPE_ERROR:
		handle_error(dbus, message);
		break;
	case DBUS_MESSAGE_TYPE_SIGNAL:
		handle_signal(dbus, message);
		break;
	case DBUS_MESSAGE_TYPE_METHOD_CALL:
		if (!_dbus_object_tree_dispatch(dbus->tree, dbus, message)) {
			struct l_dbus_message *error;

			error = l_dbus_message_new_error(message,
					"org.freedesktop.DBus.Error.NotFound",
					"No matching method found");
			l_dbus_send(dbus, error);
		}

		break;
	}

	l_dbus_message_unref(message);

	return true;
}

static uint32_t send_message(struct l_dbus *dbus, bool priority,
				struct l_dbus_message *message,
				l_dbus_message_func_t function,
				void *user_data, l_dbus_destroy_func_t destroy)
{
	struct message_callback *callback;
	enum dbus_message_type type;
	const char *path;

	type = _dbus_message_get_type(message);

	if ((type == DBUS_MESSAGE_TYPE_METHOD_RETURN ||
				type == DBUS_MESSAGE_TYPE_ERROR) &&
			_dbus_message_get_reply_serial(message) == 0) {
		l_dbus_message_unref(message);
		return 0;
	}

	/* Default empty signature for method return messages */
	if (type == DBUS_MESSAGE_TYPE_METHOD_RETURN &&
			!l_dbus_message_get_signature(message))
		l_dbus_message_set_arguments(message, "");

	callback = l_new(struct message_callback, 1);

	callback->serial = dbus->next_serial++;
	callback->message = message;
	callback->callback = function;
	callback->destroy = destroy;
	callback->user_data = user_data;

	if (priority) {
		l_queue_push_head(dbus->message_queue, callback);

		l_io_set_write_handler(dbus->io, message_write_handler,
							dbus, NULL);

		return callback->serial;
	}

	path = l_dbus_message_get_path(message);
	if (path)
		_dbus_object_tree_signals_flush(dbus, path);

	l_queue_push_tail(dbus->message_queue, callback);

	if (dbus->is_ready)
		l_io_set_write_handler(dbus->io, message_write_handler,
							dbus, NULL);

	return callback->serial;
}

static void bus_ready(struct l_dbus *dbus)
{
	dbus->is_ready = true;

	if (dbus->ready_handler)
		dbus->ready_handler(dbus->ready_data);

	l_io_set_read_handler(dbus->io, message_read_handler, dbus, NULL);

	/* Check for messages added before the connection was ready */
	if (l_queue_isempty(dbus->message_queue))
		return;

	l_io_set_write_handler(dbus->io, message_write_handler, dbus, NULL);
}

static void hello_callback(struct l_dbus_message *message, void *user_data)
{
	struct l_dbus *dbus = user_data;
	const char *signature;
	const char *unique_name;

	signature = l_dbus_message_get_signature(message);
	if (!signature || strcmp(signature, "s")) {
		close(l_io_get_fd(dbus->io));
		return;
	}

	if (!l_dbus_message_get_arguments(message, "s", &unique_name)) {
		close(l_io_get_fd(dbus->io));
		return;
	}

	dbus->unique_name = l_strdup(unique_name);

	bus_ready(dbus);
}

static bool auth_write_handler(struct l_io *io, void *user_data)
{
	struct l_dbus_classic *classic = user_data;
	struct l_dbus *dbus = &classic->super;
	ssize_t written, len;
	int fd;

	fd = l_io_get_fd(io);

	if (!classic->auth_command)
		return false;

	len = strlen(classic->auth_command);
	if (!len)
		return false;

	written = L_TFR(send(fd, classic->auth_command, len, 0));
	if (written < 0)
		return false;

	l_util_hexdump(false, classic->auth_command, written,
					dbus->debug_handler, dbus->debug_data);

	if (written < len) {
		memmove(classic->auth_command, classic->auth_command + written,
				len + 1 - written);
		return true;
	}

	l_free(classic->auth_command);
	classic->auth_command = NULL;

	if (classic->auth_state == SETUP_DONE) {
		struct l_dbus_message *message;

		if (classic->skip_hello) {
			bus_ready(dbus);
			return true;
		}

		l_io_set_read_handler(dbus->io, message_read_handler,
							dbus, NULL);

		message = l_dbus_message_new_method_call(dbus,
							DBUS_SERVICE_DBUS,
							DBUS_PATH_DBUS,
							L_DBUS_INTERFACE_DBUS,
							"Hello");
		l_dbus_message_set_arguments(message, "");

		send_message(dbus, true, message, hello_callback, dbus, NULL);

		return true;
	}

	return false;
}

static bool auth_read_handler(struct l_io *io, void *user_data)
{
	struct l_dbus_classic *classic = user_data;
	struct l_dbus *dbus = &classic->super;
	char buffer[64];
	char *ptr, *end;
	ssize_t offset, len;
	int fd;

	fd = l_io_get_fd(io);

	ptr = buffer;
	offset = 0;

	while (1) {
		len = L_TFR(recv(fd, ptr + offset,
						sizeof(buffer) - offset,
						MSG_DONTWAIT));
		if (len < 0) {
			if (errno != EAGAIN)
				return false;

			break;
		}

		offset += len;
	}

	ptr = buffer;
	len = offset;

	if (!ptr || len < 3)
		return true;

	end = strstr(ptr, "\r\n");
	if (!end)
		return true;

	if (end - ptr + 2 != len)
		return true;

	l_util_hexdump(true, ptr, len, dbus->debug_handler, dbus->debug_data);

	*end = '\0';

	switch (classic->auth_state) {
	case WAITING_FOR_OK:
		if (!strncmp(ptr, "OK ", 3)) {
			enum auth_state state;
			const char *command;

			if (dbus->negotiate_unix_fd) {
				command = "NEGOTIATE_UNIX_FD\r\n";
				state = WAITING_FOR_AGREE_UNIX_FD;
			} else {
				command = "BEGIN\r\n";
				state = SETUP_DONE;
			}

			l_free(dbus->guid);
			dbus->guid = l_strdup(ptr + 3);

			classic->auth_command = l_strdup(command);
			classic->auth_state = state;
			break;
		} else if (!strncmp(ptr, "REJECTED ", 9)) {
			static const char *command = "AUTH ANONYMOUS\r\n";

			dbus->negotiate_unix_fd = true;

			classic->auth_command = l_strdup(command);
			classic->auth_state = WAITING_FOR_OK;
		}
		break;

	case WAITING_FOR_AGREE_UNIX_FD:
		if (!strncmp(ptr, "AGREE_UNIX_FD", 13)) {
			static const char *command = "BEGIN\r\n";

			dbus->support_unix_fd = true;

			classic->auth_command = l_strdup(command);
			classic->auth_state = SETUP_DONE;
			break;
		} else if (!strncmp(ptr, "ERROR", 5)) {
			static const char *command = "BEGIN\r\n";

			dbus->support_unix_fd = false;

			classic->auth_command = l_strdup(command);
			classic->auth_state = SETUP_DONE;
			break;
		}
		break;

	case SETUP_DONE:
		break;
	}

	l_io_set_write_handler(io, auth_write_handler, dbus, NULL);

	return true;
}

static void disconnect_handler(struct l_io *io, void *user_data)
{
	struct l_dbus *dbus = user_data;

	dbus->is_ready = false;

	l_util_debug(dbus->debug_handler, dbus->debug_data, "disconnect");

	if (dbus->disconnect_handler)
		dbus->disconnect_handler(dbus->disconnect_data);
}

static void dbus_init(struct l_dbus *dbus, int fd)
{
	dbus->io = l_io_new(fd);
	l_io_set_close_on_destroy(dbus->io, true);
	l_io_set_disconnect_handler(dbus->io, disconnect_handler, dbus, NULL);

	dbus->is_ready = false;
	dbus->next_id = 1;
	dbus->next_serial = 1;

	dbus->message_queue = l_queue_new();
	dbus->message_list = l_hashmap_new();
	dbus->signal_list = l_hashmap_new();

	dbus->tree = _dbus_object_tree_new();
}

static void classic_free(struct l_dbus *dbus)
{
	struct l_dbus_classic *classic =
		l_container_of(dbus, struct l_dbus_classic, super);
	unsigned int i;

	for (i = 0; i < classic->num_fds; i++)
		close(classic->fd_buf[i]);
	l_free(classic->fd_buf);

	l_free(classic->auth_command);
	l_hashmap_destroy(classic->match_strings, l_free);
	l_free(classic);
}

static bool classic_send_message(struct l_dbus *dbus,
					struct l_dbus_message *message)
{
	int fd = l_io_get_fd(dbus->io);
	struct msghdr msg;
	struct iovec iov[2], *iovpos;
	ssize_t r;
	int *fds = NULL;
	uint32_t num_fds = 0;
	struct cmsghdr *cmsg;
	int iovlen;

	iov[0].iov_base = _dbus_message_get_header(message, &iov[0].iov_len);
	iov[1].iov_base = _dbus_message_get_body(message, &iov[1].iov_len);

	if (dbus->support_unix_fd)
		fds = _dbus_message_get_fds(message, &num_fds);

	iovpos = iov;
	iovlen = 2;

	while (1) {
		memset(&msg, 0, sizeof(msg));
		msg.msg_iov = iovpos;
		msg.msg_iovlen = iovlen;

		if (num_fds) {
			msg.msg_control =
				alloca(CMSG_SPACE(num_fds * sizeof(int)));
			msg.msg_controllen = CMSG_LEN(num_fds * sizeof(int));

			cmsg = CMSG_FIRSTHDR(&msg);
			cmsg->cmsg_len = msg.msg_controllen;
			cmsg->cmsg_level = SOL_SOCKET;
			cmsg->cmsg_type = SCM_RIGHTS;
			memcpy(CMSG_DATA(cmsg), fds, num_fds * sizeof(int));
		}

		r = L_TFR(sendmsg(fd, &msg, 0));
		if (r < 0)
			return false;

		while ((size_t) r >= iovpos->iov_len) {
			r -= iovpos->iov_len;
			iovpos++;
			iovlen--;

			if (!iovlen)
				break;
		}

		if (!iovlen)
			break;

		iovpos->iov_base += r;
		iovpos->iov_len -= r;

		/* The FDs have been transmitted, don't retransmit */
		num_fds = 0;
	}

	return true;
}

static struct l_dbus_message *classic_recv_message(struct l_dbus *dbus)
{
	struct l_dbus_classic *classic =
		l_container_of(dbus, struct l_dbus_classic, super);
	int fd = l_io_get_fd(dbus->io);
	struct dbus_header hdr;
	struct msghdr msg;
	struct iovec iov[2], *iovpos;
	struct cmsghdr *cmsg;
	ssize_t len, r;
	void *header, *body;
	size_t header_size, body_size;
	union {
		uint8_t bytes[CMSG_SPACE(16 * sizeof(int))];
		struct cmsghdr align;
	} fd_buf;
	int *fds = NULL;
	uint32_t num_fds = 0;
	int iovlen;
	struct l_dbus_message *message;
	unsigned int i;

	len = recv(fd, &hdr, DBUS_HEADER_SIZE, MSG_PEEK | MSG_DONTWAIT);
	if (len != DBUS_HEADER_SIZE)
		return NULL;

	header_size = align_len(DBUS_HEADER_SIZE + hdr.dbus1.field_length, 8);
	header = l_malloc(header_size);

	body_size = hdr.dbus1.body_length;
	body = l_malloc(body_size);

	iov[0].iov_base = header;
	iov[0].iov_len  = header_size;
	iov[1].iov_base = body;
	iov[1].iov_len  = body_size;

	iovpos = iov;
	iovlen = 2;

	while (1) {
		memset(&msg, 0, sizeof(msg));
		msg.msg_iov = iovpos;
		msg.msg_iovlen = iovlen;
		msg.msg_control = &fd_buf;
		msg.msg_controllen = sizeof(fd_buf);

		r = L_TFR(recvmsg(fd, &msg,
					MSG_CMSG_CLOEXEC | MSG_WAITALL));
		if (r < 0)
			goto cmsg_fail;

		for (cmsg = CMSG_FIRSTHDR(&msg); cmsg;
				cmsg = CMSG_NXTHDR(&msg, cmsg)) {
			if (cmsg->cmsg_level != SOL_SOCKET ||
					cmsg->cmsg_type != SCM_RIGHTS)
				continue;

			num_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
			fds = (void *) CMSG_DATA(cmsg);

			/* Set FD_CLOEXEC on all file descriptors */
			for (i = 0; i < num_fds; i++) {
				long flags;

				flags = fcntl(fds[i], F_GETFD, NULL);
				if (flags < 0)
					continue;

				if (!(flags & FD_CLOEXEC))
					fcntl(fds[i], F_SETFD,
						flags | FD_CLOEXEC);
			}

			classic->fd_buf = l_realloc(classic->fd_buf,
						(classic->num_fds + num_fds) *
						sizeof(int));
			memcpy(classic->fd_buf + classic->num_fds, fds,
				num_fds * sizeof(int));
			classic->num_fds += num_fds;
		}

		while ((size_t) r >= iovpos->iov_len) {
			r -= iovpos->iov_len;
			iovpos++;
			iovlen--;

			if (!iovlen)
				break;
		}

		if (!iovlen)
			break;

		iovpos->iov_base += r;
		iovpos->iov_len -= r;
	}

	if (hdr.endian != DBUS_NATIVE_ENDIAN) {
		l_util_debug(dbus->debug_handler,
				dbus->debug_data, "Endianness incorrect");
		goto bad_msg;
	}

	if (hdr.version != 1) {
		l_util_debug(dbus->debug_handler,
				dbus->debug_data, "Protocol version incorrect");
		goto bad_msg;
	}

	num_fds = _dbus_message_unix_fds_from_header(header, header_size);
	if (num_fds > classic->num_fds)
		goto bad_msg;

	message = dbus_message_build(header, header_size, body, body_size,
					classic->fd_buf, num_fds);

	if (message && num_fds) {
		if (classic->num_fds > num_fds) {
			memmove(classic->fd_buf, classic->fd_buf + num_fds,
				(classic->num_fds - num_fds) * sizeof(int));
			classic->num_fds -= num_fds;
		} else {
			l_free(classic->fd_buf);

			classic->fd_buf = NULL;
			classic->num_fds = 0;
		}
	}

	if (message)
		return message;

bad_msg:
cmsg_fail:
	for (i = 0; i < classic->num_fds; i++)
		close(classic->fd_buf[i]);

	l_free(classic->fd_buf);

	classic->fd_buf = NULL;
	classic->num_fds = 0;

	l_free(header);
	l_free(body);

	return NULL;
}

static bool classic_add_match(struct l_dbus *dbus, unsigned int id,
				const struct _dbus_filter_condition *rule,
				int rule_len)
{
	struct l_dbus_classic *classic =
		l_container_of(dbus, struct l_dbus_classic, super);
	char *match_str;
	struct l_dbus_message *message;

	match_str = _dbus_filter_rule_to_str(rule, rule_len);

	l_hashmap_insert(classic->match_strings, L_UINT_TO_PTR(id), match_str);

	message = l_dbus_message_new_method_call(dbus,
						DBUS_SERVICE_DBUS,
						DBUS_PATH_DBUS,
						L_DBUS_INTERFACE_DBUS,
						"AddMatch");

	l_dbus_message_set_arguments(message, "s", match_str);

	send_message(dbus, false, message, NULL, NULL, NULL);

	return true;
}

static bool classic_remove_match(struct l_dbus *dbus, unsigned int id)
{
	struct l_dbus_classic *classic =
		l_container_of(dbus, struct l_dbus_classic, super);
	char *match_str = l_hashmap_remove(classic->match_strings,
						L_UINT_TO_PTR(id));
	struct l_dbus_message *message;

	if (!match_str)
		return false;

	message = l_dbus_message_new_method_call(dbus,
						DBUS_SERVICE_DBUS,
						DBUS_PATH_DBUS,
						L_DBUS_INTERFACE_DBUS,
						"RemoveMatch");

	l_dbus_message_set_arguments(message, "s", match_str);

	send_message(dbus, false, message, NULL, NULL, NULL);

	l_free(match_str);

	return true;
}

static void name_owner_changed_cb(struct l_dbus_message *message,
					void *user_data)
{
	struct l_dbus *dbus = user_data;
	char *name, *old, *new;

	if (!l_dbus_message_get_arguments(message, "sss", &name, &old, &new))
		return;

	_dbus_name_cache_notify(dbus->name_cache, name, new);
}

struct get_name_owner_request {
	struct l_dbus_message *message;
	struct l_dbus *dbus;
};

static void get_name_owner_reply_cb(struct l_dbus_message *reply,
					void *user_data)
{
	struct get_name_owner_request *req = user_data;
	const char *name, *owner;

	/* No name owner yet */
	if (l_dbus_message_is_error(reply))
		return;

	/* Shouldn't happen */
	if (!l_dbus_message_get_arguments(reply, "s", &owner))
		return;

	/* Shouldn't happen */
	if (!l_dbus_message_get_arguments(req->message, "s", &name))
		return;

	_dbus_name_cache_notify(req->dbus->name_cache, name, owner);
}

static bool classic_get_name_owner(struct l_dbus *bus, const char *name)
{
	struct get_name_owner_request *req;

	/* Name resolution is not performed for DBUS_SERVICE_DBUS */
	if (!strcmp(name, DBUS_SERVICE_DBUS))
		return false;

	req = l_new(struct get_name_owner_request, 1);
	req->dbus = bus;
	req->message = l_dbus_message_new_method_call(bus,
							DBUS_SERVICE_DBUS,
							DBUS_PATH_DBUS,
							L_DBUS_INTERFACE_DBUS,
							"GetNameOwner");

	l_dbus_message_set_arguments(req->message, "s", name);

	send_message(bus, false, req->message, get_name_owner_reply_cb,
			req, l_free);

	if (!bus->name_notify_enabled) {
		static struct _dbus_filter_condition rule[] = {
			{ L_DBUS_MATCH_TYPE,		"signal" },
			{ L_DBUS_MATCH_SENDER,		DBUS_SERVICE_DBUS },
			{ L_DBUS_MATCH_PATH,		DBUS_PATH_DBUS },
			{ L_DBUS_MATCH_INTERFACE,	L_DBUS_INTERFACE_DBUS },
			{ L_DBUS_MATCH_MEMBER,		"NameOwnerChanged" },
		};

		if (!bus->filter)
			bus->filter = _dbus_filter_new(bus,
						&bus->driver->filter_ops,
						bus->name_cache);

		_dbus_filter_add_rule(bus->filter, rule, L_ARRAY_SIZE(rule),
					name_owner_changed_cb, bus);

		bus->name_notify_enabled = true;
	}

	return true;
}

struct name_request {
	l_dbus_name_acquire_func_t callback;
	void *user_data;
	struct l_dbus *dbus;
};

enum dbus_name_flag {
	DBUS_NAME_FLAG_ALLOW_REPLACEMENT	= 0x1,
	DBUS_NAME_FLAG_REPLACE_EXISTING		= 0x2,
	DBUS_NAME_FLAG_DO_NOT_QUEUE		= 0x4,
};

enum dbus_name_reply {
	DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER	= 1,
	DBUS_REQUEST_NAME_REPLY_IN_QUEUE	= 2,
	DBUS_REQUEST_NAME_REPLY_EXISTS		= 3,
	DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER	= 4,
};

static void request_name_reply_cb(struct l_dbus_message *reply, void *user_data)
{
	struct name_request *req = user_data;
	bool success = false, queued = false;
	uint32_t retval;

	if (!req->callback)
		return;

	/* No name owner yet */
	if (l_dbus_message_is_error(reply))
		goto call_back;

	/* Shouldn't happen */
	if (!l_dbus_message_get_arguments(reply, "u", &retval))
		goto call_back;

	success = (retval == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) ||
		(retval == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER) ||
		(retval == DBUS_REQUEST_NAME_REPLY_IN_QUEUE);
	queued = (retval == DBUS_REQUEST_NAME_REPLY_IN_QUEUE);

call_back:
	req->callback(req->dbus, success, queued, req->user_data);
}

static uint32_t classic_name_acquire(struct l_dbus *dbus, const char *name,
					bool allow_replacement,
					bool replace_existing, bool queue,
					l_dbus_name_acquire_func_t callback,
					void *user_data)
{
	struct name_request *req;
	struct l_dbus_message *message;
	uint32_t flags = 0;

	req = l_new(struct name_request, 1);
	req->dbus = dbus;
	req->user_data = user_data;
	req->callback = callback;

	message = l_dbus_message_new_method_call(dbus, DBUS_SERVICE_DBUS,
							DBUS_PATH_DBUS,
							L_DBUS_INTERFACE_DBUS,
							"RequestName");

	if (allow_replacement)
		flags |= DBUS_NAME_FLAG_ALLOW_REPLACEMENT;

	if (replace_existing)
		flags |= DBUS_NAME_FLAG_REPLACE_EXISTING;

	if (!queue)
		flags |= DBUS_NAME_FLAG_DO_NOT_QUEUE;

	l_dbus_message_set_arguments(message, "su", name, flags);

	return send_message(dbus, false, message, request_name_reply_cb,
				req, free);
}

static const struct l_dbus_ops classic_ops = {
	.version = 1,
	.send_message = classic_send_message,
	.recv_message = classic_recv_message,
	.free = classic_free,
	.name_ops = {
		.get_name_owner = classic_get_name_owner,
	},
	.filter_ops = {
		.add_match = classic_add_match,
		.remove_match = classic_remove_match,
	},
	.name_acquire = classic_name_acquire,
};

static struct l_dbus *setup_dbus1(int fd, const char *guid, bool skip_hello)
{
	static const unsigned char creds = 0x00;
	char uid[6], hexuid[12], *ptr = hexuid;
	struct l_dbus *dbus;
	struct l_dbus_classic *classic;
	ssize_t written;
	unsigned int i;

	if (snprintf(uid, sizeof(uid), "%d", geteuid()) < 1) {
		close(fd);
		return NULL;
	}

	for (i = 0; i < strlen(uid); i++)
		ptr += sprintf(ptr, "%02x", uid[i]);

	/* Send special credentials-passing nul byte */
	written = L_TFR(send(fd, &creds, 1, 0));
	if (written < 1) {
		close(fd);
		return NULL;
	}

	classic = l_new(struct l_dbus_classic, 1);
	dbus = &classic->super;
	dbus->driver = &classic_ops;

	classic->match_strings = l_hashmap_new();

	dbus_init(dbus, fd);
	dbus->guid = l_strdup(guid);

	classic->auth_command = l_strdup_printf("AUTH EXTERNAL %s\r\n", hexuid);
	classic->auth_state = WAITING_FOR_OK;
	classic->skip_hello = skip_hello;

	dbus->negotiate_unix_fd = true;
	dbus->support_unix_fd = false;

	l_io_set_read_handler(dbus->io, auth_read_handler, dbus, NULL);
	l_io_set_write_handler(dbus->io, auth_write_handler, dbus, NULL);

	return dbus;
}

static struct l_dbus *setup_unix(char *params)
{
	char *path = NULL, *guid = NULL;
	bool abstract = false;
	struct sockaddr_un addr;
	size_t len;
	int fd;

	while (params) {
		char *key = strsep(&params, ",");
		char *value;

		if (!key)
			break;

		value = strchr(key, '=');
		if (!value)
			continue;

		*value++ = '\0';

		if (!strcmp(key, "path")) {
			path = value;
			abstract = false;
		} else if (!strcmp(key, "abstract")) {
			path = value;
			abstract = true;
		} else if (!strcmp(key, "guid"))
			guid = value;
	}

	if (!path)
		return NULL;

	fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
	if (fd < 0)
		return NULL;

	memset(&addr, 0, sizeof(addr));
	addr.sun_family = AF_UNIX;

	len = strlen(path);

	if (abstract) {
		if (len > sizeof(addr.sun_path) - 1) {
			close(fd);
			return NULL;
		}

		addr.sun_path[0] = '\0';
		strncpy(addr.sun_path + 1, path, sizeof(addr.sun_path) - 2);
		len++;
	} else {
		if (len > sizeof(addr.sun_path)) {
			close(fd);
			return NULL;
		}

		strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
	}

	if (connect(fd, (struct sockaddr *) &addr,
				sizeof(addr.sun_family) + len) < 0) {
		close(fd);
		return NULL;
	}

	return setup_dbus1(fd, guid, false);
}

static bool setup_tcp_cb(struct l_io *io, void *user_data)
{
	static const unsigned char creds = 0x00;
	struct l_dbus *dbus = user_data;
	struct l_dbus_classic *classic;
	ssize_t written;
	int fd = l_io_get_fd(io);

	/* Send special credentials-passing nul byte */
	written = L_TFR(send(fd, &creds, 1, 0));
	if (written < 1) {
		l_util_debug(dbus->debug_handler, dbus->debug_handler,
						"error writing NUL byte");
		close(fd);
		return false;
	}

	dbus->driver = &classic_ops;
	dbus->negotiate_unix_fd = false;
	dbus->support_unix_fd = false;

	classic = l_container_of(dbus, struct l_dbus_classic, super);
	classic->match_strings = l_hashmap_new();
	classic->auth_command = l_strdup("AUTH ANONYMOUS\r\n");
	classic->auth_state = WAITING_FOR_OK;

	l_io_set_read_handler(dbus->io, auth_read_handler, dbus, NULL);
	l_io_set_write_handler(dbus->io, auth_write_handler, dbus, NULL);

	return auth_write_handler(dbus->io, dbus);
}

static struct l_dbus *setup_tcp(char *params)
{
	char *host = NULL;
	char *port = NULL;
	char *family = NULL;
	struct addrinfo hints = { 0 };
	struct addrinfo *res;
	struct addrinfo *iter;
	struct l_dbus *dbus = NULL;

	while (params) {
		char *key = strsep(&params, ",");
		char *value;

		value = strchr(key, '=');
		if (!value)
			continue;

		*value++ = '\0';

		if (!strcmp(key, "host"))
			host = value;
		else if (!strcmp(key, "port"))
			port = value;
		else if (!strcmp(key, "family"))
			family = value;
	}

	if (!host || !port)
		return NULL;

	if (!family)
		hints.ai_family = AF_UNSPEC;
	else if (!strcmp(family, "ipv4"))
		hints.ai_family = AF_INET;
	else if (!strcmp(family, "ipv6"))
		hints.ai_family = AF_INET6;
	else
		return NULL;

	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
	hints.ai_protocol = IPPROTO_TCP;

	if (getaddrinfo(host, port, &hints, &res) != 0)
		return NULL;

	for (iter = res; iter; iter = iter->ai_next) {
		int fd;
		struct l_dbus_classic *classic;

		fd = socket(iter->ai_family, iter->ai_socktype | SOCK_NONBLOCK,
					iter->ai_protocol);
		if (fd < 0)
			continue;

		if (connect(fd, iter->ai_addr, iter->ai_addrlen) < 0) {
			if (errno != EINPROGRESS) {
				close(fd);
				continue;
			}
		}

		classic = l_new(struct l_dbus_classic, 1);
		dbus = &classic->super;
		dbus_init(dbus, fd);
		l_io_set_write_handler(dbus->io, setup_tcp_cb, dbus, NULL);
		break;
	}

	freeaddrinfo(res);
	return dbus;
}

static struct l_dbus *setup_address(const char *address)
{
	struct l_dbus *dbus = NULL;
	char *address_copy;

	address_copy = strdupa(address);

	while (address_copy) {
		char *transport = strsep(&address_copy, ";");
		char *params;

		if (!transport)
			break;

		params = strchr(transport, ':');
		if (params)
			*params++ = '\0';

		if (!strcmp(transport, "unix")) {
			/* Function will modify params string */
			dbus = setup_unix(params);
			break;
		} else if (!strcmp(transport, "tcp")) {
			dbus = setup_tcp(params);
			break;
		}
	}

	return dbus;
}

LIB_EXPORT struct l_dbus *l_dbus_new(const char *address)
{
	if (unlikely(!address))
		return NULL;

	return setup_address(address);
}

LIB_EXPORT struct l_dbus *l_dbus_new_default(enum l_dbus_bus bus)
{
	const char *address;

	switch (bus) {
	case L_DBUS_SYSTEM_BUS:
		address = getenv("DBUS_SYSTEM_BUS_ADDRESS");
		if (!address)
			address = DEFAULT_SYSTEM_BUS_ADDRESS;
		break;
	case L_DBUS_SESSION_BUS:
		address = getenv("DBUS_SESSION_BUS_ADDRESS");
		if (!address)
			return NULL;
		break;
	default:
		return NULL;
	}

	return setup_address(address);
}

LIB_EXPORT struct l_dbus *l_dbus_new_private(int fd)
{
	return setup_dbus1(fd, NULL, true);
}

LIB_EXPORT void l_dbus_destroy(struct l_dbus *dbus)
{
	if (unlikely(!dbus))
		return;

	if (dbus->ready_destroy)
		dbus->ready_destroy(dbus->ready_data);

	_dbus_filter_free(dbus->filter);

	_dbus_name_cache_free(dbus->name_cache);

	l_hashmap_destroy(dbus->signal_list, signal_list_destroy);
	l_hashmap_destroy(dbus->message_list, message_list_destroy);
	l_queue_destroy(dbus->message_queue, message_queue_destroy);

	l_io_destroy(dbus->io);

	if (dbus->disconnect_destroy)
		dbus->disconnect_destroy(dbus->disconnect_data);

	if (dbus->debug_destroy)
		dbus->debug_destroy(dbus->debug_data);

	l_free(dbus->guid);
	l_free(dbus->unique_name);

	_dbus_object_tree_free(dbus->tree);

	dbus->driver->free(dbus);
}

LIB_EXPORT bool l_dbus_set_ready_handler(struct l_dbus *dbus,
				l_dbus_ready_func_t function,
				void *user_data, l_dbus_destroy_func_t destroy)
{
	if (unlikely(!dbus))
		return false;

	if (dbus->ready_destroy)
		dbus->ready_destroy(dbus->ready_data);

	dbus->ready_handler = function;
	dbus->ready_destroy = destroy;
	dbus->ready_data = user_data;

	return true;
}

LIB_EXPORT bool l_dbus_set_disconnect_handler(struct l_dbus *dbus,
				l_dbus_disconnect_func_t function,
				void *user_data, l_dbus_destroy_func_t destroy)
{
	if (unlikely(!dbus))
		return false;

	if (dbus->disconnect_destroy)
		dbus->disconnect_destroy(dbus->disconnect_data);

	dbus->disconnect_handler = function;
	dbus->disconnect_destroy = destroy;
	dbus->disconnect_data = user_data;

	return true;
}

LIB_EXPORT bool l_dbus_set_debug(struct l_dbus *dbus,
				l_dbus_debug_func_t function,
				void *user_data, l_dbus_destroy_func_t destroy)
{
	if (unlikely(!dbus))
		return false;

	if (dbus->debug_destroy)
		dbus->debug_destroy(dbus->debug_data);

	dbus->debug_handler = function;
	dbus->debug_destroy = destroy;
	dbus->debug_data = user_data;

	/* l_io_set_debug(dbus->io, function, user_data, NULL); */

	return true;
}

LIB_EXPORT uint32_t l_dbus_send_with_reply(struct l_dbus *dbus,
						struct l_dbus_message *message,
						l_dbus_message_func_t function,
						void *user_data,
						l_dbus_destroy_func_t destroy)
{
	if (unlikely(!dbus || !message))
		return 0;

	return send_message(dbus, false, message, function, user_data, destroy);
}

LIB_EXPORT uint32_t l_dbus_send(struct l_dbus *dbus,
				struct l_dbus_message *message)
{
	if (unlikely(!dbus || !message))
		return 0;

	return send_message(dbus, false, message, NULL, NULL, NULL);
}

static bool remove_entry(void *data, void *user_data)
{
	struct message_callback *callback = data;
	uint32_t serial = L_PTR_TO_UINT(user_data);

	if (callback->serial == serial) {
		message_queue_destroy(callback);
		return true;
	}

	return false;
}

LIB_EXPORT bool l_dbus_cancel(struct l_dbus *dbus, uint32_t serial)
{
	struct message_callback *callback;
	unsigned int count;

	if (unlikely(!dbus || !serial))
		return false;

	callback = l_hashmap_remove(dbus->message_list, L_UINT_TO_PTR(serial));
        if (callback) {
		message_queue_destroy(callback);
		return true;
	}

	count = l_queue_foreach_remove(dbus->message_queue, remove_entry,
							L_UINT_TO_PTR(serial));
	if (!count)
		return false;

	return true;
}

LIB_EXPORT unsigned int l_dbus_register(struct l_dbus *dbus,
				l_dbus_message_func_t function,
				void *user_data, l_dbus_destroy_func_t destroy)
{
	struct signal_callback *callback;

	if (unlikely(!dbus))
		return 0;

	callback = l_new(struct signal_callback, 1);

	callback->id = dbus->next_id++;
	callback->callback = function;
	callback->destroy = destroy;
	callback->user_data = user_data;

	l_hashmap_insert(dbus->signal_list,
				L_UINT_TO_PTR(callback->id), callback);

	return callback->id;
}

LIB_EXPORT bool l_dbus_unregister(struct l_dbus *dbus, unsigned int id)
{
	struct signal_callback *callback;

	if (unlikely(!dbus || !id))
		return false;

	callback = l_hashmap_remove(dbus->signal_list, L_UINT_TO_PTR(id));
	if (!callback)
		return false;

	signal_list_destroy(callback);

	return true;
}

LIB_EXPORT uint32_t l_dbus_method_call(struct l_dbus *dbus,
				const char *destination, const char *path,
				const char *interface, const char *method,
				l_dbus_message_func_t setup,
				l_dbus_message_func_t function,
				void *user_data, l_dbus_destroy_func_t destroy)
{
	struct l_dbus_message *message;

	if (unlikely(!dbus))
		return 0;

	message = l_dbus_message_new_method_call(dbus, destination, path,
							interface, method);

	if (setup)
		setup(message, user_data);
	else
		l_dbus_message_set_arguments(message, "");

	return send_message(dbus, false, message, function, user_data, destroy);
}

uint8_t _dbus_get_version(struct l_dbus *dbus)
{
	return dbus->driver->version;
}

int _dbus_get_fd(struct l_dbus *dbus)
{
	return l_io_get_fd(dbus->io);
}

struct _dbus_object_tree *_dbus_get_tree(struct l_dbus *dbus)
{
	return dbus->tree;
}

/**
 * l_dbus_register_interface:
 * @dbus: D-Bus connection as returned by @l_dbus_new*
 * @interface: interface name string
 * @setup_func: function that sets up the methods, signals and properties by
 *              using the #dbus-service.h API.
 * @destroy: optional destructor to be called every time an instance of this
 *           interface is being removed from an object on this bus.
 * @handle_old_style_properties: whether to automatically handle SetProperty and
 *                               GetProperties for any properties registered by
 *                               @setup_func.
 *
 * Registers an interface.  If successful the interface can then be added
 * to any number of objects with @l_dbus_object_add_interface.
 *
 * Returns: whether the interface was successfully registered
 **/
LIB_EXPORT bool l_dbus_register_interface(struct l_dbus *dbus,
				const char *interface,
				l_dbus_interface_setup_func_t setup_func,
				l_dbus_destroy_func_t destroy,
				bool handle_old_style_properties)
{
	if (unlikely(!dbus))
		return false;

	if (unlikely(!dbus->tree))
		return false;

	return _dbus_object_tree_register_interface(dbus->tree, interface,
						setup_func, destroy,
						handle_old_style_properties);
}

LIB_EXPORT bool l_dbus_unregister_interface(struct l_dbus *dbus,
						const char *interface)
{
	if (unlikely(!dbus))
		return false;

	if (unlikely(!dbus->tree))
		return false;

	return _dbus_object_tree_unregister_interface(dbus->tree, interface);
}

/**
 * l_dbus_register_object:
 * @dbus: D-Bus connection
 * @path: new object path
 * @user_data: user pointer to be passed to @destroy if any
 * @destroy: optional destructor to be called when object dropped from the tree
 * @...: NULL-terminated list of 0 or more interfaces to be present on the
 *       object from the moment of creation.  For every interface the interface
 *       name string is expected followed by the @user_data pointer same as
 *       would be passed as @l_dbus_object_add_interface's last two parameters.
 *
 * Create a new D-Bus object on the tree visible to D-Bus peers.  For example:
 * 	success = l_dbus_register_object(bus, "/org/example/ExampleManager",
 * 						NULL, NULL,
 * 						"org.example.Manager",
 * 						manager_data,
 * 						NULL);
 *
 * Returns: whether the object path was successfully registered
 **/
LIB_EXPORT bool l_dbus_register_object(struct l_dbus *dbus, const char *path,
					void *user_data,
					l_dbus_destroy_func_t destroy, ...)
{
	va_list args;
	const char *interface;
	void *if_user_data;
	bool r = true;

	if (unlikely(!dbus))
		return false;

	if (unlikely(!dbus->tree))
		return false;

	if (!_dbus_object_tree_new_object(dbus->tree, path, user_data, destroy))
		return false;

	va_start(args, destroy);
	while ((interface = va_arg(args, const char *))) {
		if_user_data = va_arg(args, void *);

		if (!_dbus_object_tree_add_interface(dbus->tree, path,
							interface,
							if_user_data)) {
			_dbus_object_tree_object_destroy(dbus->tree, path);
			r = false;

			break;
		}
	}
	va_end(args);

	return r;
}

LIB_EXPORT bool l_dbus_unregister_object(struct l_dbus *dbus,
						const char *object)
{
	if (unlikely(!dbus))
		return false;

	if (unlikely(!dbus->tree))
		return false;

	return _dbus_object_tree_object_destroy(dbus->tree, object);
}

/**
 * l_dbus_object_add_interface:
 * @dbus: D-Bus connection
 * @object: object path as passed to @l_dbus_register_object
 * @interface: interface name as passed to @l_dbus_register_interface
 * @user_data: user data pointer to be passed to any method and property
 *             callbacks provided by the @setup_func and to the @destroy
 *             callback as passed to @l_dbus_register_interface
 *
 * Creates an instance of given interface at the given path in the
 * connection's object tree.  If no object was registered at this path
 * before @l_dbus_register_object gets called automatically.
 *
 * The addition of an interface to the object may trigger a query of
 * all the properties on this interface and
 * #org.freedesktop.DBus.ObjectManager.InterfacesAdded signals.
 *
 * Returns: whether the interface was successfully added.
 **/
LIB_EXPORT bool l_dbus_object_add_interface(struct l_dbus *dbus,
						const char *object,
						const char *interface,
						void *user_data)
{
	if (unlikely(!dbus))
		return false;

	if (unlikely(!dbus->tree))
		return false;

	return _dbus_object_tree_add_interface(dbus->tree, object, interface,
						user_data);
}

LIB_EXPORT bool l_dbus_object_remove_interface(struct l_dbus *dbus,
						const char *object,
						const char *interface)
{
	if (unlikely(!dbus))
		return false;

	if (unlikely(!dbus->tree))
		return false;

	return _dbus_object_tree_remove_interface(dbus->tree, object,
							interface);
}

LIB_EXPORT void *l_dbus_object_get_data(struct l_dbus *dbus, const char *object,
					const char *interface)
{
	if (unlikely(!dbus))
		return NULL;

	if (unlikely(!dbus->tree))
		return NULL;

	return _dbus_object_tree_get_interface_data(dbus->tree, object,
							interface);
}

LIB_EXPORT bool l_dbus_object_set_data(struct l_dbus *dbus, const char *object,
				const char *interface, void *user_data)
{
	if (unlikely(!dbus))
		return false;

	if (unlikely(!dbus->tree))
		return false;

	return _dbus_object_tree_set_interface_data(dbus->tree, object,
							interface, user_data);
}

LIB_EXPORT bool l_dbus_object_manager_enable(struct l_dbus *dbus,
						const char *root)
{
	if (unlikely(!dbus))
		return false;

	if (unlikely(!dbus->tree))
		return false;

	return _dbus_object_tree_add_interface(dbus->tree, root,
						L_DBUS_INTERFACE_OBJECT_MANAGER,
						dbus);
}

LIB_EXPORT unsigned int l_dbus_add_disconnect_watch(struct l_dbus *dbus,
					const char *name,
					l_dbus_watch_func_t disconnect_func,
					void *user_data,
					l_dbus_destroy_func_t destroy)
{
	return l_dbus_add_service_watch(dbus, name, NULL, disconnect_func,
					user_data, destroy);
}

LIB_EXPORT unsigned int l_dbus_add_service_watch(struct l_dbus *dbus,
					const char *name,
					l_dbus_watch_func_t connect_func,
					l_dbus_watch_func_t disconnect_func,
					void *user_data,
					l_dbus_destroy_func_t destroy)
{
	if (!name)
		return 0;

	if (!dbus->name_cache)
		dbus->name_cache = _dbus_name_cache_new(dbus,
						&dbus->driver->name_ops);

	return _dbus_name_cache_add_watch(dbus->name_cache, name, connect_func,
						disconnect_func, user_data,
						destroy);
}

LIB_EXPORT bool l_dbus_remove_watch(struct l_dbus *dbus, unsigned int id)
{
	if (!dbus->name_cache)
		return false;

	return _dbus_name_cache_remove_watch(dbus->name_cache, id);
}

/**
 * l_dbus_add_signal_watch:
 * @dbus: D-Bus connection
 * @sender: bus name to match the signal sender against or NULL to
 *          match any sender
 * @path: object path to match the signal path against or NULL to
 *        match any path
 * @interface: interface name to match the signal interface against
 *             or NULL to match any interface
 * @member: name to match the signal name against or NULL to match any
 *          signal
 * @...: a list of further conditions to be met by the signal followed
 *       by three more mandatory parameters:
 *       enum l_dbus_match_type list_end_marker,
 *       l_dbus_message_func callback,
 *       void *user_data,
 *       The value L_DBUS_MATCH_NONE must be passed as the end of list
 *       marker, followed by the signal match callback and user_data.
 *       In the list, every condition is a pair of parameters:
 *       enum l_dbus_match_type match_type, const char *value.
 *
 * Subscribe to a group of signals based on a set of conditions that
 * compare the signal's header fields and string arguments against given
 * values.  For example:
 * 	signal_id = l_dbus_add_signal_watch(bus, "org.example", "/"
 * 						"org.example.Manager",
 * 						"PropertyChanged",
 * 						L_DBUS_MATCH_ARGUMENT(0),
 * 						"ExampleProperty",
 * 						L_DBUS_MATCH_NONE
 * 						manager_property_change_cb,
 * 						NULL);
  *
 * Returns: a non-zero signal filter identifier that can be passed to
 *          l_dbus_remove_signal_watch to remove this filter rule, or
 *          zero on failure.
 **/
LIB_EXPORT unsigned int l_dbus_add_signal_watch(struct l_dbus *dbus,
						const char *sender,
						const char *path,
						const char *interface,
						const char *member, ...)
{
	struct _dbus_filter_condition *rule;
	int rule_len;
	va_list args;
	const char *value;
	l_dbus_message_func_t signal_func;
	enum l_dbus_match_type type;
	void *user_data;
	unsigned int id;

	va_start(args, member);

	rule_len = 0;
	while (true) {
		type = va_arg(args, enum l_dbus_match_type);
		if (type == L_DBUS_MATCH_NONE)
			break;

		va_arg(args, const char *);
		rule_len++;
	}

	va_end(args);

	rule = l_new(struct _dbus_filter_condition, rule_len + 5);

	rule_len = 0;

	rule[rule_len].type = L_DBUS_MATCH_TYPE;
	rule[rule_len++].value = "signal";

	if (sender) {
		rule[rule_len].type = L_DBUS_MATCH_SENDER;
		rule[rule_len++].value = sender;
	}

	if (path) {
		rule[rule_len].type = L_DBUS_MATCH_PATH;
		rule[rule_len++].value = path;
	}

	if (interface) {
		rule[rule_len].type = L_DBUS_MATCH_INTERFACE;
		rule[rule_len++].value = interface;
	}

	if (member) {
		rule[rule_len].type = L_DBUS_MATCH_MEMBER;
		rule[rule_len++].value = member;
	}

	va_start(args, member);

	while (true) {
		type = va_arg(args, enum l_dbus_match_type);
		if (type == L_DBUS_MATCH_NONE)
			break;

		value = va_arg(args, const char *);

		rule[rule_len].type = type;
		rule[rule_len++].value = value;
	}

	signal_func = va_arg(args, l_dbus_message_func_t);
	user_data = va_arg(args, void *);

	va_end(args);

	if (!dbus->filter) {
		if (!dbus->name_cache)
			dbus->name_cache = _dbus_name_cache_new(dbus,
						&dbus->driver->name_ops);

		dbus->filter = _dbus_filter_new(dbus,
						&dbus->driver->filter_ops,
						dbus->name_cache);
	}

	id = _dbus_filter_add_rule(dbus->filter, rule, rule_len,
					signal_func, user_data);

	l_free(rule);

	return id;
}

LIB_EXPORT bool l_dbus_remove_signal_watch(struct l_dbus *dbus, unsigned int id)
{
	if (!dbus->filter)
		return false;

	return _dbus_filter_remove_rule(dbus->filter, id);
}

/**
 * l_dbus_name_acquire:
 * @dbus: D-Bus connection
 * @name: Well-known bus name to be acquired
 * @allow_replacement: Whether to allow another peer's name request to
 *                     take the name ownership away from this connection
 * @replace_existing: Whether to allow D-Bus to take the name's ownership
 *                    away from another peer in case the name is already
 *                    owned and allows replacement.  Ignored if name is
 *                    currently free.
 * @queue: Whether to allow the name request to be queued by D-Bus in
 *         case it cannot be acquired now, rather than to return a failure.
 * @callback: Callback to receive the request result when done.
 *
 * Acquire a well-known bus name (service name) on the bus.
 *
 * Returns: a non-zero request serial that can be passed to l_dbus_cancel
 *          while waiting for the callback or zero if the callback has
 *          has happened while l_dbus_name_acquire was running.
 **/
LIB_EXPORT uint32_t l_dbus_name_acquire(struct l_dbus *dbus, const char *name,
				bool allow_replacement, bool replace_existing,
				bool queue, l_dbus_name_acquire_func_t callback,
				void *user_data)
{
	return dbus->driver->name_acquire(dbus, name, allow_replacement,
						replace_existing, queue,
						callback, user_data);
}
