/* 
 * Copyright 2008-2018 Aerospike, Inc.
 *
 * Portions may be licensed to Aerospike, Inc. under one or more contributor
 * license agreements.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
#include "citrusleaf/cf_b64.h"

/******************************************************************************
 * CONSTANTS
 ******************************************************************************/

static const char base64_chars[] = { 
	'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 const bool base64_valid_a[] = {
		/*00*/ /*01*/ /*02*/ /*03*/ /*04*/ /*05*/ /*06*/ /*07*/   /*08*/ /*09*/ /*0A*/ /*0B*/ /*0C*/ /*0D*/ /*0E*/ /*0F*/
/*00*/	false, false, false, false, false, false, false, false,   false, false, false, false, false, false, false, false,
/*10*/  false, false, false, false, false, false, false, false,   false, false, false, false, false, false, false, false,
/*20*/	false, false, false, false, false, false, false, false,   false, false, false,  true, false, false, false,  true,
/*30*/	 true,  true,  true,  true,  true,  true,  true,  true,    true,  true, false, false, false, false, false, false,
/*40*/	false,  true,  true,  true,  true,  true,  true,  true,    true,  true,  true,  true,  true,  true,  true,  true,
/*50*/	 true,  true,  true,  true,  true,  true,  true,  true,    true,  true,  true, false, false, false, false, false,
/*60*/	false,  true,  true,  true,  true,  true,  true,  true,    true,  true,  true,  true,  true,  true,  true,  true,
/*70*/	 true,  true,  true,  true,  true,  true,  true,  true,    true,  true,  true, false, false, false, false, false,
/*80*/	false, false, false, false, false, false, false, false,   false, false, false, false, false, false, false, false,
/*90*/	false, false, false, false, false, false, false, false,   false, false, false, false, false, false, false, false,
/*A0*/	false, false, false, false, false, false, false, false,   false, false, false, false, false, false, false, false,
/*B0*/	false, false, false, false, false, false, false, false,   false, false, false, false, false, false, false, false,
/*C0*/	false, false, false, false, false, false, false, false,   false, false, false, false, false, false, false, false,
/*D0*/	false, false, false, false, false, false, false, false,   false, false, false, false, false, false, false, false,
/*E0*/	false, false, false, false, false, false, false, false,   false, false, false, false, false, false, false, false,
/*F0*/	false, false, false, false, false, false, false, false,   false, false, false, false, false, false, false, false
};

static const uint8_t base64_decode_a[] = {
		/*00*/ /*01*/ /*02*/ /*03*/ /*04*/ /*05*/ /*06*/ /*07*/   /*08*/ /*09*/ /*0A*/ /*0B*/ /*0C*/ /*0D*/ /*0E*/ /*0F*/
/*00*/	    0,     0,     0,     0,     0,     0,     0,     0,       0,     0,     0,     0,     0,     0,     0,     0,
/*10*/      0,     0,     0,     0,     0,     0,     0,     0,       0,     0,     0,     0,     0,     0,     0,     0,
/*20*/	    0,     0,     0,     0,     0,     0,     0,     0,       0,     0,     0,    62,     0,     0,     0,    63,
/*30*/	   52,    53,    54,    55,    56,    57,    58,    59,      60,    61,     0,     0,     0,     0,     0,     0,
/*40*/	    0,     0,     1,     2,     3,     4,     5,     6,       7,     8,     9,    10,    11,    12,    13,    14,
/*50*/	   15,    16,    17,    18,    19,    20,    21,    22,      23,    24,    25,     0,     0,     0,     0,     0,
/*60*/	    0,    26,    27,    28,    29,    30,    31,    32,      33,    34,    35,    36,    37,    38,    39,    40,
/*70*/	   41,    42,    43,    44,    45,    46,    47,    48,      49,    50,    51,     0,     0,     0,     0,     0,
/*80*/	    0,     0,     0,     0,     0,     0,     0,     0,       0,     0,     0,     0,     0,     0,     0,     0,
/*90*/	    0,     0,     0,     0,     0,     0,     0,     0,       0,     0,     0,     0,     0,     0,     0,     0,
/*A0*/	    0,     0,     0,     0,     0,     0,     0,     0,       0,     0,     0,     0,     0,     0,     0,     0,
/*B0*/	    0,     0,     0,     0,     0,     0,     0,     0,       0,     0,     0,     0,     0,     0,     0,     0,
/*C0*/	    0,     0,     0,     0,     0,     0,     0,     0,       0,     0,     0,     0,     0,     0,     0,     0,
/*D0*/	    0,     0,     0,     0,     0,     0,     0,     0,       0,     0,     0,     0,     0,     0,     0,     0,
/*E0*/	    0,     0,     0,     0,     0,     0,     0,     0,       0,     0,     0,     0,     0,     0,     0,     0,
/*F0*/	    0,     0,     0,     0,     0,     0,     0,     0,       0,     0,     0,     0,     0,     0,     0,     0
};

// Shortcuts:
#define EA base64_chars
#define VA base64_valid_a
#define DA base64_decode_a


/******************************************************************************
 * FUNCTIONS
 ******************************************************************************/

