/*******************************************************************************
 * Copyright (c) 2005, 2009 Intel Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Kevin O'Leary, Intel - Initial API and implementation
 * $Id: Base64.c,v 1.10 2009/07/10 00:11:28 jwest Exp $ 
 *
 *******************************************************************************/ 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tptp/TPTPUtils.h"

#define LOOKUPLENGTH 64
#define NUM_TEST 10

static const char base64char[LOOKUPLENGTH] =  
	{ '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','+','/' };

int lookup(char c);

/*
 * tptp_encodeBase64()
 *
 * Function to encode a byte array into Base64 representation.
 *
 * Parameters:
 *  binaryData: pointer to the byte array to encode.
 *  dataLen: number of encode-able bytes in the "in_buffer" array.
 *  decodedData: pointer to the Base64 array to store the result. This has to be preallocated. This is null-terminated.
 *  bufferLen: size of the "out"array.
 *
 * Returns:
 *  0 if encoding is successful
 *  >0 if the "bufferLen" is too small for the encoding. The number returned is the number of bytes needed for the "out" array
 *
 * Notes:
 *  In order to get the length required for allocating the "out" buffer, the caller should call this function like this:
 *  int length = encode(raw, strlen(raw), NULL, 0);
 *
 */
int tptp_encodeBase64(unsigned char* binaryData, int dataLen, tptp_string* encodedData, int bufferLen) {
	int i;
	int out_count = 0;

	int required;

	/*
	 * Calculate the number of bytes required
	 *
	 * #bytes #Base64
	 * ------ -------
	 *    1      4
	 *    2      4
	 *    3      4
	 *    4      8
	 *    5      8
	 *    6      8
	 *    7     12
	 *    8     12
	 *    9     12
	 *
	 * Therefore the formula is:
	 *
	 * required = ((number of bytes - 1) / 3 + 1) * 4
	 *
	 * Note that this is not counting the terminating null character.
	 *
	 */

	required = ((dataLen - 1) / 3 + 1) * 4 + 1; /* Needs a null at the end */

	if(bufferLen < required) {
		return required;
	}

	/* Process 3 raw bytes at a time */
	for(i = 0; i < (dataLen / 3); i++) {
		int b[3];

		b[0] = binaryData[3 * i];
		b[1] = binaryData[3 * i + 1];
		b[2] = binaryData[3 * i + 2];

		/* Proess the first slot */
		encodedData[out_count] = base64char[b[0] >> 2];
		out_count++;

		/* Process the second slot */
		encodedData[out_count] = base64char[(b[1] >> 4) + ((b[0] & 0x03) << 4)];
		out_count++;

		/* Process the third slot */
		encodedData[out_count] = base64char[(b[2] >> 6) + ((b[1] & 0x0F) << 2)];
		out_count++;

		/* Process the fourth slot */
		encodedData[out_count] = base64char[b[2] & 0x3F];
		out_count++;
	}

	/* Process the remaining misaligned bytes if any */
	if(dataLen % 3 == 2) {
		int b[2];

		b[0] = binaryData[dataLen - 2];
		b[1] = binaryData[dataLen - 1];

		/* Proess the first slot */
		encodedData[out_count] = base64char[b[0] >> 2];
		out_count++;

		/* Process the second slot */
		encodedData[out_count] = base64char[(b[1] >> 4) + ((b[0] & 0x03) << 4)];
		out_count++;

		/* Process the third slot */
		encodedData[out_count] = base64char[0 + ((b[1] & 0x0F) << 2)]; /* Zeros padded */
		out_count++;

		/* Add one '=' at the end signaling it is padded */
		encodedData[out_count] = '=';
		out_count++;
	}
	else if(dataLen % 3 == 1) {
		int b[1];

		b[0] = binaryData[dataLen - 1];

		/* Proess the first slot */
		encodedData[out_count] = base64char[b[0] >> 2];
		out_count++;

		/* Process the second slot */
		encodedData[out_count] = base64char[0 + ((b[0] & 0x03) << 4)]; /* Zeros padded */
		out_count++;

		/* Add two '=' at the end signaling it is padded */
		encodedData[out_count] = '=';
		out_count++;

		encodedData[out_count] = '=';
		out_count++;
	}

	encodedData[out_count] = 0; /* For encoding only: terminate with a null */

	return 0;
}

