/* Copyright (c) Pigeonhole authors, see top-level COPYING file */

#include "lib.h"
#include "str.h"
#include "str-sanitize.h"
#include "mempool.h"
#include "buffer.h"
#include "hash.h"
#include "array.h"
#include "ostream.h"

#include "sieve-common.h"
#include "sieve-error.h"
#include "sieve-extensions.h"
#include "sieve-code.h"
#include "sieve-script.h"

#include "sieve-binary-private.h"

/*
 * Forward declarations
 */

static inline sieve_size_t
sieve_binary_emit_dynamic_data(struct sieve_binary_block *sblock,
			       const void *data, size_t size);

/*
 * Emission functions
 */

/* Low-level emission functions */

static inline void
_sieve_binary_emit_data(struct sieve_binary_block *sblock,
			const void *data, sieve_size_t size)
{
	buffer_append(sblock->data, data, size);
}

static inline void
_sieve_binary_emit_byte(struct sieve_binary_block *sblock, uint8_t byte)
{
	_sieve_binary_emit_data(sblock, &byte, 1);
}

static inline void
_sieve_binary_update_data(struct sieve_binary_block *sblock,
			  sieve_size_t address, const void *data,
			  sieve_size_t size)
{
	buffer_write(sblock->data, address, data, size);
}

sieve_size_t sieve_binary_emit_data(struct sieve_binary_block *sblock,
				    const void *data, sieve_size_t size)
{
	sieve_size_t address = _sieve_binary_block_get_size(sblock);

	_sieve_binary_emit_data(sblock, data, size);

	return address;
}

sieve_size_t sieve_binary_emit_byte(struct sieve_binary_block *sblock,
				    uint8_t byte)
{
	sieve_size_t address = _sieve_binary_block_get_size(sblock);

	_sieve_binary_emit_data(sblock, &byte, 1);

	return address;
}

void sieve_binary_update_data(struct sieve_binary_block *sblock,
			      sieve_size_t address, const void *data,
			      sieve_size_t size)
{
	_sieve_binary_update_data(sblock, address, data, size);
}

/* Offset emission functions */

sieve_size_t sieve_binary_emit_offset(struct sieve_binary_block *sblock,
				      sieve_offset_t offset)
{
	sieve_size_t address = _sieve_binary_block_get_size(sblock);
	uint8_t encoded[sizeof(offset)];
	int i;

	for (i = sizeof(offset)-1; i >= 0; i--) {
		encoded[i] = (uint8_t)offset;
		offset >>= 8;
	}

	_sieve_binary_emit_data(sblock, encoded, sizeof(offset));

	return address;
}

void sieve_binary_resolve_offset(struct sieve_binary_block *sblock,
				 sieve_size_t address)
{
	sieve_size_t cur_address = _sieve_binary_block_get_size(sblock);
	sieve_offset_t offset;
	uint8_t encoded[sizeof(offset)];
	int i;

	i_assert(cur_address > address);
	i_assert((cur_address - address) <= (sieve_offset_t)-1);
	offset = cur_address - address;
	for (i = sizeof(offset)-1; i >= 0; i--) {
		encoded[i] = (uint8_t)offset;
		offset >>= 8;
	}

	_sieve_binary_update_data(sblock, address, encoded, sizeof(offset));
}

/* Literal emission */

sieve_size_t sieve_binary_emit_integer(struct sieve_binary_block *sblock,
				       sieve_number_t integer)
{
	sieve_size_t address = _sieve_binary_block_get_size(sblock);
	uint8_t buffer[sizeof(sieve_number_t) * 8 / 7 + 1];
	int bufpos = sizeof(buffer) - 1;

	/* Encode last byte [0xxxxxxx]; msb == 0 marks the last byte */
	buffer[bufpos] = integer & 0x7F;
	bufpos--;

	/* Encode first bytes [1xxxxxxx] */
	integer >>= 7;
	while (integer > 0) {
		buffer[bufpos] = (integer & 0x7F) | 0x80;
		bufpos--;
		integer >>= 7;
	}

	/* Emit encoded integer */
	bufpos++;
	_sieve_binary_emit_data(sblock, buffer + bufpos, sizeof(buffer) - bufpos);

	return address;
}

static inline sieve_size_t
sieve_binary_emit_dynamic_data(struct sieve_binary_block *sblock,
			       const void *data, sieve_size_t size)
{
	sieve_size_t address =
		sieve_binary_emit_integer(sblock, (sieve_number_t)size);

	_sieve_binary_emit_data(sblock, data, size);

	return address;
}

sieve_size_t sieve_binary_emit_cstring(struct sieve_binary_block *sblock,
				       const char *str)
{
	sieve_size_t address =
		sieve_binary_emit_dynamic_data(sblock, str,
					       (sieve_size_t)strlen(str));

	_sieve_binary_emit_byte(sblock, 0);
	return address;
}

sieve_size_t sieve_binary_emit_string(struct sieve_binary_block *sblock,
				      const string_t *str)
{
	sieve_size_t address =
		sieve_binary_emit_dynamic_data(sblock, str_data(str),
					       (sieve_size_t)str_len(str));

	_sieve_binary_emit_byte(sblock, 0);
	return address;
}

/*
 * Extension emission
 */

sieve_size_t sieve_binary_emit_extension(struct sieve_binary_block *sblock,
					 const struct sieve_extension *ext,
					 unsigned int offset)
{
	sieve_size_t address = _sieve_binary_block_get_size(sblock);
	struct sieve_binary_extension_reg *ereg = NULL;

	(void)sieve_binary_extension_register(sblock->sbin, ext, &ereg);

	i_assert(ereg != NULL);

	_sieve_binary_emit_byte(sblock, offset + ereg->index);
	return address;
}

