/* Copyright (c) Dovecot authors, see top-level COPYING file */

#include "test-lib.h"
#include "hex-binary.h"
#include "mmap-util.h"
#include "hash-method.h"

#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
#  define MAP_ANONYMOUS MAP_ANON
#endif

static unsigned char *buf;
static unsigned int buf_size;

static void test_hash_method_one(const struct hash_method *method)
{
	unsigned char *ctx, *digest;
	unsigned int i;

	test_begin(t_strdup_printf("hash method %s", method->name));

	ctx = i_malloc(method->context_size);
	digest = i_malloc(method->digest_size);
	method->init(ctx);

	/* make sure the code doesn't try to access data past boundaries */
	for (i = 0; i < buf_size; i++)
		method->loop(ctx, buf + buf_size - i, i);
	method->result(ctx, digest);

	i_free(ctx);
	i_free(digest);
	test_end();
}

static void test_hash_method_boundary(void)
{
	unsigned int i;

	buf_size = mmap_get_page_size();
#ifdef MAP_ANONYMOUS
	buf = mmap(NULL, buf_size*2, PROT_READ | PROT_WRITE,
		   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
	mprotect(buf + buf_size, buf_size, PROT_NONE);
#else
	buf = i_malloc(buf_size);
#endif
	memset(buf, 0, buf_size);

	for (i = 0; hash_methods[i] != NULL; i++)
		test_hash_method_one(hash_methods[i]);
}

static void test_hash_methods_fips() {
	const char *last_method = NULL;

	struct {
		const char *method;
		const void *input;
		size_t ilen;
		size_t rounds;
		const void *output;
		size_t olen;
	} test_vectors[] =
	{
		{ "md4",
			"",
			0,
			1,
			"\x31\xd6\xcf\xe0\xd1\x6a\xe9\x31"
			"\xb7\x3c\x59\xd7\xe0\xc0\x89\xc0",
			128 / 8
		},
		{ "md4",
			"abc",
			3,
			1,
			"\xa4\x48\x01\x7a\xaf\x21\xd8\x52"
			"\x5f\xc1\x0a\xe8\x7a\xa6\x72\x9d",
			128 / 8
		},
		{ "md4",
			"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
			"ghijklmnopqrstuvwxyz0123456789",
			62,
			1,
			"\x04\x3f\x85\x82\xf2\x41\xdb\x35"
			"\x1c\xe6\x27\xe1\x53\xe7\xf0\xe4",
			128 / 8
		},
		{ "md5",
			"",
			0,
			1,
			"\xd4\x1d\x8c\xd9\x8f\x00\xb2\x04"
			"\xe9\x80\x09\x98\xec\xf8\x42\x7e",
			128 / 8
		},
		{ "md5",
			"abc",
			3,
			1,
			"\x90\x01\x50\x98\x3c\xd2\x4f\xb0"
			"\xd6\x96\x3f\x7d\x28\xe1\x7f\x72",
			128 / 8
		},
		{ "md5",
			"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
			"ghijklmnopqrstuvwxyz0123456789",
			62,
			1,
			"\xd1\x74\xab\x98\xd2\x77\xd9\xf5"
			"\xa5\x61\x1c\x2c\x9f\x41\x9d\x9f",
			128 / 8
		},
		{ "sha1",
			"",
			0,
			1,
			"\xda\x39\xa3\xee\x5e\x6b\x4b\x0d"
			"\x32\x55\xbf\xef\x95\x60\x18\x90"
			"\xaf\xd8\x07\x09",
			160 / 8
		},
		{ "sha1",
			"abc",
			3,
			1,
			"\xa9\x99\x3e\x36\x47\x06\x81\x6a"
			"\xba\x3e\x25\x71\x78\x50\xc2\x6c"
			"\x9c\xd0\xd8\x9d",
			160 / 8
		},
		{ "sha1",
			"abcdbcdecdefdefgefghfghighijhijk"
			"ijkljklmklmnlmnomnopnopq",
			56,
			1,
			"\x84\x98\x3e\x44\x1c\x3b\xd2\x6e"
			"\xba\xae\x4a\xa1\xf9\x51\x29\xe5"
			"\xe5\x46\x70\xf1",
			160 / 8
		},
		{ "sha256",
			"",
			0,
			1,
			"\xe3\xb0\xc4\x42\x98\xfc\x1c\x14"
			"\x9a\xfb\xf4\xc8\x99\x6f\xb9\x24"
			"\x27\xae\x41\xe4\x64\x9b\x93\x4c"
			"\xa4\x95\x99\x1b\x78\x52\xb8\x55",
			256 / 8
		},
		{ "sha256",
			"abc",
			3,
			1,
			"\xba\x78\x16\xbf\x8f\x01\xcf\xea"
			"\x41\x41\x40\xde\x5d\xae\x22\x23"
			"\xb0\x03\x61\xa3\x96\x17\x7a\x9c"
			"\xb4\x10\xff\x61\xf2\x00\x15\xad",
			256 / 8
		},
		{ "sha256",
			"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
			56,
			1,
			"\x24\x8d\x6a\x61\xd2\x06\x38\xb8"
			"\xe5\xc0\x26\x93\x0c\x3e\x60\x39"
			"\xa3\x3c\xe4\x59\x64\xff\x21\x67"
			"\xf6\xec\xed\xd4\x19\xdb\x06\xc1",
			256 / 8
		},
		{ "sha384",
			"",
			0,
			1,
			"\x38\xb0\x60\xa7\x51\xac\x96\x38"
			"\x4c\xd9\x32\x7e\xb1\xb1\xe3\x6a"
			"\x21\xfd\xb7\x11\x14\xbe\x07\x43"
			"\x4c\x0c\xc7\xbf\x63\xf6\xe1\xda"
			"\x27\x4e\xde\xbf\xe7\x6f\x65\xfb"
			"\xd5\x1a\xd2\xf1\x48\x98\xb9\x5b",
			384 / 8
		},
		{ "sha384",
			"abc",
			3,
			1,
			"\xcb\x00\x75\x3f\x45\xa3\x5e\x8b"
			"\xb5\xa0\x3d\x69\x9a\xc6\x50\x07"
			"\x27\x2c\x32\xab\x0e\xde\xd1\x63"
			"\x1a\x8b\x60\x5a\x43\xff\x5b\xed"
			"\x80\x86\x07\x2b\xa1\xe7\xcc\x23"
			"\x58\xba\xec\xa1\x34\xc8\x25\xa7",
			384 / 8
		},
		{ "sha384",
			"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
			56,
			1,
			"\x33\x91\xfd\xdd\xfc\x8d\xc7\x39"
			"\x37\x07\xa6\x5b\x1b\x47\x09\x39"
			"\x7c\xf8\xb1\xd1\x62\xaf\x05\xab"
			"\xfe\x8f\x45\x0d\xe5\xf3\x6b\xc6"
			"\xb0\x45\x5a\x85\x20\xbc\x4e\x6f"
			"\x5f\xe9\x5b\x1f\xe3\xc8\x45\x2b",
			384 / 8
		},
		{ "sha512",
			"",
			0,
			1,
			"\xcf\x83\xe1\x35\x7e\xef\xb8\xbd"
			"\xf1\x54\x28\x50\xd6\x6d\x80\x07"
			"\xd6\x20\xe4\x05\x0b\x57\x15\xdc"
			"\x83\xf4\xa9\x21\xd3\x6c\xe9\xce"
			"\x47\xd0\xd1\x3c\x5d\x85\xf2\xb0"
			"\xff\x83\x18\xd2\x87\x7e\xec\x2f"
			"\x63\xb9\x31\xbd\x47\x41\x7a\x81"
			"\xa5\x38\x32\x7a\xf9\x27\xda\x3e",
			512 / 8
		},
		{ "sha512",
			"abc",
			3,
			1,
			"\xdd\xaf\x35\xa1\x93\x61\x7a\xba"
			"\xcc\x41\x73\x49\xae\x20\x41\x31"
			"\x12\xe6\xfa\x4e\x89\xa9\x7e\xa2"
			"\x0a\x9e\xee\xe6\x4b\x55\xd3\x9a"
			"\x21\x92\x99\x2a\x27\x4f\xc1\xa8"
			"\x36\xba\x3c\x23\xa3\xfe\xeb\xbd"
			"\x45\x4d\x44\x23\x64\x3c\xe8\x0e"
			"\x2a\x9a\xc9\x4f\xa5\x4c\xa4\x9f",
			512 / 8
		},
		{ "sha512",
			"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
			56,
			1,
			"\x20\x4a\x8f\xc6\xdd\xa8\x2f\x0a"
			"\x0c\xed\x7b\xeb\x8e\x08\xa4\x16"
			"\x57\xc1\x6e\xf4\x68\xb2\x28\xa8"
			"\x27\x9b\xe3\x31\xa7\x03\xc3\x35"
			"\x96\xfd\x15\xc1\x3b\x1b\x07\xf9"
			"\xaa\x1d\x3b\xea\x57\x78\x9c\xa0"
			"\x31\xad\x85\xc7\xa7\x1d\xd7\x03"
			"\x54\xec\x63\x12\x38\xca\x34\x45",
			512 / 8
		},
		{ "sha3-256",
			"",
			0,
			1,
			"\xa7\xff\xc6\xf8\xbf\x1e\xd7\x66"
			"\x51\xc1\x47\x56\xa0\x61\xd6\x62"
 			"\xf5\x80\xff\x4d\xe4\x3b\x49\xfa"
			"\x82\xd8\x0a\x4b\x80\xf8\x43\x4a",
			256 / 8
		},
		{ "sha3-256",
			"\xb7\x71\xd5\xce\xf5\xd1\xa4\x1a"
			"\x93\xd1\x56\x43\xd7\x18\x1d\x2a"
			"\x2e\xf0\xa8\xe8\x4d\x91\x81\x2f"
			"\x20\xed\x21\xf1\x47\xbe\xf7\x32"
			"\xbf\x3a\x60\xef\x40\x67\xc3\x73"
			"\x4b\x85\xbc\x8c\xd4\x71\x78\x0f"
			"\x10\xdc\x9e\x82\x91\xb5\x83\x39"
			"\xa6\x77\xb9\x60\x21\x8f\x71\xe7"
			"\x93\xf2\x79\x7a\xea\x34\x94\x06"
			"\x51\x28\x29\x06\x5d\x37\xbb\x55"
			"\xea\x79\x6f\xa4\xf5\x6f\xd8\x89"
			"\x6b\x49\xb2\xcd\x19\xb4\x32\x15"
			"\xad\x96\x7c\x71\x2b\x24\xe5\x03"
			"\x2d\x06\x52\x32\xe0\x2c\x12\x74"
			"\x09\xd2\xed\x41\x46\xb9\xd7\x5d"
			"\x76\x3d\x52\xdb\x98\xd9\x49\xd3"
			"\xb0\xfe\xd6\xa8\x05\x2f\xbb",
			1080 / 8,
			1,
			"\xa1\x9e\xee\x92\xbb\x20\x97\xb6"
			"\x4e\x82\x3d\x59\x77\x98\xaa\x18"
			"\xbe\x9b\x7c\x73\x6b\x80\x59\xab"
			"\xfd\x67\x79\xac\x35\xac\x81\xb5",
			256 / 8
		},
		{ "sha3-256",
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3",
			200,
			1,
			"\x79\xf3\x8a\xde\xc5\xc2\x03\x07"
			"\xa9\x8e\xf7\x6e\x83\x24\xaf\xbf"
			"\xd4\x6c\xfd\x81\xb2\x2e\x39\x73"
			"\xc6\x5f\xa1\xbd\x9d\xe3\x17\x87",
			256 / 8,
		},
		{ "sha3-256",
			"\xa3",
			1,
			200,
			"\x79\xf3\x8a\xde\xc5\xc2\x03\x07"
			"\xa9\x8e\xf7\x6e\x83\x24\xaf\xbf"
			"\xd4\x6c\xfd\x81\xb2\x2e\x39\x73"
			"\xc6\x5f\xa1\xbd\x9d\xe3\x17\x87",
			256 / 8,
		},
		{ "sha3-512",
			"",
			0,
			1,
			"\xa6\x9f\x73\xcc\xa2\x3a\x9a\xc5"
			"\xc8\xb5\x67\xdc\x18\x5a\x75\x6e"
			"\x97\xc9\x82\x16\x4f\xe2\x58\x59"
			"\xe0\xd1\xdc\xc1\x47\x5c\x80\xa6"
			"\x15\xb2\x12\x3a\xf1\xf5\xf9\x4c"
			"\x11\xe3\xe9\x40\x2c\x3a\xc5\x58"
			"\xf5\x00\x19\x9d\x95\xb6\xd3\xe3"
			"\x01\x75\x85\x86\x28\x1d\xcd\x26",
			512 / 8
		},
		{ "sha3-512",
			"\xb7\x71\xd5\xce\xf5\xd1\xa4\x1a"
			"\x93\xd1\x56\x43\xd7\x18\x1d\x2a"
			"\x2e\xf0\xa8\xe8\x4d\x91\x81\x2f"
			"\x20\xed\x21\xf1\x47\xbe\xf7\x32"
			"\xbf\x3a\x60\xef\x40\x67\xc3\x73"
			"\x4b\x85\xbc\x8c\xd4\x71\x78\x0f"
			"\x10\xdc\x9e\x82\x91\xb5\x83\x39"
			"\xa6\x77\xb9\x60\x21\x8f\x71\xe7"
			"\x93\xf2\x79\x7a\xea\x34\x94\x06"
			"\x51\x28\x29\x06\x5d\x37\xbb\x55"
			"\xea\x79\x6f\xa4\xf5\x6f\xd8\x89"
			"\x6b\x49\xb2\xcd\x19\xb4\x32\x15"
			"\xad\x96\x7c\x71\x2b\x24\xe5\x03"
			"\x2d\x06\x52\x32\xe0\x2c\x12\x74"
			"\x09\xd2\xed\x41\x46\xb9\xd7\x5d"
			"\x76\x3d\x52\xdb\x98\xd9\x49\xd3"
			"\xb0\xfe\xd6\xa8\x05\x2f\xbb",
			1080 / 8,
			1,
			"\x75\x75\xa1\xfb\x4f\xc9\xa8\xf9"
			"\xc0\x46\x6b\xd5\xfc\xa4\x96\xd1"
			"\xcb\x78\x69\x67\x73\xa2\x12\xa5"
			"\xf6\x2d\x02\xd1\x4e\x32\x59\xd1"
			"\x92\xa8\x7e\xba\x44\x07\xdd\x83"
			"\x89\x35\x27\x33\x14\x07\xb6\xda"
			"\xda\xad\x92\x0d\xbc\x46\x48\x9b"
			"\x67\x74\x93\xce\x5f\x20\xb5\x95",
			512 / 8
		},
		{ "sha3-512",
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3"
			"\xa3\xa3\xa3\xa3\xa3\xa3\xa3\xa3",
			200,
			1,
			"\xe7\x6d\xfa\xd2\x20\x84\xa8\xb1"
			"\x46\x7f\xcf\x2f\xfa\x58\x36\x1b"
			"\xec\x76\x28\xed\xf5\xf3\xfd\xc0"
			"\xe4\x80\x5d\xc4\x8c\xae\xec\xa8"
			"\x1b\x7c\x13\xc3\x0a\xdf\x52\xa3"
			"\x65\x95\x84\x73\x9a\x2d\xf4\x6b"
			"\xe5\x89\xc5\x1c\xa1\xa4\xa8\x41"
			"\x6d\xf6\x54\x5a\x1c\xe8\xba\x00",
			512 / 8,
		},
		{ "sha3-512",
			"\xa3",
			1,
			200,
			"\xe7\x6d\xfa\xd2\x20\x84\xa8\xb1"
			"\x46\x7f\xcf\x2f\xfa\x58\x36\x1b"
			"\xec\x76\x28\xed\xf5\xf3\xfd\xc0"
			"\xe4\x80\x5d\xc4\x8c\xae\xec\xa8"
			"\x1b\x7c\x13\xc3\x0a\xdf\x52\xa3"
			"\x65\x95\x84\x73\x9a\x2d\xf4\x6b"
			"\xe5\x89\xc5\x1c\xa1\xa4\xa8\x41"
			"\x6d\xf6\x54\x5a\x1c\xe8\xba\x00",
			512 / 8,
		},
		/* xxh64 reference vectors (seed=0) */
		{ "xxh64",
			"",
			0,
			1,
			"\xef\x46\xdb\x37\x51\xd8\xe9\x99",
			64 / 8
		},
		{ "xxh64",
			"abc",
			3,
			1,
			"\x44\xbc\x2c\xf5\xad\x77\x09\x99",
			64 / 8
		},
		/* 62 bytes: exercises the >= 32-byte accumulator path */
		{ "xxh64",
			"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
			"ghijklmnopqrstuvwxyz0123456789",
			62,
			1,
			"\xba\x76\x1a\x9a\xa8\x0e\x0d\x36",
			64 / 8
		},
		/* 16 bytes × 3 rounds: exercises chunked input across the 32-byte block boundary */
		{ "xxh64",
			"\x00\x01\x02\x03\x04\x05\x06\x07"
			"\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
			16,
			3,
			"\x32\x0d\xcd\xab\xd8\x29\x7a\x9e",
			64 / 8
		},
	};

	for(size_t i = 0; i < N_ELEMENTS(test_vectors); i++) {

		if (last_method == NULL ||
		    strcmp(last_method, test_vectors[i].method) != 0) {
			if (last_method != NULL)
				test_end();
			last_method = test_vectors[i].method;
			test_begin(t_strdup_printf("hash method %s (test vectors)",
				   last_method));
		}
		const struct hash_method *method =
			hash_method_lookup(test_vectors[i].method);
		unsigned char context[method->context_size];
		unsigned char result[method->digest_size];
		test_assert_idx(method->digest_size == test_vectors[i].olen, i);
		method->init(context);
		for(size_t n = 0; n < test_vectors[i].rounds; n++)
			method->loop(context, test_vectors[i].input,
				     test_vectors[i].ilen);
		method->result(context, result);
		test_assert_idx(memcmp(result, test_vectors[i].output,
				       test_vectors[i].olen) == 0, i);
	}

	test_end();
}

static void test_hash_methods_large(void)
{
	struct {
		const char *method;
		const char *hash;
	} tests[] = {
		{ "sha256", "1ad0598b790b3acb38876105cc8938c3365f3215fbee3412ac3cd5e96a7dad01" },
		{ "sha384", "c187c084ffe516fea74b313340a540bc0bab306b1bdc564da21ecdc639e51f194460a0279c04aa40d65cec58698b10c0" },
		{ "sha512", "556247cfeab056903a3f42cf8496019d9ad90911ded9aa1ede3046b803623e5e2cd2adbd0620e666a927436d125984de9199d643ff21ad1c76e29b116c13ffb2" },
	};
	unsigned char data[1024];
	unsigned int i;

	test_begin("hash method (large inputs)");
	for (i = 0; i < sizeof(data); i++)
		data[i] = i & 0xFF;

	for (i = 0; i < N_ELEMENTS(tests); i++) {
		const struct hash_method *method =
			hash_method_lookup(tests[i].method);
		unsigned char context[method->context_size];
		unsigned char result[method->digest_size];

		method->init(context);
		for (unsigned int j = 0; j < 600000; j++)
			method->loop(context, data, sizeof(data));
		method->result(context, result);
		test_assert_strcmp_idx(binary_to_hex(result, method->digest_size),
				       tests[i].hash, i);
	}
	test_end();
}

void test_hash_method(void)
{
	test_hash_method_boundary();
	test_hash_methods_fips();
	if (!ON_VALGRIND)
		test_hash_methods_large();
}
