/*
 * DF-5679 — differential / known-answer / boundary harness for
 * sys/libkern/icrc32.c (and its sibling sys/libkern/crc32.c which
 * shares crc32_tab).
 *
 * Compiled in USERLAND against the pristine kernel sources (copies of
 * icrc32.c + crc32.c must sit next to this file). Checks:
 *
 *  T1  all 256 entries of crc32_tab         == generated table, poly 0xEDB88320
 *  T2  all 256 entries of crc32Table (C)    == generated table, poly 0x82F63B78
 *  T3  sctp_crc_tableil8_o32 == crc32Table
 *  T4  sliced tables o40..o88 == iterative slicing construction
 *      T_{k+1}[i] = (T_k[i] >> 8) ^ T_0[T_k[i] & 0xff]
 *  T5  known-answer vectors ("123456789" and ""):
 *        crc32      -> 0xCBF43926 / 0x00000000
 *        iscsi_crc32-> 0xE3069283 / 0x00000000
 *  T6  functional equivalence for every length 0..600 x alignment 0..3:
 *        iscsi_crc32          == bitwise reference CRC-32C (init/xorout ~0)
 *        calculate_crc32c     == bitwise reference raw (no invert)
 *        singletable_crc32c   == bitwise raw   (single-table engine, all L)
 *        crc32 / crc32_ext    == bitwise reference CRC-32   (init/xorout ~0)
 *  T7  incremental chaining semantics:
 *        ext(ext(0,a),b) == one-shot(a||b)   [iscsi_crc32_ext, crc32_ext,
 *        calculate_crc32c raw chaining]      with random seeds too
 *  T8  1 MiB random buffer, multitable path vs bitwise reference
 *  T9  guard-page OOB-read detector: data ends exactly at a PROT_NONE
 *      page; L = 0..255 (covers every L mod 8 x every effective buffer
 *      alignment). Any read past p+L faults -> FAIL.
 *  T10 size_t -> unsigned int length truncation demo:
 *        iscsi_crc32(buf9, 0x100000009ULL) == iscsi_crc32(buf9, 9)
 *      The kernel API takes size_t but calculate_crc32c() truncates to
 *      unsigned int, so a 4 GiB + 9 request is silently serviced as 9.
 *
 * Exit 0 iff every check passes. Deterministic (fixed xorshift seed).
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>
#include <setjmp.h>
#include <signal.h>
#include <sys/mman.h>
#include <unistd.h>

#if defined(__linux__)
#include <endian.h>	/* host smoke-compile only */
#endif

/* the code under test, verbatim */
#include "icrc32.c"
#include "crc32.c"

static uint32_t fails, checks;

#define CHECK(cond, name, ...) do {					\
	checks++;							\
	if (!(cond)) {							\
		fails++;						\
		printf("FAIL %-28s ", name);				\
		printf(__VA_ARGS__);					\
		printf("\n");						\
	}								\
} while (0)

/* ---------- reference implementations (bitwise) ---------- */

static void
gen_table(uint32_t poly, uint32_t t[256])
{
	for (unsigned i = 0; i < 256; i++) {
		uint32_t c = i;
		for (int k = 0; k < 8; k++)
			c = (c >> 1) ^ (poly & (0u - (c & 1u)));
		t[i] = c;
	}
}

/* ---------- clean bitwise reference (no tables at all) ---------- */

static uint32_t
ref_raw(uint32_t crc, const unsigned char *p, size_t n, uint32_t poly)
{
	for (size_t i = 0; i < n; i++) {
		crc ^= p[i];
		for (int k = 0; k < 8; k++)
			crc = (crc >> 1) ^ (poly & (0u - (crc & 1u)));
	}
	return crc;
}

#define POLY_CRC32  0xEDB88320u	/* reflected 0x04C11DB7 */
#define POLY_CRC32C 0x82F63B78u	/* reflected 0x1EDC6F41 */

