/*
 * 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 <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <endian.h>
#include <limits.h>

#include "private.h"
#include "useful.h"
#include "util.h"
#include "queue.h"
#include "string.h"
#include "log.h"
#include "dbus.h"
#include "dbus-private.h"
#include "gvariant-private.h"

static const char *simple_types = "sogybnqiuxtdh";
static const char *variable_types = "sogav";
static const char *fixed_types = "bynqhiuxtd";

/*
 * The alignment of a container type is equal to the largest alignment of
 * any potential child of that container. This means that, even if an array
 * of 32-bit integers is empty, it still must be aligned to the nearest
 * multiple of 4 bytes. It also means that the variant type (described below)
 * has an alignment of 8 (since it could potentially contain a value of any
 * other type and the maximum alignment is 8).
 */
static int get_basic_alignment(const char type)
{
	switch (type) {
	case 'b':
		return 1;
	case 'y':
		return 1;
	case 'n':
	case 'q':
		return 2;
	case 'i':
	case 'u':
		return 4;
	case 'x':
	case 't':
	case 'd':
		return 8;
	case 's':
	case 'g':
	case 'o':
		return 1;
	case 'h':
		return 4;
	case 'v':
		return 8;
	default:
		return 0;
	}
}

static int get_basic_fixed_size(const char type)
{
	switch (type) {
	case 'b':
		return 1;
	case 'y':
		return 1;
	case 'n':
	case 'q':
		return 2;
	case 'i':
	case 'u':
		return 4;
	case 'x':
	case 't':
	case 'd':
		return 8;
	case 'h':
		return 4;
	default:
		return 0;
	}
}

static const char *validate_next_type(const char *sig, int *out_alignment)
{
	char s = *sig;
	int alignment;

	if (s == '\0')
		return NULL;

	if (strchr(simple_types, s) || s == 'v') {
		*out_alignment = get_basic_alignment(s);
		return sig + 1;
	}

	switch (s) {
	case 'a':
		return validate_next_type(++sig, out_alignment);

	case '{':
		s = *++sig;

		/* Dictionary keys can only be simple types */
		if (!strchr(simple_types, s))
			return NULL;

		alignment = get_basic_alignment(s);

		sig = validate_next_type(sig + 1, out_alignment);

		if (!sig)
			return NULL;

		if (*sig != '}')
			return NULL;

		if (alignment > *out_alignment)
			*out_alignment = alignment;

		return sig + 1;

	case '(':
	{
		int max_alignment = 1, alignment;

		sig++;

		while (sig && *sig != ')') {
			sig = validate_next_type(sig, &alignment);

			if (alignment > max_alignment)
				max_alignment = alignment;
		}

		if (!sig)
			return NULL;

		if (*sig != ')')
			return NULL;

		*out_alignment = max_alignment;

		return sig + 1;
	}
	}

	return NULL;
}

bool _gvariant_valid_signature(const char *sig)
{
	const char *s = sig;
	int a;

	if (strlen(sig) > 255)
		return false;

	do {
		s = validate_next_type(s, &a);

		if (!s)
			return false;
	} while (*s);

	return true;
}

int _gvariant_num_children(const char *sig)
{
	const char *s = sig;
	int a;
	int num_children = 0;

	if (strlen(sig) > 255)
		return false;

	do {
		s = validate_next_type(s, &a);

		if (!s)
			return -1;

		num_children += 1;
	} while (*s);

	return num_children;
}

int _gvariant_get_alignment(const char *sig)
{
	int max_alignment = 1, alignment;
	const char *s = sig;

	/* 8 is the largest alignment possible, so quit if we reach it */
	while (*s && max_alignment != 8) {
		s = validate_next_type(s, &alignment);
		if (!s)
			return 0;

		if (alignment > max_alignment)
			max_alignment = alignment;
	}

	return max_alignment;
}

bool _gvariant_is_fixed_size(const char *sig)
{
	while (*sig != 0) {
		if (strchr(variable_types, sig[0]))
			return false;

		sig += 1;
	}

	return true;
}

