

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  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.
//_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
/*! @file
    @brief CMSDK unit test for the vendored hash library: runs md5/sha1/sha256/
           crc32/sha3/keccak against known test vectors. First-party test code
           wrapping third-party hashes. */
// //////////////////////////////////////////////////////////
// hash/HashTest.cpp
// Unit test for the hash library (md5, sha1, sha256, crc32, sha3, keccak).
// Uses published known-answer test vectors.
//

#include "hash/HashTest.h"

#include "hash/md5.h"
#include "hash/sha1.h"
#include "hash/sha256.h"
#include "hash/crc32.h"
#include "hash/sha3.h"
#include "hash/keccak.h"

#include "PsyTime.h"
#include "UnitTestFramework.h"

#include <string>
#include <cctype>
#include <cstring>
#include <vector>

namespace cmlabs {

// Lower-case a hex digest so comparisons are case-insensitive.
static std::string ToLowerHex(const std::string& s) {
	std::string out(s);
	for (size_t i = 0; i < out.size(); i++)
		out[i] = (char)std::tolower((unsigned char)out[i]);
	return out;
}

// Compare a produced hex digest against the expected value (case-insensitive).
// On mismatch sets the failure reason (with algo name + expected/got) and
// returns false.
static bool CheckHex(const char* algo, const char* input,
                     const std::string& got, const char* expected) {
	if (ToLowerHex(got) != ToLowerHex(expected)) {
		unittest::fail("%s(\"%s\"): expected %s, got %s",
		               algo, input, expected, got.c_str());
		return false;
	}
	unittest::detail("%s(\"%s\") = %s", algo, input, got.c_str());
	return true;
}

bool Hash_UnitTest() {
	unittest::progress(0, "start");

	// ---- MD5 known-answer vectors (RFC 1321) ----
	unittest::progress(10, "md5");
	{
		hash::MD5 md5;
		if (!CheckHex("MD5", "", md5(""),
		              "d41d8cd98f00b204e9800998ecf8427e")) return false;
		if (!CheckHex("MD5", "abc", md5("abc"),
		              "900150983cd24fb0d6963f7d28e17f72")) return false;
		if (!CheckHex("MD5", "message digest", md5("message digest"),
		              "f96b697d7cb7938d525a2f31aaf161d0")) return false;

		// Streaming add() must match the one-shot result.
		hash::MD5 s;
		s.add("ab", 2);
		s.add("c", 1);
		if (!CheckHex("MD5-stream", "abc", s.getHash(),
		              "900150983cd24fb0d6963f7d28e17f72")) return false;
	}

	// ---- SHA1 known-answer vectors (FIPS 180-2) ----
	unittest::progress(25, "sha1");
	{
		hash::SHA1 sha1;
		if (!CheckHex("SHA1", "", sha1(""),
		              "da39a3ee5e6b4b0d3255bfef95601890afd80709")) return false;
		if (!CheckHex("SHA1", "abc", sha1("abc"),
		              "a9993e364706816aba3e25717850c26c9cd0d89d")) return false;
		if (!CheckHex("SHA1",
		              "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
		              sha1("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"),
		              "84983e441c3bd26ebaae4aa1f95129e5e54670f1")) return false;

		// getHashRaw() should be HashBytes raw bytes matching the hex digest.
		hash::SHA1 r;
		r.add("abc", 3);
		std::string raw = r.getHashRaw();
		if (raw.size() != (size_t)hash::SHA1::HashBytes) {
			unittest::fail("SHA1 getHashRaw size: expected %d, got %zu",
			               (int)hash::SHA1::HashBytes, raw.size());
			return false;
		}
	}

	// ---- SHA256 known-answer vectors (FIPS 180-2) ----
	unittest::progress(40, "sha256");
	{
		hash::SHA256 sha256;
		if (!CheckHex("SHA256", "",
		              sha256(""),
		              "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"))
			return false;
		if (!CheckHex("SHA256", "abc",
		              sha256("abc"),
		              "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"))
			return false;
	}

	// ---- CRC32 known-answer vector ----
	// CRC32("123456789") == 0xCBF43926; getHash() returns 8 hex chars.
	unittest::progress(55, "crc32");
	{
		hash::CRC32 crc;
		if (!CheckHex("CRC32", "123456789", crc("123456789"),
		              "cbf43926")) return false;

		// Verify the raw-byte accessor agrees with the hex digest (big-endian).
		hash::CRC32 c2;
		c2.add("123456789", 9);
		unsigned char buf[hash::CRC32::HashBytes];
		c2.getHash(buf);
		uint32 val = ((uint32)buf[0] << 24) | ((uint32)buf[1] << 16) |
		             ((uint32)buf[2] << 8) | (uint32)buf[3];
		if (val != 0xCBF43926u) {
			unittest::fail("CRC32 raw bytes: expected 0xCBF43926, got 0x%08X", val);
			return false;
		}
	}

	// ---- SHA3-256 known-answer vector ----
	unittest::progress(68, "sha3");
	{
		hash::SHA3 sha3(hash::SHA3::Bits256);
		if (!CheckHex("SHA3-256", "",
		              sha3(""),
		              "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a"))
			return false;
		hash::SHA3 sha3b(hash::SHA3::Bits256);
		if (!CheckHex("SHA3-256", "abc",
		              sha3b("abc"),
		              "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"))
			return false;
	}

	// ---- Keccak-256 known-answer vector (pre-NIST padding) ----
	unittest::progress(80, "keccak");
	{
		hash::Keccak keccak(hash::Keccak::Keccak256);
		if (!CheckHex("Keccak-256", "",
		              keccak(""),
		              "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"))
			return false;
		hash::Keccak keccakb(hash::Keccak::Keccak256);
		if (!CheckHex("Keccak-256", "abc",
		              keccakb("abc"),
		              "4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45"))
			return false;
	}

	// ---- Throughput metric: hash a sizeable buffer with SHA256 ----
	unittest::progress(90, "throughput");
	{
		const size_t bufSize = 64 * 1024;            // 64 KB block
		const int    iters   = 256;                  // 16 MB total
		std::vector<unsigned char> buf(bufSize);
		for (size_t i = 0; i < bufSize; i++)
			buf[i] = (unsigned char)(i * 31 + 7);

		uint64 t0 = GetTimeNow();
		hash::SHA256 sha;
		for (int i = 0; i < iters; i++)
			sha.add(&buf[0], bufSize);
		std::string digest = sha.getHash();          // force completion
		double us = (double)(GetTimeNow() - t0);
		if (us <= 0.0) us = 1.0;
		double totalBytes = (double)bufSize * (double)iters;
		double mbps = (totalBytes / (1024.0 * 1024.0)) / (us / 1e6);
		unittest::metric("sha256_throughput", mbps, "MB/s", true);
		unittest::detail("SHA256 hashed %.0f bytes in %.0f us -> %s",
		                 totalBytes, us, digest.c_str());
	}

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

void Register_Hash_Tests() {
	UnitTestRunner::instance().registerTest("hash", Hash_UnitTest,
		"Hash library: MD5/SHA1/SHA256/CRC32/SHA3/Keccak known-answer vectors", "util");
}

} // namespace cmlabs