static uint32_t
ref_crc32(const void *buf, size_t n)			/* zlib-style */
{
	return ref_raw(0xFFFFFFFFu, buf, n, POLY_CRC32) ^ 0xFFFFFFFFu;
}

static uint32_t
ref_crc32c(const void *buf, size_t n)			/* iSCSI/RFC3720 style */
{
	return ref_raw(0xFFFFFFFFu, buf, n, POLY_CRC32C) ^ 0xFFFFFFFFu;
}

/* ---------- deterministic PRNG ---------- */

static uint64_t
rs64(uint64_t *s)
{
	uint64_t x = *s;
	x ^= x << 13; x ^= x >> 7; x ^= x << 17;
	return *s = x;
}

/* ---------- guard-page machinery (T9) ---------- */

static sigjmp_buf oob_jmp;
static volatile sig_atomic_t in_probe;

static void
oob_sighandler(int sig)
{
	(void)sig;
	if (in_probe)
		siglongjmp(oob_jmp, 1);
}

typedef uint32_t (*fn1)(const void *, size_t);

static int
probe_guard(fn1 fn, const unsigned char *p, size_t n)
{
	in_probe = 1;
	if (sigsetjmp(oob_jmp, 1) == 0) {
		fn(p, n);
		in_probe = 0;
		return 0;		/* no fault */
	}
	in_probe = 0;
	return 1;			/* faulted: OOB read */
}

static uint32_t
call_calc_c(const void *p, size_t n)	/* calculate_crc32c wrapper for probe */
{
	return calculate_crc32c(0xFFFFFFFFu, p, (unsigned)n);
}

