// -*- mode: c; tab-width: 8; indent-tabs-mode: 1; st-rulers: [70] -*-
// vim: ts=8 sw=8 ft=c noet

#include "libsodium_api_utils.h"

static int	LS_API_INIT(utils, compare);
static void	LS_API_EXEC(utils, compare);
static int	LS_API_INIT(utils, is_zero);
static void	LS_API_EXEC(utils, is_zero);
static int	LS_API_INIT(utils, increment);
static void	LS_API_EXEC(utils, increment);
static int	LS_API_INIT(utils, add);
static void	LS_API_EXEC(utils, add);
static int	LS_API_INIT(utils, bin2hex);
static void	LS_API_EXEC(utils, bin2hex);
static int	LS_API_INIT(utils, hex2bin);
static void	LS_API_EXEC(utils, hex2bin);

libsodium_function_t	libsodium_functions_utils[] = {
	LS_API_R_ARGV(utils, compare, 2),
	LS_API_R_ARGV(utils, is_zero, 1),
	LS_API_R_ARGV(utils, increment, 1),
	LS_API_R_ARGV(utils, add, 2),
	LS_API_R_ARGV(utils, bin2hex, 1),
	LS_API_R_ARGV(utils, hex2bin, 2),
	{NULL}
};

/* utils_compare/2 */

typedef struct LS_API_F_ARGV(utils, compare) {
	const void	*b1;
	const void	*b2;
	size_t		len;
} LS_API_F_ARGV_T(utils, compare);

static int
LS_API_INIT(utils, compare)
{
	LS_API_F_ARGV_T(utils, compare) *argv;
	int skip;
	int type;
	int type_length;
	size_t len;
	ErlDrvSizeT x;
	void *p;

	if (ei_get_type(buffer, index, &type, &type_length) < 0
			|| type != ERL_BINARY_EXT) {
		return -1;
	}

	len = (size_t)(type_length);

	skip = *index;

	if (ei_skip_term(buffer, &skip) < 0) {
		return -1;
	}

	if (ei_get_type(buffer, &skip, &type, &type_length) < 0
			|| type != ERL_BINARY_EXT
			|| type_length != len) {
		return -1;
	}

	x = (ErlDrvSizeT)(len + len + (sizeof (LS_API_F_ARGV_T(utils, compare))));
	p = (void *)(driver_alloc(x));

	if (p == NULL) {
		return -1;
	}

	argv = (LS_API_F_ARGV_T(utils, compare) *)(p);
	p += (sizeof (LS_API_F_ARGV_T(utils, compare)));
	argv->b1 = (const unsigned char *)(p);
	p += len;
	argv->b2 = (const unsigned char *)(p);
	argv->len = len;

	if (ei_decode_binary(buffer, index, (void *)(argv->b1), NULL) < 0) {
		(void) driver_free(argv);
		return -1;
	}

	if (ei_decode_binary(buffer, index, (void *)(argv->b2), NULL) < 0) {
		(void) driver_free(argv);
		return -1;
	}

	request->argv = (void *)(argv);

	return 0;
}

static void
LS_API_EXEC(utils, compare)
{
	LS_API_F_ARGV_T(utils, compare) *argv;
	LS_API_READ_ARGV(utils, compare);
	int r;

	r = sodium_compare(argv->b1, argv->b2, argv->len);

	ErlDrvTermData spec[] = {
		LS_RES_TAG(request),
		ERL_DRV_INT, (ErlDrvSInt)(r),
		ERL_DRV_TUPLE, 2
	};

	LS_RESPOND(request, spec, __FILE__, __LINE__);
}

/* utils_is_zero/1 */

typedef struct LS_API_F_ARGV(utils, is_zero) {
	const unsigned char	*n;
	size_t			nlen;
} LS_API_F_ARGV_T(utils, is_zero);

static int
LS_API_INIT(utils, is_zero)
{
	LS_API_F_ARGV_T(utils, is_zero) *argv;
	int type;
	int type_length;
	ErlDrvSizeT x;
	void *p;

	if (ei_get_type(buffer, index, &type, &type_length) < 0
			|| type != ERL_BINARY_EXT) {
		return -1;
	}

	x = (ErlDrvSizeT)(type_length + (sizeof (LS_API_F_ARGV_T(utils, is_zero))));
	p = (void *)(driver_alloc(x));

	if (p == NULL) {
		return -1;
	}

	argv = (LS_API_F_ARGV_T(utils, is_zero) *)(p);
	p += (sizeof (LS_API_F_ARGV_T(utils, is_zero)));
	argv->n = (const unsigned char *)(p);

	if (ei_decode_binary(buffer, index, (void *)(argv->n), (long *)&(argv->nlen)) < 0) {
		(void) driver_free(argv);
		return -1;
	}

	request->argv = (void *)(argv);

	return 0;
}

