/*
 * Base64 encoding/decoding (RFC1341)
 * Copyright (c) 2005-2011, Jouni Malinen <j@w1.fi>
 *
 * Copyright (C) 2020 iopsys Software Solutions AB.
 *
 * Modified to return encode/decode data in user passed buffer instead of
 * allocating one in the library functions.
 *
 * This software may be distributed under the terms of the BSD license.
 * See README for more details.
 */

#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "easy.h"

static const unsigned char base64_table[65] =
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

/**
 * base64_encode - Base64 encode
 * @src: Data to be encoded
 * @len: Length of the data to be encoded
 * @out: Pointer to output buffer where encoded data is written to
 * @out_len: Pointer to output length (initialized with sizeof out buffer)
 * Returns: 0 on success, else -1 or appropriate error codes.
 *
 * Returned buffer is '\0' terminated to make it easier to use as a C string.
 * The '\0' terminator is not included in out_len.
 */
int LIBEASY_API base64_encode(const unsigned char *src, size_t len,
				unsigned char *out, size_t *out_len)
{
	unsigned char *pos;
	const unsigned char *end, *in;
	size_t olen;
	int line_len;

	if (!out || !out_len)
		return -EINVAL;

	olen = len * 4 / 3 + 4; /* 3-byte blocks to 4-byte */
	olen += olen / 72; /* line feeds */
	olen++; /* nul termination */
	if (olen < len)
		return -ERANGE; /* integer overflow */

	if (*out_len < olen)
		return -EMSGSIZE;

	memset(out, 0, olen);
	end = src + len;
	in = src;
	pos = out;
	line_len = 0;
	while (end - in >= 3) {
		*pos++ = base64_table[in[0] >> 2];
		*pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)];
		*pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)];
		*pos++ = base64_table[in[2] & 0x3f];
		in += 3;
		line_len += 4;
		if (line_len >= 72) {
			*pos++ = '\n';
			line_len = 0;
		}
	}

	if (end - in) {
		*pos++ = base64_table[in[0] >> 2];
		if (end - in == 1) {
			*pos++ = base64_table[(in[0] & 0x03) << 4];
			*pos++ = '=';
		} else {
			*pos++ = base64_table[((in[0] & 0x03) << 4) |
					      (in[1] >> 4)];
			*pos++ = base64_table[(in[1] & 0x0f) << 2];
		}
		*pos++ = '=';
		line_len += 4;
	}

	if (line_len)
		*pos++ = '\n';

	*pos = '\0';
	*out_len = (size_t)(pos - out);

	return 0;
}


/**
 * base64_decode - Base64 decode
 * @src: Data to be decoded
 * @len: Length of the data to be decoded
 * @out: Pointer to output buffer where decoded data is written to
 * @out_len: Pointer to output length (initialized with sizeof out buffer)
 * Returns: 0 on success, else -1 or appropriate error codes.
 */
int LIBEASY_API base64_decode(const unsigned char *src, size_t len,
				unsigned char *out, size_t *out_len)
{
	unsigned char dtable[256], *pos, block[4], tmp;
	size_t i, count, olen;
	int pad = 0;

	if (!out || !out_len)
		return -EINVAL;

	memset(dtable, 0x80, 256);
	for (i = 0; i < sizeof(base64_table) - 1; i++)
		dtable[base64_table[i]] = (unsigned char) i;
	dtable['='] = 0;

	count = 0;
	for (i = 0; i < len; i++) {
		if (dtable[src[i]] != 0x80)
			count++;
	}

	if (count == 0 || count % 4)
		return -1;

	olen = count / 4 * 3;
	if (*out_len < olen)
		return -EMSGSIZE;

	memset(out, 0, olen);
	pos = out;

	count = 0;
	for (i = 0; i < len; i++) {
		tmp = dtable[src[i]];
		if (tmp == 0x80)
			continue;

		if (src[i] == '=')
			pad++;
		block[count] = tmp;
		count++;
		if (count == 4) {
			*pos++ = (unsigned char)((block[0] << 2U) | (block[1] >> 4U));
			*pos++ = (unsigned char)((block[1] << 4U) | (block[2] >> 2U));
			*pos++ = (unsigned char)((block[2] << 6U) | block[3]);
			count = 0;
			if (pad) {
				if (pad == 1)
					pos--;
				else if (pad == 2)
					pos -= 2;
				else {
					/* Invalid padding */
					return -1;
				}
				break;
			}
		}
	}

	*out_len = (size_t)(pos - out);

	return 0;
}