/* ARISA - BASE64 Encoder and Decoder
 * Copyright (C) 2004 Carl Ritson
 *
 * Based on RFC1341, does not implement line packing, therefore is *NOT* a
 * valid Encoder, and violates Decoding rules.
 * See http://www.rfceditor.org for more information.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#define _GNU_SOURCE
#define _REENTRANT
#define _THREAD_SAFE

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

#include <sys/types.h>

#include <string.h>
#include <stdint.h>

#include "../config.h"

#include "base64.h"

static const char	pad = '=';

static const char	encode_table[] = {
	'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 
	'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
	'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
	'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
};

static volatile int	decode_initialised = 0; // assume int is atomic
static uint8_t		decode_table[256];

static void init_decode_table(void) {
	int i;
	
	if(decode_initialised)
		return;

	for(i = 0; i < 256; ++i)
		decode_table[i] = 255;
	for(i = 0; i < 64; ++i)
		decode_table[(uint8_t)encode_table[i]] = i;
	decode_table[(uint8_t)pad] = 0;

	decode_initialised = 1;
}

int base64_enc(char *dst, size_t *dst_len, uint8_t *src, size_t src_len) {
	char group[4];
	int dpos = 0, spos = 0;

	while(dpos < *dst_len && spos < src_len) {
		if(dpos + 4 >= (*dst_len - 1))
			return -1;
		if((src_len - spos) >= 3) {
			uint8_t b1 = src[spos], b2 = src[spos+1];
			uint8_t b3 = src[spos+2];
			group[0] = encode_table[b1 & 0x3f];
			group[1] = encode_table[(b1 & 0xc0) >> 6 | (b2 & 0x0f) << 2];
			group[2] = encode_table[(b2 & 0xf0) >> 4 | (b3 & 0x03) << 4];
			group[3] = encode_table[(b3 & 0xfc) >> 2];
			spos += 3;
		} else if((src_len - spos) == 2) {
			uint8_t b1 = src[spos], b2 = src[spos+1];
			group[0] = encode_table[b1 & 0x3f];
			group[1] = encode_table[(b1 & 0xc0) >> 6 | (b2 & 0x0f) << 2];
			group[2] = encode_table[(b2 & 0xf0) >> 4];
			group[3] = pad;
			spos += 2;
		} else /* if((src_len - spos) == 1) */ {
			uint8_t b1 = src[spos];
			group[0] = encode_table[b1 & 0x3f];
			group[1] = encode_table[(b1 & 0xc0) >> 6];
			group[2] = pad;
			group[3] = pad;
			spos += 1;
		}
		strncpy(dst+dpos,group,4);
		dpos += 4;
	}

	dst[dpos] = '\0';
	*dst_len = dpos;

	return 0;
}

int base64_dec(uint8_t *dst, size_t *dst_len, char *src, size_t src_len) {
	uint8_t group[3];
	int gused = 0;
	int dpos = 0, spos = 0, padding = 0;

	init_decode_table();
	
	while(dpos < *dst_len && spos < src_len) {
		if(src[spos] == '\0')
			break;
		if(src[spos] == pad)
			padding++;
		if(padding > 2)
			break;
		
		uint8_t b = decode_table[(uint8_t)src[spos++]];
		if(b == 255)
			continue;
		
		if(gused == 0) {
			group[0] = b;
			gused++;
		} else if(gused == 1) {
			group[0] |= (b & 0x03) << 6;
			group[1] = (b & 0x3c) >> 2;
			gused++;
		} else if(gused == 2) {
			group[1] |= (b & 0x0f) << 4;
			group[2] = (b & 0x30) >> 4;
			gused++;
		} else if(gused == 3) {
			group[2] |= b << 2;
			gused++;
		}
		
		if(gused == 4) {
			gused = gused - 1 - padding;
			if(dpos + gused < *dst_len)
				memcpy(dst + dpos,&group,gused);
			dpos += gused;
			gused = 0;
		}
	}
	
	if(src[spos] == '\0' || (spos == src_len && gused == 0)) {
		*dst_len = dpos;
		return 0;
	} else {
		return -1;
	}
}