// Must have allocated big enough 'out' - e.g. use cf_b64_encoded_len(in_size).
void
cf_b64_encode(const uint8_t* in, uint32_t in_size, char* out)
{
	int i = 0;
	int j = 0;

	while (in_size >= 3) {
		uint8_t i0 = in[i];
		uint8_t i1 = in[i + 1];
		uint8_t i2 = in[i + 2];

		out[j]     = EA[ (i0 & 0xfc) >> 2];
		out[j + 1] = EA[((i0 & 0x03) << 4) + ((i1 & 0xf0) >> 4)];
		out[j + 2] = EA[((i1 & 0x0f) << 2) + ((i2 & 0xc0) >> 6)];
		out[j + 3] = EA[  i2 & 0x3f];

		i += 3;
		j += 4;

		in_size -= 3;
	}

	if (in_size == 1) {
		uint8_t i0 = in[i];

		out[j]     = EA[(i0 & 0xfc) >> 2];
		out[j + 1] = EA[(i0 & 0x03) << 4];
		out[j + 2] = '=';
		out[j + 3] = '=';
	}
	else if (in_size == 2) {
		uint8_t i0 = in[i];
		uint8_t i1 = in[i + 1];

		out[j]     = EA[ (i0 & 0xfc) >> 2];
		out[j + 1] = EA[((i0 & 0x03) << 4) + ((i1 & 0xf0) >> 4)];
		out[j + 2] = EA[ (i1 & 0x0f) << 2];
		out[j + 3] = '=';
	}
}

// Must have allocated big enough 'out' - e.g. use cf_b64_decoded_buf_size().
// Note that 'in_len' must be a padded encoded size - an exact multiple of 4.
// 'out_size' returns the decoded size after padding has been accounted for -
// i.e. it may be 1 or 2 less than the 'out' buffer size. Also, 'in_len' = 0 is
// handled, and gives 'out_size' = 0.
void
cf_b64_decode(const char* in, uint32_t in_len, uint8_t* out, uint32_t* out_size)
{
	uint32_t i = 0;
	uint32_t j = 0;

	while (i < in_len) {
		uint8_t i0 = (uint8_t)in[i];
		uint8_t i1 = (uint8_t)in[i + 1];
		uint8_t i2 = (uint8_t)in[i + 2];
		uint8_t i3 = (uint8_t)in[i + 3];

		out[j]     = (DA[i0] << 2) | (DA[i1] >> 4);
		out[j + 1] = (DA[i1] << 4) | (DA[i2] >> 2);
		out[j + 2] = (DA[i2] << 6) |  DA[i3];

		i += 4;
		j += 3;
	}

	if (out_size) {
		if (i != 0) {
			if (in[i - 1] == '=') {
				j--;
			}

			if (in[i - 2] == '=') {
				j--;
			}
		}

		*out_size = j;
	}
}

// Same as cf_b64_decode() but 'in' and 'out' are the same buffer.
void
cf_b64_decode_in_place(uint8_t* in_out, uint32_t in_len, uint32_t* out_size)
{
	uint32_t i = 0;
	uint32_t j = 0;

	uint32_t d = 0;

	if (out_size && in_len != 0) {
		if (in_out[in_len - 1] == '=') {
			d++;
		}

		if (in_out[in_len - 2] == '=') {
			d++;
		}
	}

	while (i < in_len) {
		uint8_t i0 = in_out[i];
		uint8_t i1 = in_out[i + 1];
		uint8_t i2 = in_out[i + 2];
		uint8_t i3 = in_out[i + 3];

		uint8_t b0 = (DA[i0] << 2) | (DA[i1] >> 4);
		uint8_t b1 = (DA[i1] << 4) | (DA[i2] >> 2);
		uint8_t b2 = (DA[i2] << 6) |  DA[i3];

		in_out[j]     = b0;
		in_out[j + 1] = b1;
		in_out[j + 2] = b2;

		i += 4;
		j += 3;
	}

	if (out_size) {
		*out_size = j - d;
	}
}

// Make sure the length is ok and all characters are valid base64 characters,
// including any '=' padding at the end. Note that 'in_len' = 0 is considered
// invalid.
static bool
is_valid_encoded(const char* in, uint32_t in_len)
{
	if (! in || in_len == 0 || (in_len & 3) != 0) {
		return false;
	}

	const uint8_t* end = (const uint8_t*)in + in_len - 2;
	const uint8_t* read = (const uint8_t*)in;

	while (read < end) {
		if (! VA[*read++]) {
			return false;
		}
	}

	if (*read == '=') {
		return *(read + 1) == '=';
	}

	if (! VA[*read++]) {
		return false;
	}

	return *read == '=' || VA[*read];
}

// Same as cf_b64_decode() but validates input as ok to decode.
bool
cf_b64_validate_and_decode(const char* in, uint32_t in_len, uint8_t* out,
		uint32_t* out_size)
{
	if (! is_valid_encoded(in, in_len)) {
		return false;
	}

	cf_b64_decode(in, in_len, out, out_size);

	return true;
}

// Same as cf_b64_decode_in_place() but validates input as ok to decode.
bool
cf_b64_validate_and_decode_in_place(uint8_t* in_out, uint32_t in_len,
		uint32_t* out_size)
{
	if (! is_valid_encoded((const char*)in_out, in_len)) {
		return false;
	}

	cf_b64_decode_in_place(in_out, in_len, out_size);

	return true;
}