/*
 * tptp_decodeBase64()
 *
 * Function to decode a Base64 representation to a byte array.
 *
 * Parameters:
 *  base64Data: pointer to the Base64 array to decode.
 *  encodedLen: number of decode-able bytes in the "in_buffer" array.
 *  decodedData: pointer to the byte array to store the result. This has to be preallocated. This is null-terminated.
 *  bufferLen: size of the "out"array.
 *
 * Returns:
 *  0 if decoding is successful
 *  >0 if the "bufferLen" is too small for the decoding. The number returned is the number of bytes needed for the "out" array
 *  <0 if the "in" array does not corresponding to a base 64 representation or it is corrupted. There number of Base64 characters should be a multiple of 4.
 *
 * Notes:
 *  In order to get the length required for allocating the "out" buffer, the caller should call this function like this:
 *  int length = decode(base64, strlen(base64), NULL, 0);
 *
 */
 int tptp_decodeBase64(tptp_string* base64Data, int encodedLen, unsigned char* decodedData, int bufferLen) {
	int i, j;
	int out_count = 0;

	char *in_buffer; /* A modified buffer with all newlines removed */
	int in_len; /* A modified buffer size without all the newlines */

	int required;

	/* Need to handle the newline in MIME format */
	in_len = encodedLen;
	in_buffer = (char*)malloc(sizeof(char) * encodedLen); /* Upperbound max size equals to the original */
	memset(in_buffer, 0, encodedLen);
	for(i = 0, j = 0; i < encodedLen; i++) {
		if((base64Data[i] == '\n') || (base64Data[i] == '\r')) {
			in_len--; /* Decrement the length by one whenever a newline is encountered */
		}
		else {
			in_buffer[j] = base64Data[i]; /* Otherwise copy the byte to the modified buffer */
			j++;
		}
	}

	/* Check if the input buffer contains a valid Base64 representation*/
	if(in_len % 4 != 0) {
		free(in_buffer);
		return -1;
	}

	/* Check the output buffer if it can contain the result */
	required = in_len / 4 * 3; /* The upperbound */
	if(in_buffer[in_len - 1] == '=') {
		required--; /* One '=' means one zero padded byte */
		if(in_buffer[in_len - 2] == '=') {
			required--; /* Two '=' means two zero padded bytes */
		}
	}

	if(bufferLen < required || decodedData == NULL) {
		free(in_buffer);
		return required; /* Add a null at the end */
	}


	for(i = 0; i < (in_len / 4); i++) {
		int b64[4];

		b64[0] = lookup(in_buffer[i * 4]);
		b64[1] = lookup(in_buffer[i * 4 + 1]);
		b64[2] = lookup(in_buffer[i * 4 + 2]);
		b64[3] = lookup(in_buffer[i * 4 + 3]);

		/* Process the 1st byte */
		decodedData[out_count] = (b64[0] << 2) + (b64[1] >> 4);
		out_count++;

		/* No more byte if the 3rd and 4th Base64 equal to the '=' character */
		if((b64[2] == -1) && (b64[3] == -1)){
			/* done */
		}
		else {
			decodedData[out_count] = (b64[1] << 4) + (b64[2] >> 2);
			out_count++;

			/* No more byte if the 4th Base64 equals to the '=' character */
			if(b64[3] == -1) {
				/* done */
			}
			else {
				decodedData[out_count] = (b64[2] << 6) + b64[3];
				out_count++;
			}
		}

	}

	/* decodedData[out_count] = 0; */ /* Decoding does not need to terminate with a null */
	free(in_buffer);

	return 0;
}

int lookup(char c) {
	switch(c) {
		/* A-Z */
		case 'A': return 0;
		case 'B': return 1;
		case 'C': return 2;
		case 'D': return 3;
		case 'E': return 4;
		case 'F': return 5;
		case 'G': return 6;
		case 'H': return 7;
		case 'I': return 8;
		case 'J': return 9;
		case 'K': return 10;
		case 'L': return 11;
		case 'M': return 12;
		case 'N': return 13;
		case 'O': return 14;
		case 'P': return 15;
		case 'Q': return 16;
		case 'R': return 17;
		case 'S': return 18;
		case 'T': return 19;
		case 'U': return 20;
		case 'V': return 21;
		case 'W': return 22;
		case 'X': return 23;
		case 'Y': return 24;
		case 'Z': return 25;

		/* a-z */
		case 'a': return 26;
		case 'b': return 27;
		case 'c': return 28;
		case 'd': return 29;
		case 'e': return 30;
		case 'f': return 31;
		case 'g': return 32;
		case 'h': return 33;
		case 'i': return 34;
		case 'j': return 35;
		case 'k': return 36;
		case 'l': return 37;
		case 'm': return 38;
		case 'n': return 39;
		case 'o': return 40;
		case 'p': return 41;
		case 'q': return 42;
		case 'r': return 43;
		case 's': return 44;
		case 't': return 45;
		case 'u': return 46;
		case 'v': return 47;
		case 'w': return 48;
		case 'x': return 49;
		case 'y': return 50;
		case 'z': return 51;

		/* 0-9, +, / */
		case '0': return 52;
		case '1': return 53;
		case '2': return 54;
		case '3': return 55;
		case '4': return 56;
		case '5': return 57;
		case '6': return 58;
		case '7': return 59;
		case '8': return 60;
		case '9': return 61;
		case '+': return 62;
		case '/': return 63;

		/* = */
		case '=': return -1;
	}

	return 0;
}

