
/* 
 * Argyll Color Management System
 *
 * Very simple & concise base64 encoder/decoder
 *
 */

/*
 * Author: Graeme W. Gill
 *
 * Copyright 2014, Graeme W. Gill
 * All rights reserved.
 *
 * This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :-
 * see the License2.txt file for licencing details.
 */


#include <stdio.h>
# define DLL_LOCAL
#include "base64.h"

static int enc(int val) {
	val &= 0x3f;

	if (val <= 25)
		return ('A' + val);
	if (val <= 51)
		return ('a' + val - 26);
	if (val <= 61)
		return ('0' + val - 52);
	if (val == 62)
		return '+';
	return '/';
}

/* Encode slen bytes into nul terminated string. */
/* if dlen is !NULL, then set it to resulting string length excluding nul */
/* We assume that the destination buffer is long enough at EBASE64LEN */
void DLL_LOCAL ebase64(int *dlen, char *dst, unsigned char *src, int slen) {
	unsigned char buf[3];
	int i, j;

	/* Do this 3 characters at a time */
	for (j = i = 0; i < slen; i += 3) { 
		int ib;

		/* Grab 3 bytes or zero pad, and count bits */
		buf[0] = src[i]; ib = 8;
		buf[1] = (i+1) < slen ? (ib += 8, src[i+1]) : 0;
		buf[2] = (i+2) < slen ? (ib += 8, src[i+2]) : 0;

		/* Output up to 4 encoded characters */
		dst[j] = enc(buf[0] >> 2), j++;
		if (ib > 6) {
			dst[j] = enc(buf[0] << 4 | buf[1] >> 4), j++;
			if (ib > 12) {
				dst[j] = enc(buf[1] << 2 | buf[2] >> 6), j++;
				if (ib > 18)
					dst[j] = enc(buf[2]), j++;
			}
		}
	}
	if (dlen != NULL)
		*dlen = j;

	dst[j++] = '\000';
}

/* Return binary value corresponding to encoded character, */
/* -1 for invalid character, -2 if nul terminator */
/* (We're assuming ASCII encoding) */
static int dec(int val) {
	val &= 0xff;

	if (val == 0)
		return -2;
	if (val == '+')
		return 62;
	if (val == '/')
		return 63;
	if (val < '0')
		return -1;
	if (val <= '9')
		return val - '0' + 52;
	if (val < 'A')
		return -1;
	if (val <= 'Z')
		return val - 'A';
	if (val < 'a')
		return -1;
	if (val <= 'z')
		return val - 'a' + 26;
	return -1;
}

/* Decode nul terminated string into bytes, ignoring illegal characters. */
/* if dlen is !NULL, then set it to resulting byte length */
/* We assume that the destination buffer is long enough at DBASE64LEN */
void DLL_LOCAL dbase64(int *dlen, unsigned char *dst, char *src) {
	int buf[4];
	int j;

	/* Do this 4 characters at a time */
	for (j = 0;;) { 
		int v, ib;

		/* Grab 4 characters skipping illegals, and count bits */
		while ((buf[0] = dec(*src++)) == -1)
			;
		if (buf[0] == -2)
			break;
		ib = 6;
		while ((buf[1] = dec(*src++)) == -1)
			;
		if (buf[1] != -2) {
			ib += 6;
			while ((buf[2] = dec(*src++)) == -1)
				;
			if (buf[2] != -2) {
				ib += 6;
				while ((buf[3] = dec(*src++)) == -1)
					;
				if (buf[3] != -2)
					ib += 6;
				else
					buf[3] = 0;
			} else
				buf[2] = 0;
		} else
			buf[1] = 0;

		/* Output up to 3 decoded bytes */
		dst[j] = buf[0] << 2 | buf[1] >> 4, j++;
		if (ib > 12) {
			dst[j] = buf[1] << 4 | buf[2] >> 2, j++;
			if (ib > 18) {
				dst[j] = buf[2] << 6 | buf[3], j++;
				continue;		/* We didn't run out */
			}
		}
		break;		/* Must be done if didn't continue */
	}
	if (dlen != NULL)
		*dlen = j;
}


#ifdef STANDALONE_TEST
/* test base64 code */

#include <numlib.h>

#define MAXLEN 30

int main() {
	unsigned char src[MAXLEN], check[MAXLEN];
	char dst[EBASE64LEN(MAXLEN) + 1];
	int n, slen, dlen, chlen, i, j;

	printf("Testing base64 encode/decode:\n");

	for (n = 0; n < 10000000; n++) {

		/* Create a random binary */
		slen = i_rand(0, MAXLEN);
		for (i = 0; i < slen; i++)
			src[i] = i_rand(0, 255);

		/* Encode it */
		ebase64(&dlen, dst, src, slen);

		if (dlen != EBASE64LEN(slen)) {
			error("%d: Wrong encoded length, src %d, is %d should be %d\n",n,slen,dlen,EBASE64LEN(slen));
		}

		/* Decode it */
		dbase64(&chlen, check, dst);

		if (chlen != DBASE64LEN(dlen)) {
			error("%d: Wrong decoded length, enc %d, is %d should be %d\n",n,dlen,chlen,DBASE64LEN(dlen));
		}

		/* Check characters match */
		for (i = 0; i < slen; i++) {
			if (src[i] != check[i]) {
				for (j = 0; j < slen; j++) {
					printf("%02X ",src[j]);
					if ((j % 3) == 2)
						printf(" ");
				}
				printf("\n");

				for (j = 0; j < dlen; j++) {
					printf("%c ",dst[j]);
					if ((j % 4) == 3)
						printf("  ");
				}
				printf("\n");
				for (j = 0; j < slen; j++) {
					printf("%02X ",check[j]);
					if ((j % 3) == 2)
						printf(" ");
				}
				printf("\n\n");
				printf("src len %d, dst len %d\n",slen, dlen);
				error("%d: Verify error at byte %d, is 0x%x should be 0x%x\n",n,i,check[i],src[i]);
			}
		}

		if ((n % 10000) == 0)
			printf("done %d tests\n",n);
	}
	printf("Testing %d conversions base64 complete\n",n);
}

#endif /* STANDALONE_TEST */