int _gvariant_get_fixed_size(const char *sig)
{
	const char *s = sig;
	const char *p;
	int size = 0;
	int alignment;
	int max_alignment = 1;
	int r;

	while (*s) {
		if (strchr(variable_types, *s))
			return 0;

		if (strchr(fixed_types, *s)) {
			alignment = get_basic_alignment(*s);

			if (alignment > max_alignment)
				max_alignment = alignment;

			size = align_len(size, alignment);
			size += get_basic_fixed_size(*s);
			s++;
			continue;
		}

		if (*s == '}' || *s == ')')
			break;

		p = validate_next_type(s, &alignment);

		if (!p)
			return 0;

		if (alignment > max_alignment)
			max_alignment = alignment;

		size = align_len(size, alignment);

		/* Handle special case of unit type */
		if (s[0] == '(' && s[1] == ')')
			r = 1;
		else
			r = _gvariant_get_fixed_size(s + 1);

		if (r == 0)
			return 0;

		size += r;
		s = p;
	}

	size = align_len(size, max_alignment);

	return size;
}

static inline size_t offset_length(size_t size, size_t n_offsets)
{
	if (size + n_offsets <= 0xff)
		return 1;
	if (size + n_offsets * 2 <= 0xffff)
		return 2;
	if (size + n_offsets * 4 <= 0xffffffff)
		return 4;
	return 8;
}

static inline size_t read_word_le(const void *p, size_t sz) {
	union {
		uint16_t u16;
		uint32_t u32;
		uint64_t u64;
	} x;

	if (sz == 1)
		return *(uint8_t *) p;

	memcpy(&x, p, sz);

	if (sz == 2)
		return le16toh(x.u16);
	if (sz == 4)
		return le32toh(x.u32);
	return le64toh(x.u64);
}

static inline void write_word_le(void *p, size_t value, size_t sz) {
	union {
		uint16_t u16;
		uint32_t u32;
		uint64_t u64;
	} x;

	if (sz == 1) {
		*(uint8_t *) p = value;
		return;
	}

	if (sz == 2)
		x.u16 = htole16((uint16_t) value);
	else if (sz == 4)
		x.u32 = htole32((uint32_t) value);
	else
		x.u64 = htole64((uint64_t) value);

	memcpy(p, &x, sz);
}

static bool gvariant_iter_init_internal(struct l_dbus_message_iter *iter,
					struct l_dbus_message *message,
					enum dbus_container_type type,
					const char *sig_start,
					const char *sig_end, const void *data,
					size_t len)
{
	const char *p;
	int i;
	int v;
	char subsig[256];
	unsigned int num_variable = 0;
	unsigned int offset_len = offset_length(len, 0);
	size_t last_offset;
	struct gvariant_type_info {
		uint8_t sig_start;
		uint8_t sig_end;
		bool fixed_size : 1;
		unsigned int alignment : 4;
		size_t end;		/* Index past the end of the type */
	} *children;
	int n_children;

	if (sig_end) {
		size_t len = sig_end - sig_start;
		memcpy(subsig, sig_start, len);
		subsig[len] = '\0';
	} else
		strcpy(subsig, sig_start);

	iter->message = message;
	iter->sig_start = sig_start;
	iter->sig_len = strlen(subsig);
	iter->sig_pos = 0;
	iter->data = data;
	iter->len = len;
	iter->pos = 0;

	if (subsig[0] != '\0') {
		n_children = _gvariant_num_children(subsig);
		if (n_children < 0)
			return false;

		children = l_new(struct gvariant_type_info, n_children);
	} else {
		n_children = 0;

		children = NULL;
	}

	for (p = sig_start, i = 0; i < n_children; i++) {
		int alignment;
		size_t size;
		size_t len;

		children[i].sig_start = p - sig_start;
		p = validate_next_type(p, &alignment);
		children[i].sig_end = p - sig_start;

		len = children[i].sig_end - children[i].sig_start;
		memcpy(subsig, sig_start + children[i].sig_start, len);
		subsig[len] = '\0';

		children[i].alignment = alignment;
		children[i].fixed_size = _gvariant_is_fixed_size(subsig);

		if (children[i].fixed_size) {
			size = _gvariant_get_fixed_size(subsig);
			children[i].end = size;
		} else if (i + 1 < n_children)
			num_variable += 1;
	}

	if (len < num_variable * offset_len)
		goto fail;

	last_offset = len - num_variable * offset_len;

	if (num_variable > 0)
		iter->offsets = iter->data + len - offset_len;
	else
		iter->offsets = NULL;

	for (i = 0, v = 0; i < n_children; i++) {
		size_t o;

		if (children[i].fixed_size) {
			if (i == 0)
				continue;

			o = align_len(children[i-1].end,
					children[i].alignment);
			children[i].end += o;

			if (children[i].end > len)
				goto fail;

			continue;
		}

		if (num_variable == 0) {
			children[i].end = last_offset;
			continue;
		}

		v += 1;
		children[i].end = read_word_le(data + len - offset_len * v,
						offset_len);
		num_variable -= 1;

		if (children[i].end > len)
			goto fail;
	}

	iter->container_type = type;

	if (type == DBUS_CONTAINER_TYPE_ARRAY &&
			!children[0].fixed_size) {
		size_t offset = read_word_le(iter->data + iter->len -
						offset_len, offset_len);
		iter->offsets = iter->data + offset;
	}

	l_free(children);

	return true;

fail:
	l_free(children);
	return false;
}

