/* Copyright (c) 2007-2018 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "base32.h"
#include "buffer.h"

static const char b32enc[] =
	"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

static const char b32hexenc[] =
	"0123456789ABCDEFGHIJKLMNOPQRSTUV";

static const unsigned char b32dec[256] = {
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0-7 */
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 8-15 */
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 16-23 */
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 24-31 */
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 32-39 */
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 40-47 */
	0xff, 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, /* 48-55 */
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 56-63 */
	0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* 64-71 */
	0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, /* 72-79 */
	0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, /* 80-87 */
	0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, /* 88-95 */
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 96-103 */
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 104-111 */
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 112-119 */
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 120-127 */

	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 128-255 */
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
};

static const unsigned char b32hexdec[256] = {
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0-7 */
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 8-15 */
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 16-23 */
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 24-31 */
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 32-39 */
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 40-47 */
	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, /* 48-55 */
	0x08, 0x09, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 56-63 */
	0xff, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, /* 64-71 */
	0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, /* 72-79 */
	0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0xff, /* 80-87 */
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 88-95 */
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 96-103 */
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 104-111 */
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 112-119 */
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 120-127 */

	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 128-255 */
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
};

static void
base32_encode_with_alphabet(const char *alph,
	bool pad, const void *src, size_t src_size, buffer_t *dest)
{
	const unsigned char *src_c = src;
	unsigned char tmp[8], endb;
	size_t src_pos;

	/* [5  3][2  5  1][4  4][1  5  2][3  5]
	   (5)(3  2)(5)(1  4)(4  1)(5)(2  3)(5)
	 */

	/* encode main part */
	for (src_pos = 0; src_pos + 4 < src_size; src_pos += 5) {
		tmp[0] = alph[src_c[src_pos] >> 3];
		tmp[1] = alph[((src_c[src_pos] & 0x07) << 2) |
				((src_c[src_pos+1] >> 6) & 0x03)];
		tmp[2] = alph[((src_c[src_pos+1] >> 1) & 0x1f)];
		tmp[3] = alph[((src_c[src_pos+1] & 0x01) << 4) |
				(src_c[src_pos+2] >> 4)];
		tmp[4] = alph[((src_c[src_pos+2] & 0x0f) << 1) |
				(src_c[src_pos+3] >> 7)];
		tmp[5] = alph[((src_c[src_pos+3] >> 2) & 0x1f)];
		tmp[6] = alph[((src_c[src_pos+3] & 0x03) << 3) |
				(src_c[src_pos+4] >> 5)];
		tmp[7] = alph[src_c[src_pos+4] & 0x1f];
		buffer_append(dest, tmp, 8);
	}

	/* encode last < 5 bytes if any */
	if (src_pos < src_size) {
		tmp[0] = alph[src_c[src_pos] >> 3];
		switch (src_size - src_pos) {
		case 1:
			tmp[1] = alph[((src_c[src_pos] & 0x07) << 2)];
			endb = 2;
			break;
		case 2:
			tmp[1] = alph[((src_c[src_pos] & 0x07) << 2) |
					((src_c[src_pos+1] >> 6) & 0x03)];
			tmp[2] = alph[((src_c[src_pos+1] >> 1) & 0x1f)];
			tmp[3] = alph[((src_c[src_pos+1] & 0x01) << 4)];
			endb = 4;
			break;
		case 3:
			tmp[1] = alph[((src_c[src_pos] & 0x07) << 2) |
					((src_c[src_pos+1] >> 6) & 0x03)];
			tmp[2] = alph[((src_c[src_pos+1] >> 1) & 0x1f)];
			tmp[3] = alph[((src_c[src_pos+1] & 0x01) << 4) |
					(src_c[src_pos+2] >> 4)];
			tmp[4] = alph[((src_c[src_pos+2] & 0x0f) << 1)];
			endb = 5;
			break;
		case 4:
			tmp[1] = alph[((src_c[src_pos] & 0x07) << 2) |
					((src_c[src_pos+1] >> 6) & 0x03)];
			tmp[2] = alph[((src_c[src_pos+1] >> 1) & 0x1f)];
			tmp[3] = alph[((src_c[src_pos+1] & 0x01) << 4) |
					(src_c[src_pos+2] >> 4)];
			tmp[4] = alph[((src_c[src_pos+2] & 0x0f) << 1) |
					(src_c[src_pos+3] >> 7)];
			tmp[5] = alph[((src_c[src_pos+3] >> 2) & 0x1f)];
			tmp[6] = alph[((src_c[src_pos+3] & 0x03) << 3)];
			endb = 7;
			break;
		default:
			i_unreached();
		}

		/* add padding if required */
		if (pad) {
			memset(&tmp[endb], '=', sizeof(tmp)-endb);
			buffer_append(dest, tmp, 8);
		} else {
			buffer_append(dest, tmp, endb);
		}
	}
}