void sieve_binary_emit_extension_object(
	struct sieve_binary_block *sblock,
	const struct sieve_extension_objects *objs, unsigned int code)
{
	if (objs->count > 1)
		_sieve_binary_emit_byte(sblock, code);
}

/*
 * Code retrieval
 */

#define ADDR_CODE_READ(block) \
	size_t _code_size; \
	const int8_t *_code = buffer_get_data((block)->data, &_code_size)

#define ADDR_CODE_AT(address) \
	((int8_t)(_code[*address]))
#define ADDR_DATA_AT(address) \
	((uint8_t)(_code[*address]))
#define ADDR_POINTER(address) \
	((const int8_t *)(&_code[*address]))

#define ADDR_BYTES_LEFT(address) \
	((*address) > _code_size ? 0 : ((_code_size) - (*address)))
#define ADDR_JUMP(address, offset) \
	(*address) += offset

/* Literals */

bool sieve_binary_read_byte(struct sieve_binary_block *sblock,
			    sieve_size_t *address, unsigned int *byte_r)
{
	ADDR_CODE_READ(sblock);

	if (ADDR_BYTES_LEFT(address) >= 1) {
		if (byte_r != NULL)
			*byte_r = ADDR_DATA_AT(address);
		ADDR_JUMP(address, 1);

		return TRUE;
	}

	if (byte_r != NULL)
		*byte_r = 0;
	return FALSE;
}

bool sieve_binary_read_code(struct sieve_binary_block *sblock,
			    sieve_size_t *address, signed int *code_r)
{
	ADDR_CODE_READ(sblock);

	if (ADDR_BYTES_LEFT(address) >= 1) {
		if (code_r != NULL)
			*code_r = ADDR_CODE_AT(address);
		ADDR_JUMP(address, 1);

		return TRUE;
	}

	if (code_r != NULL)
		*code_r = 0;
	return FALSE;
}


bool sieve_binary_read_offset(struct sieve_binary_block *sblock,
			      sieve_size_t *address, sieve_offset_t *offset_r)
{
	sieve_offset_t offs = 0;
	ADDR_CODE_READ(sblock);

	if (ADDR_BYTES_LEFT(address) >= 4) {
		int i;

		for (i = 0; i < 4; i++) {
			offs = (offs << 8) + ADDR_DATA_AT(address);
			ADDR_JUMP(address, 1);
		}

		if (offset_r != NULL)
			*offset_r = offs;

		return TRUE;
	}
	return FALSE;
}

/* FIXME: might need negative numbers in the future */
bool sieve_binary_read_integer(struct sieve_binary_block *sblock,
			       sieve_size_t *address, sieve_number_t *int_r)
{
	int bits = sizeof(sieve_number_t) * 8;
	sieve_number_t integer = 0;

	ADDR_CODE_READ(sblock);

	if (ADDR_BYTES_LEFT(address) == 0)
		return FALSE;

	/* Read first integer bytes [1xxxxxxx] */
	while ((ADDR_DATA_AT(address) & 0x80) > 0) {
		if (ADDR_BYTES_LEFT(address) > 0 && bits > 0) {
			integer |= ADDR_DATA_AT(address) & 0x7F;
			ADDR_JUMP(address, 1);

			/* Each byte encodes 7 bits of the integer */
			integer <<= 7;
			bits -= 7;
		} else {
			/* This is an error */
			return FALSE;
		}
	}

	/* Read last byte [0xxxxxxx] */
	integer |= ADDR_DATA_AT(address) & 0x7F;
	ADDR_JUMP(address, 1);

	if (int_r != NULL)
		*int_r = integer;
	return TRUE;
}

bool sieve_binary_read_string(struct sieve_binary_block *sblock,
			      sieve_size_t *address, string_t **str_r)
{
	unsigned int strlen = 0;
	const char *strdata;

	ADDR_CODE_READ(sblock);

	if (!sieve_binary_read_unsigned(sblock, address, &strlen))
		return FALSE;

	if (strlen > ADDR_BYTES_LEFT(address))
		return FALSE;

	strdata = (const char *)ADDR_POINTER(address);
	ADDR_JUMP(address, strlen);

	if (ADDR_CODE_AT(address) != 0)
		return FALSE;

 	if (str_r != NULL)
		*str_r = t_str_new_const(strdata, strlen);

	ADDR_JUMP(address, 1);

	return TRUE;
}

bool sieve_binary_read_extension(struct sieve_binary_block *sblock,
				 sieve_size_t *address, unsigned int *offset_r,
				 const struct sieve_extension **ext_r)
{
	unsigned int code;
	unsigned int offset = *offset_r;
	const struct sieve_extension *ext = NULL;

	ADDR_CODE_READ(sblock);

	if (ADDR_BYTES_LEFT(address) == 0)
		return FALSE;

	*offset_r = code = ADDR_DATA_AT(address);
	ADDR_JUMP(address, 1);

	if (code >= offset) {
		ext = sieve_binary_extension_get_by_index(sblock->sbin,
							  (code - offset));
		if (ext == NULL)
			return FALSE;
	}

	if (ext_r != NULL)
		*ext_r = ext;
	return TRUE;
}

const void *
sieve_binary_read_extension_object(struct sieve_binary_block *sblock,
				   sieve_size_t *address,
				   const struct sieve_extension_objects *objs)
{
	unsigned int code;

	ADDR_CODE_READ(sblock);

	if (objs->count == 0)
		return NULL;
	if (objs->count == 1)
		return objs->objects;
	if (ADDR_BYTES_LEFT(address) == 0)
		return NULL;

	code = ADDR_DATA_AT(address);
	ADDR_JUMP(address, 1);

	if (code >= objs->count)
		return NULL;
	return ((const void *const *)objs->objects)[code];
}