bool _gvariant_iter_init(struct l_dbus_message_iter *iter,
				struct l_dbus_message *message,
				const char *sig_start, const char *sig_end,
				const void *data, size_t len)
{
	return gvariant_iter_init_internal(iter, message,
						DBUS_CONTAINER_TYPE_STRUCT,
						sig_start, sig_end, data, len);
}

static const void *next_item(struct l_dbus_message_iter *iter,
							size_t *out_item_size)
{
	const void *start;
	const char *p;
	char sig[256];
	int alignment;
	bool fixed_size;
	bool last_member;
	unsigned int sig_len;
	unsigned int offset_len;

	memcpy(sig, iter->sig_start + iter->sig_pos,
			iter->sig_len - iter->sig_pos);
	sig[iter->sig_len - iter->sig_pos] = '\0';

	/*
	 * Find the next type and make a note whether it is the last in the
	 * structure.  Arrays will always have a single complete type, so
	 * last_member will always be true.
	 */
	p = validate_next_type(sig, &alignment);
	if (!p)
		return NULL;

	sig_len = p - sig;

	last_member = *p == '\0';
	sig[sig_len] = '\0';

	fixed_size = _gvariant_is_fixed_size(sig);

	if (iter->container_type != DBUS_CONTAINER_TYPE_ARRAY)
		iter->sig_pos += sig_len;

	iter->pos = align_len(iter->pos, alignment);

	if (fixed_size) {
		*out_item_size = _gvariant_get_fixed_size(sig);
		goto done;
	}

	if (iter->container_type != DBUS_CONTAINER_TYPE_ARRAY && last_member) {
		unsigned int len = iter->len;

		offset_len = offset_length(iter->len, 0);

		if (iter->offsets && iter->offsets + offset_len <
				iter->data + len)
			len = iter->offsets + offset_len - iter->data;

		*out_item_size = len - iter->pos;
		goto done;
	}

	if (iter->offsets >= iter->data + iter->len)
			return NULL;

	offset_len = offset_length(iter->len, 0);
	*out_item_size = read_word_le(iter->offsets, offset_len) - iter->pos;

	/* In structures the offsets are in reverse order */
	if (iter->container_type == DBUS_CONTAINER_TYPE_ARRAY)
		iter->offsets += offset_len;
	else
		iter->offsets -= offset_len;

done:
	start = iter->data + iter->pos;

	if (start >= iter->data + iter->len)
		return NULL;

	iter->pos += *out_item_size;

	return start;
}

