/*******************************************************************************
** This file is provided under a dual BSD/GPLv2 license.  When using or
** redistributing this file, you may do so under either license.
**
** GPL LICENSE SUMMARY
**
** Copyright (c) 2013-2023 Intel Corporation All Rights Reserved
**
** This program is free software; you can redistribute it and/or modify it under
** the terms of version 2 of the GNU General Public License as published by the
** Free Software Foundation.
**
** 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.,
** 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
** The full GNU General Public License is included in this distribution in the
** file called LICENSE.GPL.
**
** BSD LICENSE
**
** Copyright (c) 2013-2023 Intel Corporation All Rights Reserved
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are met:
**
** * Redistributions of source code must retain the above copyright notice, this
**   list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright notice,
**   this list of conditions and the following disclaimer in the documentation
**   and/or other materials provided with the distribution.
** * Neither the name of Intel Corporation nor the names of its contributors may
**   be used to endorse or promote products derived from this software without
**   specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,  SPECIAL, EXEMPLARY, OR
** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
** POSSIBILITY OF SUCH DAMAGE.
**
*******************************************************************************/

#include "esif_ccb_string.h"
#include "esif_sdk_base64.h"

// Base64 Decoding Helper Macros
#define BASE64_DECODED_BUFFER_LEN(encoded_len)	((((encoded_len) + 3) / 4) * 3)
#define BASE64_DECODED_ISVALID(ch)				(isalpha(ch) || isdigit(ch) || (ch) == '+' || (ch) == '/')
#define BASE64_DECODED_BYTE(ch)					((unsigned char)((ch) == '=' ? 0x00 : (ch) == '/' ? 0x3F : (ch) == '+' ? 0x3E : isdigit(ch) ? (ch) - '0' + 0x34 : islower(ch) ? (ch) - 'a' + 0x1A : (ch) - 'A'))

// Decode a Base64-Encoded Buffer into Binary Buffer
esif_error_t esif_base64_decode(
	void *target_buf,			// Target Buffer. May be NULL if *target_len = 0. Data must be padded with '=' if encoded_len not divisible by 4.
	size_t *target_len,			// Target Length: Buffer size if target_buf not NULL, Output: Decoded Length [if ESIF_OK] or Required Length [if ESIF_E_NEED_LARGER_BUFFER]
	const char *encoded_buf,	// Base-64 Encoded Buffer [Null-terminator optional]. May be NULL if *target_len = 0
	size_t encoded_len			// Base-64 Encoded Length, including Padding [not including optional Null-terminator]. May include whitespace.
)
{
	esif_error_t rc = ESIF_E_PARAMETER_IS_NULL;

	if (encoded_len > MAX_BASE64_ENCODED_LEN) {
		rc = ESIF_E_PARAMETER_IS_OUT_OF_BOUNDS;
	}
	else if (target_len && *target_len < BASE64_DECODED_BUFFER_LEN(encoded_len)) {
		*target_len = BASE64_DECODED_BUFFER_LEN(encoded_len);
		rc = ESIF_E_NEED_LARGER_BUFFER;
	}
	else if (target_buf && target_len && encoded_buf) {
		size_t j = 0;
		size_t k = 0;
		size_t padding = 0;
		size_t whitespace = 0;
		const char *whitespace_chars = "\r\n\t ";
		unsigned char decoded[4] = { 0 };
		unsigned char *outbuf = target_buf;
		rc = ESIF_E_COMMAND_DATA_INVALID;

		// Ignore trailing whitespace
		while (encoded_len > 0 && esif_ccb_strchr(whitespace_chars, encoded_buf[encoded_len - 1]) != NULL) {
			encoded_len--;
			whitespace++;
		}

		// Decode each 4-byte text chunk into a 3 byte binary chunk (final binary chunk will be 1-3 bytes)
		for (j = 0, k = 0; j + 3 < encoded_len && k + 2 < *target_len; j += 4, k += 3) {
			if ((!BASE64_DECODED_ISVALID(encoded_buf[j]))
				|| (!BASE64_DECODED_ISVALID(encoded_buf[j + 1]))
				|| (!BASE64_DECODED_ISVALID(encoded_buf[j + 2]) && (j + 2 < encoded_len - 2 || encoded_buf[j + 2] != '='))
				|| (!BASE64_DECODED_ISVALID(encoded_buf[j + 3]) && (j + 3 < encoded_len - 2 || encoded_buf[j + 3] != '='))) {
				break;
			}
			decoded[0] = BASE64_DECODED_BYTE(encoded_buf[j]);
			decoded[1] = BASE64_DECODED_BYTE(encoded_buf[j + 1]);
			decoded[2] = BASE64_DECODED_BYTE(encoded_buf[j + 2]);
			decoded[3] = BASE64_DECODED_BYTE(encoded_buf[j + 3]);
			outbuf[k]     = ((decoded[0] & 0x3F) << 2) | ((decoded[1] & 0x30) >> 4);
			outbuf[k + 1] = ((decoded[1] & 0x0F) << 4) | ((decoded[2] & 0x3C) >> 2);
			outbuf[k + 2] = ((decoded[2] & 0x03) << 6) | ((decoded[3] & 0x3F));

			// Do not decode trailing padding bytes
			if (j + 4 == encoded_len) {
				if (encoded_buf[j + 2] == '=') {
					padding++;
				}
				if (encoded_buf[j + 3] == '=') {
					padding++;
				}
			}
			// Allow for whitespace every 4 bytes since many conversion tools add newlines every 64 bytes
			while (j + 4 < encoded_len && esif_ccb_strchr(whitespace_chars, encoded_buf[j + 4]) != NULL) {
				whitespace++;
				j++;
			}
		}
		// If buffer fully decoded, return success and actual decoded length
		if (j == encoded_len && k == *target_len - BASE64_DECODED_BUFFER_LEN(whitespace)) {
			*target_len = k - padding;
			rc = ESIF_OK;
		}
	}
	return rc;
}