

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  This file is part of CMSDK version 2.1.1.260726 (26 July 2026)
//_/_/  
//_/_/  developed by
//_/_/  
//_/_/  Communicative Machines Ltd. 
//_/_/    http://cmlabs.com/
//_/_/  Copyright (c) 2026
//_/_/  
//_/_/  in collaboration with 
//_/_/  
//_/_/  Icelandic Institute for Intelligent Machines ses
//_/_/    Menntavegur 1
//_/_/    101 Reykjavik
//_/_/    ICELAND
//_/_/    http://www.iiim.is/
//_/_/
//_/_/  and is released under the following
//_/_/  
//_/_/  EXTENDED BSD License
//_/_/
//_/_/  Redistribution and use in source and binary forms, with or without
//_/_/  modification, is permitted provided that the following conditions 
//_/_/  are met:
//_/_/
//_/_/  - Redistributions of source code must retain the above copyright 
//_/_/    and collaboration 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 its copyright holders nor the names of its 
//_/_/    contributors may be used to endorse or promote products derived from 
//_/_/    this software without specific prior written permission.
//_/_/
//_/_/  - CADIA Clause: The license granted in and to the software under this 
//_/_/    agreement is a limited-use license. The software may not be used in 
//_/_/    furtherance of: (i) intentionally causing bodily injury or severe 
//_/_/    emotional harm to any person; (ii) invading the personal privacy or 
//_/_/    violating the human rights of any person; or (iii) committing or 
//_/_/    preparing for any act of war. 
//_/_/
//_/_/  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.
//_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
/* 
   Base64.cpp and Base64.h

   Copyright (C) 2004-2008 René Nyffenegger

   This source code is provided 'as-is', without any express or implied
   warranty. In no event will the author be held liable for any damages
   arising from the use of this software.

   Permission is granted to anyone to use this software for any purpose,
   including commercial applications, and to alter it and redistribute it
   freely, subject to the following restrictions:

   1. The origin of this source code must not be misrepresented; you must not
      claim that you wrote the original source code. If you use this source code
      in a product, an acknowledgment in the product documentation would be
      appreciated but is not required.

   2. Altered source versions must be plainly marked as such, and must not be
      misrepresented as being the original source code.

   3. This notice may not be removed or altered from any source distribution.

   René Nyffenegger rene.nyffenegger@adp-gmbh.ch

*/

#include "Base64.h"
#include "UnitTestFramework.h"
#include "PsyTime.h"
#include <iostream>

static const std::string base64_chars = 
             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
             "abcdefghijklmnopqrstuvwxyz"
             "0123456789+/";


static inline bool is_base64(unsigned char c) {
  return (isalnum(c) || (c == '+') || (c == '/'));
}

/* Added for convenience by Thor List */
std::string base64_encode(std::string string_to_encode) {
	return base64_encode((const unsigned char*)string_to_encode.c_str(), (unsigned int)string_to_encode.length());
}

/* Added for convenience by Thor List */
std::string base64_decode(const char* encoded_string) {
	std::string str = encoded_string;
	return base64_decode(str);
}

std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) {
  std::string ret;
  int i = 0;
  int j = 0;
  unsigned char char_array_3[3];
  unsigned char char_array_4[4];

  while (in_len--) {
    char_array_3[i++] = *(bytes_to_encode++);
    if (i == 3) {
      char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
      char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
      char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
      char_array_4[3] = char_array_3[2] & 0x3f;

      for(i = 0; (i <4) ; i++)
        ret += base64_chars[char_array_4[i]];
      i = 0;
    }
  }

  if (i)
  {
    for(j = i; j < 3; j++)
      char_array_3[j] = '\0';

    char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
    char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
    char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
    char_array_4[3] = char_array_3[2] & 0x3f;

    for (j = 0; (j < i + 1); j++)
      ret += base64_chars[char_array_4[j]];

    while((i++ < 3))
      ret += '=';

  }

  return ret;

}

std::string base64_decode(std::string const& encoded_string) {
  int in_len = (int)encoded_string.size();
  int i = 0;
  int j = 0;
  int in_ = 0;
  unsigned char char_array_4[4], char_array_3[3];
  std::string ret;

  while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
    char_array_4[i++] = encoded_string[in_]; in_++;
    if (i ==4) {
      for (i = 0; i <4; i++)
        char_array_4[i] = (unsigned char)base64_chars.find(char_array_4[i]);

      char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
      char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
      char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

      for (i = 0; (i < 3); i++)
        ret += char_array_3[i];
      i = 0;
    }
  }

  if (i) {
    for (j = i; j <4; j++)
      char_array_4[j] = 0;

    for (j = 0; j <4; j++)
      char_array_4[j] = (unsigned char)base64_chars.find(char_array_4[j]);

    char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
    char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
    char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

    for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
  }

  return ret;
}