bool _gvariant_iter_next_entry_basic(struct l_dbus_message_iter *iter,
					char type, void *out)
{
	size_t item_size = 0;
	const void *start;
	uint8_t uint8_val;
	uint16_t uint16_val;
	uint32_t uint32_val;
	uint64_t uint64_val;
	int16_t int16_val;
	int32_t int32_val;
	int64_t int64_val;

	if (iter->pos >= iter->len)
		return false;

	if (iter->sig_start[iter->sig_pos] != type)
		return false;

	start = next_item(iter, &item_size);
	if (!start)
		return false;

	switch (type) {
	case 'o':
	case 's':
	case 'g':
	{
		const void *end = memchr(start, 0, item_size);

		if (!end)
			return false;

		*(const char**) out = start;
		break;
	}
	case 'b':
		uint8_val = l_get_u8(start);
		*(bool *) out = !!uint8_val;
		break;
	case 'y':
		uint8_val = l_get_u8(start);
		*(uint8_t *) out = uint8_val;
		break;
	case 'n':
		int16_val = l_get_s16(start);
		*(int16_t *) out = int16_val;
		break;
	case 'q':
		uint16_val = l_get_u16(start);
		*(uint16_t *) out = uint16_val;
		break;
	case 'i':
		int32_val = l_get_s32(start);
		*(int32_t *) out = int32_val;
		break;
	case 'h':
	case 'u':
		uint32_val = l_get_u32(start);
		*(uint32_t *) out = uint32_val;
		break;
	case 'x':
		int64_val = l_get_s64(start);
		*(int64_t *) out = int64_val;
		break;
	case 't':
		uint64_val = l_get_u64(start);
		*(uint64_t *) out = uint64_val;
		break;
	case 'd':
		uint64_val = l_get_u64(start);
		*(uint64_t *) out = uint64_val;
		break;
	}

	return true;
}

bool _gvariant_iter_enter_struct(struct l_dbus_message_iter *iter,
					struct l_dbus_message_iter *structure)
{
	bool is_dict = iter->sig_start[iter->sig_pos] == '{';
	bool is_struct = iter->sig_start[iter->sig_pos] == '(';
	const char *sig_start = iter->sig_start + iter->sig_pos + 1;
	const char *sig_end;
	const void *start;
	size_t item_size;
	enum dbus_container_type type;

	if (!is_dict && !is_struct)
		return false;

	start = next_item(iter, &item_size);
	if (!start)
		return false;

	/* For ARRAY containers the sig_pos is never incremented */
	if (iter->container_type == DBUS_CONTAINER_TYPE_ARRAY)
		sig_end = iter->sig_start + iter->sig_len - 1;
	else
		sig_end = iter->sig_start + iter->sig_pos - 1;

	type = is_dict ? DBUS_CONTAINER_TYPE_DICT_ENTRY :
			DBUS_CONTAINER_TYPE_STRUCT;

	return gvariant_iter_init_internal(structure, iter->message,
						type, sig_start, sig_end,
						start, item_size);
}

bool _gvariant_iter_enter_variant(struct l_dbus_message_iter *iter,
					struct l_dbus_message_iter *variant)
{
	size_t item_size;
	const void *start, *end, *nul;
	char signature[256];

	if (iter->sig_start[iter->sig_pos] != 'v')
		return false;

	start = next_item(iter, &item_size);
	if (!start)
		return false;

	/* Find the signature */
	end = start + item_size;
	nul = memrchr(start, 0, end - start);

	if (!nul)
		return false;

	if (end - nul - 1 > 255)
		return false;

	memcpy(signature, nul + 1, end - nul - 1);
	signature[end - nul - 1] = '\0';

	if (_gvariant_num_children(signature) != 1)
		return false;

	return gvariant_iter_init_internal(variant, iter->message,
						DBUS_CONTAINER_TYPE_VARIANT,
						nul + 1, end,
						start, nul - start);
}

bool _gvariant_iter_enter_array(struct l_dbus_message_iter *iter,
					struct l_dbus_message_iter *array)
{
	const char *sig_start;
	const char *sig_end;
	size_t item_size;
	const void *start;

	if (iter->sig_start[iter->sig_pos] != 'a')
		return false;

	sig_start = iter->sig_start + iter->sig_pos + 1;

	start = next_item(iter, &item_size);
	if (!start)
		return false;

	/* For ARRAY containers the sig_pos is never incremented */
	if (iter->container_type == DBUS_CONTAINER_TYPE_ARRAY)
		sig_end = iter->sig_start + iter->sig_len;
	else
		sig_end = iter->sig_start + iter->sig_pos;