void base32_encode(bool pad, const void *src, size_t src_size,
	buffer_t *dest)
{
	base32_encode_with_alphabet(b32enc, pad, src, src_size, dest);
}

void base32hex_encode(bool pad, const void *src, size_t src_size,
	buffer_t *dest)
{
	base32_encode_with_alphabet(b32hexenc, pad, src, src_size, dest);
}

#define IS_EMPTY(c) \
	((c) == '\n' || (c) == '\r' || (c) == ' ' || (c) == '\t')

static int
base32_decode_with_alphabet(const unsigned char *alph,
		  const void *src, size_t src_size, size_t *src_pos_r,
		  buffer_t *dest)
{
	const unsigned char *src_c = src;
	size_t block_pos, src_pos;
	unsigned char output[5], ipos, opos;
	int ret = 1;

	/* (5)(3  2)(5)(1  4)(4  1)(5)(2  3)(5)
	   [5  3][2  5  1][4  4][1  5  2][3  5]
	 */
	ipos = opos = 0;
	block_pos = 0;
	for (src_pos = 0; src_pos < src_size; src_pos++) {
		unsigned char input = alph[src_c[src_pos]];

		if (input == 0xff) {
			if (unlikely(!IS_EMPTY(src_c[src_pos])))
				break;
			continue;
		}

		ipos++;
		switch (ipos) {
		case 1:
			output[0] = input << 3;
			opos = 0;
			break;
		case 2:
			output[0] |= input >> 2;
			output[1] = (input & 0x03) << 6;
			opos = 1;
			break;
		case 3:
		 	output[1] |= input << 1;
			opos = 1;
			break;
		case 4:
			output[1] |= input >> 4;
			output[2] = (input & 0x0f) << 4;
			opos = 2;
			break;
		case 5:
			output[2] |= input >> 1;
			output[3] = (input & 0x01) << 7;
			opos = 3;
			break;
		case 6:
			output[3] |= input << 2;
			opos = 3;
			break;
		case 7:
			output[3] |= input >> 3;
			output[4] = ((input & 0x07) << 5);
			opos = 4;
			break;
		case 8:
			output[4] |= input;
			buffer_append(dest, output, 5);
			ipos = 0;
			opos = 0;
			block_pos = src_pos;
			break;
		default:
			i_unreached();
		}
	}

	if (ipos > 0) {
		for (; src_pos < src_size; src_pos++) {
			if (src_c[src_pos] != '=') {
				if (unlikely(!IS_EMPTY(src_c[src_pos]))) {
					ret = -1;
					break;
				}
				continue;
			}
			if (++ipos >= 8) {
				buffer_append(dest, output, opos);
				ipos = 0;
				ret = 0;
				src_pos++;
				break;
			}
		}
	}

	if (src_pos_r != NULL) {
		if (ipos == 0) {
			for (; src_pos < src_size; src_pos++) {
				if (!IS_EMPTY(src_c[src_pos]))
					break;
			}

			*src_pos_r = src_pos;
		} else {
			*src_pos_r = block_pos;
		}
	}
	return ret;
}

int base32_decode(const void *src, size_t src_size,
		  size_t *src_pos_r, buffer_t *dest)
{
	return base32_decode_with_alphabet
		(b32dec, src, src_size, src_pos_r, dest);
}
int base32hex_decode(const void *src, size_t src_size,
		  size_t *src_pos_r, buffer_t *dest)
{
	return base32_decode_with_alphabet
		(b32hexdec, src, src_size, src_pos_r, dest);
}

buffer_t *t_base32_decode_str(const char *str)
{
	buffer_t *buf;
	size_t len = strlen(str);

	buf = t_buffer_create(MAX_BASE32_DECODED_SIZE(len));
	(void)base32_decode(str, len, NULL, buf);
	return buf;
}

buffer_t *t_base32hex_decode_str(const char *str)
{
	buffer_t *buf;
	size_t len = strlen(str);

	buf = t_buffer_create(MAX_BASE32_DECODED_SIZE(len));
	(void)base32hex_decode(str, len, NULL, buf);
	return buf;
}

bool base32_is_valid_char(char c)
{
	return b32dec[(uint8_t)c] != 0xff;
}

bool base32hex_is_valid_char(char c)
{
	return b32hexdec[(uint8_t)c] != 0xff;
}