static void
LS_API_EXEC(utils, is_zero)
{
	LS_API_F_ARGV_T(utils, is_zero) *argv;
	LS_API_READ_ARGV(utils, is_zero);
	int r;

	r = sodium_is_zero(argv->n, argv->nlen);

	ErlDrvTermData spec[] = {
		LS_RES_TAG(request),
		ERL_DRV_INT, (ErlDrvSInt)(r),
		ERL_DRV_TUPLE, 2
	};

	LS_RESPOND(request, spec, __FILE__, __LINE__);
}

/* utils_increment/1 */

typedef struct LS_API_F_ARGV(utils, increment) {
	unsigned char	*n;
	size_t		nlen;
} LS_API_F_ARGV_T(utils, increment);

static int
LS_API_INIT(utils, increment)
{
	LS_API_F_ARGV_T(utils, increment) *argv;
	int type;
	int type_length;
	ErlDrvSizeT x;
	void *p;

	if (ei_get_type(buffer, index, &type, &type_length) < 0
			|| type != ERL_BINARY_EXT) {
		return -1;
	}

	x = (ErlDrvSizeT)(type_length + (sizeof (LS_API_F_ARGV_T(utils, increment))));
	p = (void *)(driver_alloc(x));

	if (p == NULL) {
		return -1;
	}

	argv = (LS_API_F_ARGV_T(utils, increment) *)(p);
	p += (sizeof (LS_API_F_ARGV_T(utils, increment)));
	argv->n = (unsigned char *)(p);

	if (ei_decode_binary(buffer, index, (void *)(argv->n), (long *)&(argv->nlen)) < 0) {
		(void) driver_free(argv);
		return -1;
	}

	request->argv = (void *)(argv);

	return 0;
}

static void
LS_API_EXEC(utils, increment)
{
	LS_API_F_ARGV_T(utils, increment) *argv;
	LS_API_READ_ARGV(utils, increment);

	(void) sodium_increment(argv->n, argv->nlen);

	ErlDrvTermData spec[] = {
		LS_RES_TAG(request),
		ERL_DRV_BUF2BINARY, (ErlDrvTermData)(argv->n), argv->nlen,
		ERL_DRV_TUPLE, 2
	};

	LS_RESPOND(request, spec, __FILE__, __LINE__);
}

/* utils_add/2 */

typedef struct LS_API_F_ARGV(utils, add) {
	unsigned char		*a;
	const unsigned char	*b;
	size_t			len;
} LS_API_F_ARGV_T(utils, add);

static int
LS_API_INIT(utils, add)
{
	LS_API_F_ARGV_T(utils, add) *argv;
	int skip;
	int type;
	int type_length;
	size_t len;
	ErlDrvSizeT x;
	void *p;

	if (ei_get_type(buffer, index, &type, &type_length) < 0
			|| type != ERL_BINARY_EXT) {
		return -1;
	}

	len = (size_t)(type_length);

	skip = *index;

	if (ei_skip_term(buffer, &skip) < 0) {
		return -1;
	}

	if (ei_get_type(buffer, &skip, &type, &type_length) < 0
			|| type != ERL_BINARY_EXT
			|| type_length != len) {
		return -1;
	}

	x = (ErlDrvSizeT)(len + len + (sizeof (LS_API_F_ARGV_T(utils, add))));
	p = (void *)(driver_alloc(x));

	if (p == NULL) {
		return -1;
	}

	argv = (LS_API_F_ARGV_T(utils, add) *)(p);
	p += (sizeof (LS_API_F_ARGV_T(utils, add)));
	argv->a = (unsigned char *)(p);
	p += len;
	argv->b = (const unsigned char *)(p);
	argv->len = len;

	if (ei_decode_binary(buffer, index, (void *)(argv->a), NULL) < 0) {
		(void) driver_free(argv);
		return -1;
	}

	if (ei_decode_binary(buffer, index, (void *)(argv->b), NULL) < 0) {
		(void) driver_free(argv);
		return -1;
	}

	request->argv = (void *)(argv);

	return 0;
}

static void
LS_API_EXEC(utils, add)
{
	LS_API_F_ARGV_T(utils, add) *argv;
	LS_API_READ_ARGV(utils, add);

	(void) sodium_add(argv->a, argv->b, argv->len);

	ErlDrvTermData spec[] = {
		LS_RES_TAG(request),
		ERL_DRV_BUF2BINARY, (ErlDrvTermData)(argv->a), argv->len,
		ERL_DRV_TUPLE, 2
	};

	LS_RESPOND(request, spec, __FILE__, __LINE__);
}

/* utils_bin2hex/1 */

typedef struct LS_API_F_ARGV(utils, bin2hex) {
	const unsigned char	*bin;
	size_t			bin_len;
} LS_API_F_ARGV_T(utils, bin2hex);