	return gvariant_iter_init_internal(array, iter->message,
						DBUS_CONTAINER_TYPE_ARRAY,
						sig_start, sig_end,
						start, item_size);
}

bool _gvariant_iter_skip_entry(struct l_dbus_message_iter *iter)
{
	size_t size;

	if (!next_item(iter, &size))
		return false;

	return true;
}

struct dbus_builder {
	struct l_string *signature;
	void *body;
	size_t body_size;
	size_t body_pos;
	struct l_queue *containers;
	struct {
		struct container *container;
		int sig_end;
		size_t body_pos;
		size_t offset_index;
		bool variable_is_last : 1;
	} mark;
};

struct container {
	size_t *offsets;
	size_t offsets_size;
	size_t offset_index;
	size_t start;
	bool variable_is_last : 1;
	enum dbus_container_type type;
	char signature[256];
	uint8_t sigindex;
};

static inline size_t grow_body(struct dbus_builder *builder,
					size_t len, unsigned int alignment)
{
	size_t size = align_len(builder->body_pos, alignment);

	if (size + len > builder->body_size) {
		builder->body = l_realloc(builder->body, size + len);
		builder->body_size = size + len;
	}

	if (size - builder->body_pos > 0)
		memset(builder->body + builder->body_pos, 0,
			size - builder->body_pos);

	builder->body_pos = size + len;

	return size;
}

static inline bool grow_offsets(struct container *container)
{
	size_t needed;

	if (container->offset_index < container->offsets_size)
		return true;

	needed = container->offsets_size * 2;

	if (needed > USHRT_MAX)
		return false;

	if (needed == 0)
		needed = 8;

	container->offsets = l_realloc(container->offsets,
						needed * sizeof(size_t));
	container->offsets_size = needed;

	return true;
}

static struct container *container_new(enum dbus_container_type type,
					const char *signature, size_t start)
{
	struct container *ret;

	ret = l_new(struct container, 1);

	ret->type = type;
	strcpy(ret->signature, signature);
	ret->start = start;

	return ret;
}

static void container_free(struct container *container)
{
	l_free(container->offsets);
	l_free(container);
}

static void container_append_struct_offsets(struct container *container,
					struct dbus_builder *builder)
{
	size_t offset_size;
	int i;
	size_t start;

	if (container->variable_is_last)
		container->offset_index -= 1;

	if (container->offset_index == 0)
		return;

	offset_size = offset_length(builder->body_pos,
						container->offset_index);

	start = grow_body(builder, offset_size * container->offset_index, 1);

	for (i = container->offset_index - 1; i >= 0; i--) {
		write_word_le(builder->body + start,
				container->offsets[i], offset_size);
		start += offset_size;
	}
}

static void container_append_array_offsets(struct container *container,
					struct dbus_builder *builder)
{
	size_t offset_size;
	unsigned int i;
	size_t start;

	if (container->offset_index == 0)
		return;

	offset_size = offset_length(builder->body_pos,
						container->offset_index);
	start = grow_body(builder, offset_size * container->offset_index, 1);

	for (i = 0; i < container->offset_index; i++) {
		write_word_le(builder->body + start,
				container->offsets[i], offset_size);
		start += offset_size;
	}
}

struct dbus_builder *_gvariant_builder_new(void *body, size_t body_size)
{
	struct dbus_builder *builder;
	struct container *root;

	builder = l_new(struct dbus_builder, 1);
	builder->signature = l_string_new(63);

	builder->containers = l_queue_new();
	root = container_new(DBUS_CONTAINER_TYPE_STRUCT, "", 0);
	l_queue_push_head(builder->containers, root);

	builder->body = body;
	builder->body_size = body_size;
	builder->body_pos = body_size;

	builder->mark.container = root;

	return builder;
}

void _gvariant_builder_free(struct dbus_builder *builder)
{
	if (unlikely(!builder))
		return;

	l_string_free(builder->signature);
	l_queue_destroy(builder->containers,
				(l_queue_destroy_func_t) container_free);
	l_free(builder->body);

	l_free(builder);
}

