/**
 * file: https://github.com/pasis/pppoat/blob/master/src/base64.c
 * description: BASE64 encoding implementation
 * this code is in public domain
 */

#include <string.h>
#include <stdlib.h>

#include "base64.h"

const char cb64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                    "abcdefghijklmnopqrstuvwxyz"
                    "0123456789+/";

char *b64_encode(char *buf, size_t len)
{
	char *b64, *p;
	size_t new_len;
	int i;
	unsigned char t;
	unsigned char *buf2 = (unsigned char *) buf;

	new_len = (len + 2) / 3 * 4 + 1;
	b64 = p = (char *)malloc(new_len * sizeof(*p));
	if (!b64)
		return NULL;

	for (i = 0; i < len / 3; i++) {
		p[0] = cb64[buf2[0] >> 2];
		t = buf2[0] << 4 & 0x3f;
		p[1] = cb64[buf2[1] >> 4 | t];
		t = buf2[1] << 2 & 0x3f;
		p[2] = cb64[buf2[2] >> 6 | t];
		p[3] = cb64[buf2[2] & 0x3f];
		p += 4;
		buf2 += 3;
	}

	if (len % 3) {
		*p++ = cb64[*buf2 >> 2];
		t = *buf2++ << 4 & 0x3f;
		if (len % 3 == 2) {
			*p++ = cb64[*buf2 >> 4 | t];
			*p++ = cb64[*buf2 << 2 & 0x3f];
		} else {
			*p++ = cb64[t];
		}
		for (i = 0; i < 3 - len % 3; i++)
			*p++ = '=';
	}

	*p = 0;

	return b64;
}

char *b64_decode(char *buf, size_t *p_len)
{
	char *b64, *p, *chr;
	size_t new_len, len;
	int i;
	unsigned char t;

	len = strlen(buf);
	new_len = len / 4 * 3;
	b64 = p = (char *)malloc(new_len * sizeof(*p));
	if (!b64)
		return NULL;

	for(i = 0; i < len; i++) {
		chr = strchr(cb64, (int) *buf++);
		if (!chr)
			break;
		t = (unsigned char) (chr - cb64);
		switch (i % 4) {
			case 0:
				*p = t << 2;
				break;
			case 1:
				*p++ |= t >> 4;
				*p = t << 4;
				break;
			case 2:
				*p++ |= t >> 2;
				*p = t << 6;
				break;
			case 3:
				*p++ |= t;
				break;
		}
	}

	*p_len = (size_t)(p - b64);
	return b64;
}


const int CHARS_PER_LINE = 72;

void base64_init_encodestate(base64_encodestate* state_in)
{
	state_in->step = step_A;
	state_in->result = 0;
	state_in->stepcount = 0;
}

char base64_encode_value(char value_in)
{
	static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	if (value_in > 63) return '=';
	return encoding[(int)value_in];
}

int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in)
{
	const char* plainchar = plaintext_in;
	const char* const plaintextend = plaintext_in + length_in;
	char* codechar = code_out;
	char result;
	char fragment;
	
	result = state_in->result;
	
	switch (state_in->step)
	{
		while(1) {
		case step_A:
			if (plainchar == plaintextend) {
				state_in->result = result;
				state_in->step = step_A;
				return codechar - code_out;
			}
			fragment = *plainchar++;
			result = (fragment & 0x0fc) >> 2;
			*codechar++ = base64_encode_value(result);
			result = (fragment & 0x003) << 4;
		case step_B:
			if (plainchar == plaintextend) {
				state_in->result = result;
				state_in->step = step_B;
				return codechar - code_out;
			}
			fragment = *plainchar++;
			result |= (fragment & 0x0f0) >> 4;
			*codechar++ = base64_encode_value(result);
			result = (fragment & 0x00f) << 2;
		case step_C:
			if (plainchar == plaintextend) {
				state_in->result = result;
				state_in->step = step_C;
				return codechar - code_out;
			}
			fragment = *plainchar++;
			result |= (fragment & 0x0c0) >> 6;
			*codechar++ = base64_encode_value(result);
			result  = (fragment & 0x03f) >> 0;
			*codechar++ = base64_encode_value(result);
			
			++(state_in->stepcount);
			if (state_in->stepcount == CHARS_PER_LINE/4) {
				*codechar++ = '\n';
				state_in->stepcount = 0;
			}
		}
	}
	/* control should not reach here */
	return codechar - code_out;
}

int base64_encode_blockend(char* code_out, base64_encodestate* state_in)
{
	char* codechar = code_out;
	
	switch (state_in->step)
	{
		case step_B:
			*codechar++ = base64_encode_value(state_in->result);
			*codechar++ = '=';
			*codechar++ = '=';
			break;
		case step_C:
			*codechar++ = base64_encode_value(state_in->result);
			*codechar++ = '=';
			break;
		case step_A:
			break;
	}
	*codechar++ = '\n';
	
	return codechar - code_out;
}