static int
LS_API_INIT(utils, bin2hex)
{
	LS_API_F_ARGV_T(utils, bin2hex) *argv;
	int type;
	int type_length;
	ErlDrvSizeT x;
	void *p;

	if (ei_get_type(buffer, index, &type, &type_length) < 0
			|| type != ERL_BINARY_EXT) {
		return -1;
	}

	x = (ErlDrvSizeT)(type_length + (sizeof (LS_API_F_ARGV_T(utils, bin2hex))));
	p = (void *)(driver_alloc(x));

	if (p == NULL) {
		return -1;
	}

	argv = (LS_API_F_ARGV_T(utils, bin2hex) *)(p);
	p += (sizeof (LS_API_F_ARGV_T(utils, bin2hex)));
	argv->bin = (const unsigned char *)(p);

	if (ei_decode_binary(buffer, index, (void *)(argv->bin), (long *)&(argv->bin_len)) < 0) {
		(void) driver_free(argv);
		return -1;
	}

	request->argv = (void *)(argv);

	return 0;
}

static void
LS_API_EXEC(utils, bin2hex)
{
	LS_API_F_ARGV_T(utils, bin2hex) *argv;
	LS_API_READ_ARGV(utils, bin2hex);

	size_t hex_maxlen = (argv->bin_len * 2) + 1;
	char hex[hex_maxlen];
	char *hexp;

	hexp = sodium_bin2hex(hex, hex_maxlen, argv->bin, argv->bin_len);

	if (hexp == NULL) {
		LS_FAIL_OOM(request->port->drv_port);
		return;
	}

	ErlDrvTermData spec[] = {
		LS_RES_TAG(request),
		ERL_DRV_BUF2BINARY, (ErlDrvTermData)(hex), strlen(hex),
		ERL_DRV_TUPLE, 2
	};

	LS_RESPOND(request, spec, __FILE__, __LINE__);

	(void) sodium_memzero(hex, strlen(hex));
}

/* utils_hex2bin/2 */

typedef struct LS_API_F_ARGV(utils, hex2bin) {
	const char	*hex;
	const size_t	hex_len;
	const char	*ignore;
} LS_API_F_ARGV_T(utils, hex2bin);

static int
LS_API_INIT(utils, hex2bin)
{
	LS_API_F_ARGV_T(utils, hex2bin) *argv;
	int skip;
	int type;
	int type_length;
	size_t hex_len;
	size_t ignore_len;
	ErlDrvSizeT x;
	void *p;

	if (ei_get_type(buffer, index, &type, &type_length) < 0
			|| type != ERL_BINARY_EXT) {
		return -1;
	}

	hex_len = (size_t)(type_length);

	skip = *index;

	if (ei_skip_term(buffer, &skip) < 0) {
		return -1;
	}

	if (ei_get_type(buffer, &skip, &type, &type_length) < 0
			|| type != ERL_BINARY_EXT) {
		return -1;
	}

	ignore_len = (size_t)(type_length);

	if (ignore_len > 0) {
		ignore_len += 1;
	}

	x = (ErlDrvSizeT)(hex_len + ignore_len + (sizeof (LS_API_F_ARGV_T(utils, hex2bin))));
	p = (void *)(driver_alloc(x));

	if (p == NULL) {
		return -1;
	}

	argv = (LS_API_F_ARGV_T(utils, hex2bin) *)(p);
	p += (sizeof (LS_API_F_ARGV_T(utils, hex2bin)));
	argv->hex = (const char *)(p);
	p += hex_len;
	if (ignore_len == 0) {
		argv->ignore = NULL;
	} else {
		argv->ignore = (const char *)(p);
	}

	if (ei_decode_binary(buffer, index, (void *)(argv->hex), (long *)&(argv->hex_len)) < 0) {
		(void) driver_free(argv);
		return -1;
	}

	if (ignore_len > 0) {
		if (ei_decode_binary(buffer, index, (void *)(argv->ignore), (long *)&(ignore_len)) < 0) {
			(void) driver_free(argv);
			return -1;
		}
		char *c = (char *)(p + ignore_len);
		*c = '\0';
	}

	request->argv = (void *)(argv);

	return 0;
}

static void
LS_API_EXEC(utils, hex2bin)
{
	LS_API_F_ARGV_T(utils, hex2bin) *argv;
	LS_API_READ_ARGV(utils, hex2bin);

	size_t bin_maxlen = argv->hex_len;
	unsigned char bin[bin_maxlen];
	size_t bin_len = 0;

	LS_SAFE_REPLY(sodium_hex2bin(bin, bin_maxlen, argv->hex, argv->hex_len, argv->ignore, &bin_len, NULL), LS_PROTECT({
		LS_RES_TAG(request),
		ERL_DRV_BUF2BINARY, (ErlDrvTermData)(bin), bin_len,
		ERL_DRV_TUPLE, 2
	}), __FILE__, __LINE__);

	if (bin_len > 0) {
		(void) sodium_memzero(bin, bin_len);
	}
}