static bool enter_struct_dict_common(struct dbus_builder *builder,
					const char *signature,
					enum dbus_container_type type,
					const char open,
					const char close)
{
	size_t qlen = l_queue_length(builder->containers);
	struct container *container = l_queue_peek_head(builder->containers);
	int alignment;
	size_t start;

	if (qlen == 1) {
		if (l_string_length(builder->signature) +
				strlen(signature) + 2 > 255)
			return false;
	} else {
		/* Verify Signatures Match */
		char expect[256];
		const char *start;
		const char *end;

		start = container->signature + container->sigindex;
		end = validate_next_type(start, &alignment) - 1;

		if (*start != open || *end != close)
			return false;

		memcpy(expect, start + 1, end - start - 1);
		expect[end - start - 1] = '\0';

		if (strcmp(expect, signature))
			return false;
	}

	alignment = _gvariant_get_alignment(signature);
	start = grow_body(builder, 0, alignment);

	container = container_new(type, signature, start);
	l_queue_push_head(builder->containers, container);

	return true;
}

bool _gvariant_builder_enter_struct(struct dbus_builder *builder,
					const char *signature)
{
	if (signature[0] && !_gvariant_valid_signature(signature))
		return false;

	return enter_struct_dict_common(builder, signature,
					DBUS_CONTAINER_TYPE_STRUCT, '(', ')');
}

bool _gvariant_builder_enter_dict(struct dbus_builder *builder,
					const char *signature)
{
	if (_gvariant_num_children(signature) != 2)
		return false;

	if (!strchr(simple_types, signature[0]))
		return false;

	return enter_struct_dict_common(builder, signature,
					DBUS_CONTAINER_TYPE_DICT_ENTRY,
					'{', '}');
}

static bool leave_struct_dict_common(struct dbus_builder *builder,
					enum dbus_container_type type,
					const char open,
					const char close)
{
	struct container *container = l_queue_peek_head(builder->containers);
	size_t qlen = l_queue_length(builder->containers);
	struct container *parent;

	if (unlikely(qlen <= 1))
		return false;

	if (unlikely(container->type != type))
		return false;

	l_queue_pop_head(builder->containers);
	qlen -= 1;
	parent = l_queue_peek_head(builder->containers);

	if (_gvariant_is_fixed_size(container->signature)) {
		int alignment = _gvariant_get_alignment(container->signature);
		grow_body(builder, 0, alignment);

		/* Empty struct or "unit type" is encoded as a zero byte */
		if (container->signature[0] == '\0') {
			size_t start = grow_body(builder, 1, 1);

			memset(builder->body + start, 0, 1);
		}

		parent->variable_is_last = false;
	} else {
		size_t offset;

		if (!grow_offsets(parent))
			return false;

		container_append_struct_offsets(container, builder);
		offset = builder->body_pos - parent->start;
		parent->offsets[parent->offset_index++] = offset;
		parent->variable_is_last = true;
	}

	if (qlen == 1)
		l_string_append_printf(builder->signature, "%c%s%c",
						open,
						container->signature,
						close);
	else if (parent->type != DBUS_CONTAINER_TYPE_ARRAY)
		parent->sigindex += strlen(container->signature) + 2;

	container_free(container);

	return true;
}

bool _gvariant_builder_leave_struct(struct dbus_builder *builder)
{
	return leave_struct_dict_common(builder, DBUS_CONTAINER_TYPE_STRUCT,
					'(', ')');
}

bool _gvariant_builder_leave_dict(struct dbus_builder *builder)
{
	return leave_struct_dict_common(builder,
					DBUS_CONTAINER_TYPE_DICT_ENTRY,
					'{', '}');
}

bool _gvariant_builder_enter_variant(struct dbus_builder *builder,
					const char *signature)
{
	size_t qlen = l_queue_length(builder->containers);
	struct container *container = l_queue_peek_head(builder->containers);
	size_t start;

	if (_gvariant_num_children(signature) != 1)
		return false;

	if (qlen == 1) {
		if (l_string_length(builder->signature) + 1 > 255)
			return false;
	} else if (container->signature[container->sigindex] != 'v')
		return false;

	start = grow_body(builder, 0, 8);

	container = container_new(DBUS_CONTAINER_TYPE_VARIANT,
					signature, start);
	l_queue_push_head(builder->containers, container);

	return true;
}

