/*
 * base64.c: Base64 converting functions
 *
 * Copyright (C) 2001, 2002  Juha Yrjölä <juha.yrjola@iki.fi>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>

#include "internal.h"

static const u8 base64_table[66] =
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
	"0123456789+/=";

static const u8 bin_table[128] = {
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xE0,0xD0,0xFF,0xFF,0xD0,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xE0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0x3E,0xFF,0xF2,0xFF,0x3F,
        0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,
        0x3C,0x3D,0xFF,0xFF,0xFF,0xC0,0xFF,0xFF,
        0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06,
        0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,
        0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,
        0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,
        0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,
        0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,
        0x31,0x32,0x33,0xFF,0xFF,0xFF,0xFF,0xFF,
};

static void to_base64(unsigned int i, u8 *out, size_t fillers)
{
	unsigned int s, c;

	for (c = 0, s = 18; c < 4; c++, s -= 6) {
		if (fillers >= 4 - c)
			*out = base64_table[64];
		else
			*out = base64_table[(i >> s) & 0x3f];
		out++;
	}
}

static int from_base64(const char *in, unsigned int *out, size_t *skip)
{
	unsigned int res = 0, c, s = 18;
	const char *in0 = in;

	for (c = 0; c < 4; c++, in++) {
		u8 b;
		int k = *in;

		if (k < 0 || k >= (int)sizeof(bin_table))
			return -1;
		if (k == 0 && c == 0)
			return 0;
		b = bin_table[k];
		if (b == 0xC0) /* '=' */
			break;
		switch (b) {
		case 0xD0: /* '\n' or '\r' */
			c--;
			continue;
		}
		if (b > 0x3f)
			return -1;

		res |= b << s;
		s -= 6;
	}
	*skip = in - in0;
	*out = res;
	return c * 6 / 8;
}

int sc_base64_encode(const u8 *in, size_t len, u8 *out, size_t outlen, size_t linelength)
{
	unsigned int chars = 0;
	unsigned int i, c;

	linelength -= linelength & 0x03;
	while (len >= 3) {
		i = in[2] + (in[1] << 8) + (in[0] << 16);
		in += 3;
		len -= 3;
		if (outlen < 4)
			return SC_ERROR_BUFFER_TOO_SMALL;
		to_base64(i, out, 0);
		out += 4;
		outlen -= 4;
		chars += 4;
		if (chars >= linelength && linelength > 0) {
			if (outlen < 1)
				return SC_ERROR_BUFFER_TOO_SMALL;
			*out = '\n';
			out++;
			outlen--;
			chars = 0;
		}
	}
	i = c = 0;
	while (c < len)
		i |= ((unsigned int) *in++) << ((2 - c++) << 3);
	if (len) {
		if (outlen < 4)
			return SC_ERROR_BUFFER_TOO_SMALL;
		to_base64(i, out, 3 - len);
		out += 4;
		outlen -= 4;
		chars += 4;
	}
	if (chars && linelength > 0) {
		if (outlen < 1)
			return SC_ERROR_BUFFER_TOO_SMALL;
		*out = '\n';
		out++;
		outlen--;
	}
	if (outlen < 1)
		return SC_ERROR_BUFFER_TOO_SMALL;
	*out = 0;

	return 0;
}

int sc_base64_decode(const char *in, u8 *out, size_t outlen)
{
	int len = 0, r = 0;
	size_t skip = 0;
	unsigned int i = 0;

	while ((r = from_base64(in, &i, &skip)) > 0) {
		int finished = 0, s = 16;

		if (r < 3)
			finished = 1;
		while (r--) {
			if (outlen <= 0)
				return SC_ERROR_BUFFER_TOO_SMALL;
			*out++ = i >> s;
			s -= 8;
			outlen--;
			len++;
		}
		in += skip;
		if (finished || *in == 0)
			return len;
	}
	if (r == 0)
		return len;
	return SC_ERROR_INVALID_ARGUMENTS;
}