// ----------------------------------------------------------------------------
//  Unit test
// ----------------------------------------------------------------------------
// The base64_* functions live at global scope (no namespace), but the unit
// test framework, metric/progress helpers and GetTimeNow() all live in
// namespace cmlabs. We put the test and its registration inside cmlabs so the
// framework calls resolve, and reach the encode/decode functions via ::.
namespace cmlabs {

bool Base64_UnitTest() {
	unittest::progress(0, "RFC 4648 known vectors");

	// 1. RFC 4648 section 10 test vectors: encode(input) == expected
	{
		struct Vec { const char* in; const char* out; };
		static const Vec vectors[] = {
			{ "",       ""         },
			{ "f",      "Zg=="     },
			{ "fo",     "Zm8="     },
			{ "foo",    "Zm9v"     },
			{ "foob",   "Zm9vYg==" },
			{ "fooba",  "Zm9vYmE=" },
			{ "foobar", "Zm9vYmFy" },
		};
		const int nvec = (int)(sizeof(vectors) / sizeof(vectors[0]));
		for (int v = 0; v < nvec; v++) {
			std::string in = vectors[v].in;
			std::string enc = ::base64_encode(in);
			if (enc != vectors[v].out) {
				unittest::fail("Base64 test: encode(\"%s\") = \"%s\", expected \"%s\"",
					vectors[v].in, enc.c_str(), vectors[v].out);
				return false;
			}
			unittest::detail("encode(\"%s\") = \"%s\"", vectors[v].in, enc.c_str());

			// And decode of the canonical encoding must reproduce the input.
			std::string dec = ::base64_decode(std::string(vectors[v].out));
			if (dec != in) {
				unittest::fail("Base64 test: decode(\"%s\") = \"%s\", expected \"%s\"",
					vectors[v].out, dec.c_str(), vectors[v].in);
				return false;
			}
		}
	}

	// 2. Round-trip on random binary, including embedded NUL bytes.
	//    Use the buffer-based encode and the std::string-based decode so that
	//    embedded NULs are preserved (the const char* convenience overloads
	//    would truncate at the first NUL).
	unittest::progress(35, "random binary round-trip");
	{
		// Deterministic PRNG so failures are reproducible.
		uint32 seed = 0x1234abcdU;
		for (int trial = 0; trial < 200; trial++) {
			uint32 len = (seed % 256u);            // 0..255 bytes
			seed = seed * 1664525u + 1013904223u;  // LCG step
			std::string blob;
			blob.reserve(len);
			for (uint32 b = 0; b < len; b++) {
				blob.push_back((char)(seed & 0xffu));
				seed = seed * 1664525u + 1013904223u;
			}
			std::string enc = ::base64_encode((const unsigned char*)blob.data(), (unsigned int)blob.size());
			std::string dec = ::base64_decode(enc);
			if (dec != blob) {
				unittest::fail("Base64 test: random round-trip mismatch at trial %d (len %u): got %u bytes back",
					trial, (unsigned int)blob.size(), (unsigned int)dec.size());
				return false;
			}
		}
	}

	// 3. Encoded output length / padding sanity for sizes 0..32.
	unittest::progress(60, "padding & length invariants");
	{
		for (uint32 len = 0; len <= 32; len++) {
			std::string blob(len, 'A');
			std::string enc = ::base64_encode((const unsigned char*)blob.data(), (unsigned int)blob.size());
			// Standard base64: 4 chars per 3 input bytes, padded to a multiple of 4.
			size_t expectedLen = len == 0 ? 0 : (((size_t)len + 2) / 3) * 4;
			if (enc.size() != expectedLen) {
				unittest::fail("Base64 test: encode length for %u bytes = %u, expected %u",
					len, (unsigned int)enc.size(), (unsigned int)expectedLen);
				return false;
			}
			std::string dec = ::base64_decode(enc);
			if (dec != blob) {
				unittest::fail("Base64 test: length-vector round-trip mismatch at len %u", len);
				return false;
			}
		}
	}

	// 4. Throughput metric: encode + decode a fixed payload many times.
	unittest::progress(80, "throughput");
	{
		const uint32 payloadSize = 64u * 1024u; // 64 KB
		std::string payload(payloadSize, '\0');
		uint32 seed = 0xdeadbeefU;
		for (uint32 b = 0; b < payloadSize; b++) {
			payload[b] = (char)(seed & 0xffu);
			seed = seed * 1664525u + 1013904223u;
		}

		const int iterations = 64; // 64 * 64KB = 4 MB through each path
		uint64 t0 = GetTimeNow();
		std::string enc, dec;
		for (int it = 0; it < iterations; it++) {
			enc = ::base64_encode((const unsigned char*)payload.data(), (unsigned int)payload.size());
			dec = ::base64_decode(enc);
			if (dec != payload) {
				unittest::fail("Base64 test: throughput round-trip mismatch at iteration %d", it);
				return false;
			}
		}
		double us = (double)(GetTimeNow() - t0);
		if (us < 1.0) us = 1.0;
		double totalBytes = (double)payloadSize * iterations * 2.0; // encode + decode
		double mbPerSec = (totalBytes / (1024.0 * 1024.0)) / (us / 1e6);
		unittest::metric("encode_decode_throughput", mbPerSec, "MB/s", true);
		unittest::detail("processed %.1f MB in %.0f us", totalBytes / (1024.0 * 1024.0), us);
	}

	unittest::progress(100, "done");
	return true;
}

void Register_Base64_Tests() {
	UnitTestRunner::instance().registerTest("base64", Base64_UnitTest,
		"Base64 encode/decode RFC 4648 vectors, binary round-trip, throughput", "util");
}

} // namespace cmlabs