bool _gvariant_builder_leave_variant(struct dbus_builder *builder)
{
	struct container *container = l_queue_peek_head(builder->containers);
	size_t qlen = l_queue_length(builder->containers);
	struct container *parent;
	size_t start;
	size_t siglen;
	size_t offset;

	if (unlikely(qlen <= 1))
		return false;

	if (unlikely(container->type != DBUS_CONTAINER_TYPE_VARIANT))
		return false;

	l_queue_pop_head(builder->containers);
	qlen -= 1;
	parent = l_queue_peek_head(builder->containers);

	siglen = strlen(container->signature);
	start = grow_body(builder, siglen + 1, 1);
	memset(builder->body + start, 0, 1);
	memcpy(builder->body + start + 1, container->signature, siglen);

	if (!grow_offsets(parent))
		return false;

	offset = builder->body_pos - parent->start;
	parent->offsets[parent->offset_index++] = offset;
	parent->variable_is_last = true;

	if (qlen == 1)
		l_string_append_c(builder->signature, 'v');
	else if (parent->type != DBUS_CONTAINER_TYPE_ARRAY)
		parent->sigindex += 1;

	container_free(container);

	return true;
}

bool _gvariant_builder_enter_array(struct dbus_builder *builder,
					const char *signature)
{
	size_t qlen = l_queue_length(builder->containers);
	struct container *container = l_queue_peek_head(builder->containers);
	size_t start;
	int alignment;

	if (_gvariant_num_children(signature) != 1)
		return false;

	if (qlen == 1) {
		if (l_string_length(builder->signature) +
				strlen(signature) + 1 > 255)
			return false;
	} else {
		/* Verify Signatures Match */
		char expect[256];
		const char *start;
		const char *end;

		start = container->signature + container->sigindex;
		end = validate_next_type(start, &alignment);

		if (*start != 'a')
			return false;

		memcpy(expect, start + 1, end - start - 1);
		expect[end - start - 1] = '\0';

		if (strcmp(expect, signature))
			return false;
	}

	alignment = _gvariant_get_alignment(signature);
	start = grow_body(builder, 0, alignment);

	container = container_new(DBUS_CONTAINER_TYPE_ARRAY, signature, start);
	l_queue_push_head(builder->containers, container);

	return true;
}

bool _gvariant_builder_leave_array(struct dbus_builder *builder)
{
	struct container *container = l_queue_peek_head(builder->containers);
	size_t qlen = l_queue_length(builder->containers);
	struct container *parent;
	size_t offset;

	if (unlikely(qlen <= 1))
		return false;

	if (unlikely(container->type != DBUS_CONTAINER_TYPE_ARRAY))
		return false;

	l_queue_pop_head(builder->containers);
	qlen -= 1;
	parent = l_queue_peek_head(builder->containers);

	if (!_gvariant_is_fixed_size(container->signature))
		container_append_array_offsets(container, builder);

	if (!grow_offsets(parent))
		return false;

	offset = builder->body_pos - parent->start;
	parent->offsets[parent->offset_index++] = offset;
	parent->variable_is_last = true;

	if (qlen == 1)
		l_string_append_printf(builder->signature, "a%s",
							container->signature);
	else if (parent->type != DBUS_CONTAINER_TYPE_ARRAY)
		parent->sigindex += strlen(container->signature) + 1;

	container_free(container);

	return true;
}