int
main(void)
{
	uint64_t seed = 0x4446353633373931ULL;	/* "DF5679" */
	static uint32_t gt[256];

	/* ---------- T1: crc32_tab ---------- */
	gen_table(POLY_CRC32, gt);
	int bad = 0;
	for (int i = 0; i < 256; i++)
		if (crc32_tab[i] != gt[i]) bad++;
	CHECK(bad == 0, "T1 crc32_tab", "%d bad entries", bad);

	/* ---------- T2: crc32Table (CRC-32C) ---------- */
	gen_table(POLY_CRC32C, gt);
	bad = 0;
	for (int i = 0; i < 256; i++)
		if (crc32Table[i] != gt[i]) bad++;
	CHECK(bad == 0, "T2 crc32Table", "%d bad entries", bad);

	/* ---------- T3: o32 == single table ---------- */
	bad = 0;
	for (int i = 0; i < 256; i++)
		if (sctp_crc_tableil8_o32[i] != crc32Table[i]) bad++;
	CHECK(bad == 0, "T3 o32==single", "%d bad entries", bad);

	/* ---------- T4: sliced-by-8 construction ---------- */
	{
		const uint32_t *sl[8] = { sctp_crc_tableil8_o32,
		    sctp_crc_tableil8_o40, sctp_crc_tableil8_o48,
		    sctp_crc_tableil8_o56, sctp_crc_tableil8_o64,
		    sctp_crc_tableil8_o72, sctp_crc_tableil8_o80,
		    sctp_crc_tableil8_o88 };
		int tbad = 0;
		for (int k = 1; k < 8; k++) {
			int kbad = 0;
			for (int i = 0; i < 256; i++) {
				uint32_t prev = sl[k - 1][i];
				uint32_t want = (prev >> 8) ^ sl[0][prev & 0xff];
				if (sl[k][i] != want) kbad++;
			}
			if (kbad) {
				tbad += kbad;
				printf("FAIL T4 slice o%d: %d bad entries\n",
				    32 + 8 * k, kbad);
			}
		}
		CHECK(tbad == 0, "T4 sliced o40..o88", "%d bad", tbad);
	}

	/* ---------- T5: known-answer vectors ---------- */
	const char *kat = "123456789";
	CHECK(crc32(kat, 9) == 0xCBF43926u, "T5 crc32 KAT", "%08"PRIx32,
	    crc32(kat, 9));
	CHECK(iscsi_crc32(kat, 9) == 0xE3069283u, "T5 crc32c KAT", "%08"PRIx32,
	    iscsi_crc32(kat, 9));
	CHECK(crc32(kat, 0) == 0, "T5 crc32 empty", "%08"PRIx32,
	    crc32(kat, 0));
	CHECK(iscsi_crc32(kat, 0) == 0, "T5 crc32c empty", "%08"PRIx32,
	    iscsi_crc32(kat, 0));
	CHECK(singletable_crc32c(0xFFFFFFFFu, (const unsigned char *)kat, 9) ==
	    (0xE3069283u ^ 0xFFFFFFFFu), "T5 raw single", "%08"PRIx32,
	    singletable_crc32c(0xFFFFFFFFu, (const unsigned char *)kat, 9));

	/* ---------- T6: functional equivalence, all L x alignment ---------- */
	{
		unsigned char *big = malloc(4096 + 64 + 640);
		for (size_t i = 0; i < 4096 + 64 + 640; i++)
			big[i] = (unsigned char)rs64(&seed);
		int e1 = 0, e2 = 0, e3 = 0, e4 = 0;
		for (size_t L = 0; L <= 600; L++) {
			for (int a = 0; a < 4; a++) {
				unsigned char *p = big + 64 + a;
				uint32_t s = (uint32_t)rs64(&seed);
				if (iscsi_crc32(p, L) != ref_crc32c(p, L)) e1++;
				if (calculate_crc32c(s, p, (unsigned)L) !=
				    ref_raw(s, p, L, POLY_CRC32C)) e2++;
				if (singletable_crc32c(s, p, L) !=
				    ref_raw(s, p, L, POLY_CRC32C)) e3++;
				if (crc32(p, L) != ref_crc32(p, L)) e4++;
			}
		}
		CHECK(e1 == 0, "T6 iscsi_crc32", "%d mismatches", e1);
		CHECK(e2 == 0, "T6 calculate_crc32c", "%d mismatches", e2);
		CHECK(e3 == 0, "T6 singletable", "%d mismatches", e3);
		CHECK(e4 == 0, "T6 crc32", "%d mismatches", e4);
		free(big);
	}

	/* ---------- T7: incremental chaining semantics ---------- */
	{
		unsigned char a[97], b[131], cat[228];
		for (size_t i = 0; i < sizeof(a); i++) a[i] = (unsigned char)rs64(&seed);
		for (size_t i = 0; i < sizeof(b); i++) b[i] = (unsigned char)rs64(&seed);
		memcpy(cat, a, sizeof(a));
		memcpy(cat + sizeof(a), b, sizeof(b));
		int e = 0;

		uint32_t s1 = iscsi_crc32_ext(a, sizeof(a), 0);
		s1 = iscsi_crc32_ext(b, sizeof(b), s1);
		if (s1 != iscsi_crc32(cat, sizeof(cat))) e++;

		uint32_t s2 = crc32_ext(a, sizeof(a), 0);
		s2 = crc32_ext(b, sizeof(b), s2);
		if (s2 != crc32(cat, sizeof(cat))) e++;

		uint32_t seed0 = (uint32_t)rs64(&seed);
		uint32_t s3 = calculate_crc32c(seed0, a, sizeof(a));
		s3 = calculate_crc32c(s3, b, sizeof(b));
		if (s3 != ref_raw(seed0, cat, sizeof(cat), POLY_CRC32C)) e++;

		/* chunked by 1 and by 3 must equal one-shot (boundary churn) */
		/* chunked by 1 and by 3 must equal one-shot (boundary churn) */
		uint32_t s4 = 0;
		for (size_t i = 0; i < sizeof(cat); i++)
			s4 = iscsi_crc32_ext(cat + i, 1, s4);
		uint32_t s5 = 0;
		for (size_t i = 0; i + 3 <= sizeof(cat); i += 3)
			s5 = iscsi_crc32_ext(cat + i, 3, s5);	/* 76*3==228 */

		CHECK(s1 == iscsi_crc32(cat, sizeof(cat)), "T7 ext chain",
		    "%"PRIx32" != %"PRIx32, s1, iscsi_crc32(cat, sizeof(cat)));
		CHECK(s2 == crc32(cat, sizeof(cat)), "T7 crc32_ext chain",
		    "%"PRIx32" != %"PRIx32, s2, crc32(cat, sizeof(cat)));
		CHECK(s3 == ref_raw(seed0, cat, sizeof(cat), POLY_CRC32C),
		    "T7 calc chain", "%"PRIx32, s3);
		CHECK(s4 == iscsi_crc32(cat, sizeof(cat)), "T7 chain-by-1",
		    "%"PRIx32" != %"PRIx32, s4, iscsi_crc32(cat, sizeof(cat)));
		CHECK(s5 == iscsi_crc32(cat, sizeof(cat)), "T7 chain-by-3",
		    "%"PRIx32" != %"PRIx32, s5, iscsi_crc32(cat, sizeof(cat)));
	}

	/* ---------- T8: 1 MiB random ---------- */
	{
		size_t n = 1u << 20;
		unsigned char *p = malloc(n);
		for (size_t i = 0; i < n; i++) p[i] = (unsigned char)rs64(&seed);
		uint32_t got = iscsi_crc32(p, n);
		uint32_t want = ref_crc32c(p, n);
		CHECK(got == want, "T8 1MiB", "got %08"PRIx32" want %08"PRIx32,
		    got, want);
		free(p);
	}

	/* ---------- T9: guard-page OOB probe ---------- */
	{
		size_t ps = (size_t)sysconf(_SC_PAGESIZE);
		unsigned char *map = mmap(NULL, 2 * ps, PROT_READ | PROT_WRITE,
		    MAP_ANON | MAP_PRIVATE, -1, 0);
		if (map == MAP_FAILED) {
			CHECK(0, "T9 mmap", "failed");
		} else {
			mprotect(map + ps, ps, PROT_NONE);
			for (size_t i = 0; i < ps; i++)
				map[i] = (unsigned char)rs64(&seed);
			void (*oldsegv)(int) = signal(SIGSEGV, oob_sighandler);
			void (*oldbus)(int)  = signal(SIGBUS,  oob_sighandler);
			int faults = 0, probes = 0;
			for (size_t L = 0; L <= 255; L++) {
				unsigned char *p = map + ps - L;
				/* effective alignment = (-L)&3 cycles 0..3 */
				probes += 3;
				faults += probe_guard(iscsi_crc32, p, L);
				faults += probe_guard(call_calc_c, p, L);
				faults += probe_guard(crc32, p, L);
			}
			signal(SIGSEGV, oldsegv);
			signal(SIGBUS, oldbus);
			CHECK(faults == 0, "T9 guard OOB", "%d/%d probes faulted",
			    faults, probes);
			munmap(map, 2 * ps);
		}
	}

	/* ---------- T10: size_t -> unsigned int truncation ---------- */
	{
		unsigned char b9[64];
		for (size_t i = 0; i < sizeof(b9); i++)
			b9[i] = (unsigned char)rs64(&seed);
		uint32_t huge = iscsi_crc32(b9, 0x100000009ULL);	/* 4GiB+9 */
		uint32_t nine = iscsi_crc32(b9, 9);
		printf("T10 truncation: iscsi_crc32(buf, 0x100000009)=%08"PRIx32
		    "  iscsi_crc32(buf, 9)=%08"PRIx32"  %s\n",
		    huge, nine, huge == nine ? "TRUNCATED (equal)" : "honored");
		CHECK(huge == nine, "T10 truncation demo",
		    "size 0x100000009 was %s",
		    huge == nine ? "truncated to 9" : "honored");
	}

	printf("RESULT: %s (%"PRIu32" checks, %"PRIu32" failures)\n",
	    fails ? "FAIL" : "ALL-PASS", checks, fails);
	return fails ? 1 : 0;
}