bool _gvariant_builder_append_basic(struct dbus_builder *builder,
					char type, const void *value)
{
	struct container *container = l_queue_peek_head(builder->containers);
	size_t start;
	unsigned int alignment;
	size_t len;
	size_t offset;

	if (unlikely(!builder))
		return false;

	if (unlikely(!strchr(simple_types, type)))
		return false;

	alignment = get_basic_alignment(type);
	if (!alignment)
		return false;

	if (l_queue_length(builder->containers) == 1)
		l_string_append_c(builder->signature, type);
	else if (container->signature[container->sigindex] != type)
		return false;

	len = get_basic_fixed_size(type);

	if (len) {
		start = grow_body(builder, len, alignment);
		memcpy(builder->body + start, value, len);
		container->variable_is_last = false;

		if (container->type != DBUS_CONTAINER_TYPE_ARRAY)
			container->sigindex += 1;

		return true;
	}

	if (!grow_offsets(container))
		return false;

	len = strlen(value) + 1;
	start = grow_body(builder, len, alignment);
	memcpy(builder->body + start, value, len);

	offset = builder->body_pos - container->start;
	container->offsets[container->offset_index++] = offset;
	container->variable_is_last = true;

	if (container->type != DBUS_CONTAINER_TYPE_ARRAY)
		container->sigindex += 1;

	return true;
}

bool _gvariant_builder_mark(struct dbus_builder *builder)
{
	struct container *container = l_queue_peek_head(builder->containers);

	builder->mark.container = container;

	if (l_queue_length(builder->containers) == 1)
		builder->mark.sig_end = l_string_length(builder->signature);
	else
		builder->mark.sig_end = container->sigindex;

	builder->mark.body_pos = builder->body_pos;
	builder->mark.offset_index = container->offset_index;
	builder->mark.variable_is_last = container->variable_is_last;

	return true;
}

bool _gvariant_builder_rewind(struct dbus_builder *builder)
{
	struct container *container;

	while ((container = l_queue_peek_head(builder->containers)) !=
				builder->mark.container) {
		container_free(container);
		l_queue_pop_head(builder->containers);
	}

	builder->body_pos = builder->mark.body_pos;
	container->offset_index = builder->mark.offset_index;
	container->variable_is_last = builder->mark.variable_is_last;

	if (l_queue_length(builder->containers) == 1)
		l_string_truncate(builder->signature, builder->mark.sig_end);
	else
		container->sigindex = builder->mark.sig_end;

	return true;
}

char *_gvariant_builder_finish(struct dbus_builder *builder,
				void **body, size_t *body_size)
{
	char *signature;
	struct container *root;
	uint8_t *variant_buf;
	size_t size;

	if (unlikely(!builder))
		return NULL;

	if (unlikely(l_queue_length(builder->containers) != 1))
		return NULL;

	root = l_queue_peek_head(builder->containers);

	signature = l_string_unwrap(builder->signature);
	builder->signature = NULL;

	if (_gvariant_is_fixed_size(signature)) {
		int alignment = _gvariant_get_alignment(signature);
		grow_body(builder, 0, alignment);

		/* Empty struct or "unit type" is encoded as a zero byte */
		if (signature[0] == '\0') {
			size_t start = grow_body(builder, 1, 1);

			memset(builder->body + start, 0, 1);
		}
	} else
		container_append_struct_offsets(root, builder);

	/*
	 * Make sure there's enough space after the body for the variant
	 * signature written here but not included in the body size and
	 * one framing offset value to be written in
	 * _gvariant_message_finalize.
	 */
	size = 3 + strlen(signature) + 8;
	if (builder->body_pos + size > builder->body_size)
		builder->body = l_realloc(builder->body,
						builder->body_pos + size);

	variant_buf = builder->body + builder->body_pos;
	*variant_buf++ = 0;
	*variant_buf++ = '(';
	variant_buf = mempcpy(variant_buf, signature, strlen(signature));
	*variant_buf++ = ')';

	*body = builder->body;
	*body_size = builder->body_pos;
	builder->body = NULL;
	builder->body_size = 0;

	return signature;
}

/*
 * Write the header's framing offset after the body variant which is the
 * last piece of data in the message after the header, the padding and
 * the builder has written the message body.
 */
size_t _gvariant_message_finalize(size_t header_end,
					void *body, size_t body_size,
					const char *signature)
{
	size_t offset_start;
	size_t offset_size;

	offset_start = body_size + 3 + strlen(signature);

	offset_size = offset_length(align_len(header_end, 8) + offset_start, 1);

	write_word_le(body + offset_start, header_end, offset_size);

	return align_len(header_end, 8) + offset_start + offset_size;
}
