/*
 * DF-5702 differential harness: DragonFly kernel strtoq/strtouq
 * (sys/libkern/strtoq.c, sys/libkern/strtouq.c) vs the same-named libc
 * API (libc strtoq==strtoll wrapper, strtouq==strtoull wrapper, built from
 * lib/libc/stdlib/_strtol.h / _strtoul.h NetBSD template).
 *
 * Compares, per (input, base):
 *   - VALUE    (identical saturation values expected everywhere)
 *   - ENDPOFF  (endptr - nptr; contract comparison, C99 7.20.1.4)
 *   - ERANGE   (errno==ERANGE after libc call; kernel variant has no errno)
 * Classes:
 *   VALUE-DIFF    value desync kernel vs libc (security-relevant; expect 0)
 *   ENDPTR-DIFF   endptr contract divergence on VALID bases (DF-5702)
 *   ERANGE-ONLY   identical value, libc sets ERANGE, kernel cannot
 *                 (known DF-2230 root cause -- counted, not re-reported)
 *   BASE-DIFF     invalid-base rows (base 1/37/-1): libc EINVAL, kernel
 *                 legacy BSD no-validation behavior (no kernel caller
 *                 passes these; counted separately)
 * Fix validation: k_strtoq_fix/k_strtouq_fix carry ONLY the proposed
 * isxdigit((unsigned char)s[1]) guard; residual diffs must be 0 and no
 * value regressions.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <inttypes.h>
#include <sys/types.h>

int64_t  k_strtoq(const char *, char **, int);
uint64_t k_strtouq(const char *, char **, int);
int64_t  k_strtoq_fix(const char *, char **, int);
uint64_t k_strtouq_fix(const char *, char **, int);

static const char *inputs[] = {
	"", " ", "0", "1", "-1", "+1", "010", "  -12", "\t+9",
	"0x", "0X", "0xg", "0XG", "0x 1", "0xz1", "0xz", "0x0", "0x1",
	"0X7f", "0xdeadbeef", "+0x10", "-0x10", "0x1z", " 0xf",
	"9223372036854775807", "9223372036854775808",
	"-9223372036854775808", "-9223372036854775809",
	"18446744073709551615", "18446744073709551616",
	"-18446744073709551615", "-18446744073709551616",
	"0x7fffffffffffffff", "0x8000000000000000", "0xffffffffffffffff",
	"0x10000000000000000", "-0x8000000000000000", "-0x8000000000000001",
	"0b101", "1e5", "z", "0y2", "1777777777777777777777",
};
static const int bases[] = { 0, 2, 8, 10, 16, 36, 1, 37, -1 };

#define NIN (int)(sizeof(inputs)/sizeof(inputs[0]))
#define NB  (int)(sizeof(bases)/sizeof(bases[0]))

static int value_diffs, endptr_diffs, erange_only, base_sem_diffs;
static int fix_endptr_diffs, fix_value_diffs;

static const char *
esc(const char *s)
{
	static char b[64];
	char *o = b;
	*o++ = '"';
	for (; *s && o < b + sizeof(b) - 8; s++) {
		if (*s == '\t') { *o++ = '\\'; *o++ = 't'; }
		else if ((unsigned char)*s >= 32) *o++ = *s;
		else o += snprintf(o, 8, "\\x%02x", (unsigned char)*s);
	}
	*o++ = '"'; *o = 0;
	return b;
}

static int
valid_base(int base)
{
	return (base == 0 || (base >= 2 && base <= 36));
}

static void
report(const char *tag, const char *fam, const char *in, int base,
    intmax_t kv, int ko, intmax_t lv, int lo, int lerr)
{
	printf("%s %s=%-28s b=%-3d kernel=%20jd (off %3d)  libc=%20jd (off %3d)  libc_ERANGE=%d\n",
	    tag, fam, esc(in), base, kv, ko, lv, lo, lerr);
}

static void
one(const char *in, int base, int uns)
{
	char *sentinel = (char *)0x1;	/* libc EINVAL leaves *endptr untouched */
	char *kp = sentinel, *lp = sentinel, *fp = sentinel;
	uint64_t kv, lv, fv;
	int ko, lo, fo, lerr;

	if (!valid_base(base)) {
		/* legacy-BSD class: kernel has no base validation; libc
		 * errors out (EINVAL, *endptr untouched). No kernel caller
		 * passes an invalid base (all in-tree callers: 0/8/10/16),
		 * so this class is counted once and excluded from the
		 * value/fix comparisons. */
		base_sem_diffs++;
		return;
	}

	errno = 0;
	if (uns) kv = k_strtouq(in, &kp, base); else kv = (uint64_t)k_strtoq(in, &kp, base);
	ko = (kp == sentinel) ? -99 : (int)(kp - in);
	errno = 0;
	if (uns) lv = strtouq(in, &lp, base); else lv = (uint64_t)strtoq(in, &lp, base);
	lo = (lp == sentinel) ? -99 : (int)(lp - in);
	lerr = (errno == ERANGE);
	errno = 0;
	if (uns) fv = k_strtouq_fix(in, &fp, base); else fv = (uint64_t)k_strtoq_fix(in, &fp, base);
	fo = (fp == sentinel) ? -99 : (int)(fp - in);

	if (!uns && (int64_t)kv != (int64_t)lv) {
		value_diffs++;
		report("VALUE-DIFF ", uns ? "u" : "s", in, base,
		    (intmax_t)(int64_t)kv, ko, (intmax_t)(int64_t)lv, lo, lerr);
	} else if (uns && kv != lv) {
		value_diffs++;
		report("VALUE-DIFF ", "u", in, base, (intmax_t)kv, ko, (intmax_t)lv, lo, lerr);
	} else if (ko != lo) {
		endptr_diffs++;
		report("ENDPTR-DIFF", uns ? "u" : "s", in, base,
		    (intmax_t)kv, ko, (intmax_t)lv, lo, lerr);
	} else if (lerr) {
		erange_only++;
		printf("ERANGE-ONLY %s=%-28s b=%-3d value=%20jd (identical saturation; libc sets ERANGE, kernel cannot)\n",
		    uns ? "u" : "s", esc(in), base, (intmax_t)kv);
	}

	if (!uns && ((int64_t)fv != (int64_t)lv)) {
		fix_value_diffs++;
		printf("FIX-VALUE-REGRESSION s=%s b=%d\n", esc(in), base);
	} else if (uns && fv != lv) {
		fix_value_diffs++;
		printf("FIX-VALUE-REGRESSION u=%s b=%d\n", esc(in), base);
	} else if (fo != lo) {
		fix_endptr_diffs++;
		printf("FIX-ENDPTR-RESIDUAL %s=%s b=%d fix_off=%d libc_off=%d\n",
		    uns ? "u" : "s", esc(in), base, fo, lo);
	}
}

int
main(void)
{
	int i, j;

	printf("== kernel sys/libkern/strtoq.c + strtouq.c vs libc strtoq/strtouq (==strtoll/strtoull) ==\n");
	for (i = 0; i < NIN; i++)
		for (j = 0; j < NB; j++) {
			one(inputs[i], bases[j], 0);
			one(inputs[i], bases[j], 1);
		}

	printf("\nSUMMARY:\n");
	printf("  kernel-vs-libc VALUE divergences      : %d  (must be 0)\n", value_diffs);
	printf("  kernel-vs-libc ENDPTR divergences     : %d  (DF-5702: 0x-prefix-no-xdigit class, valid bases)\n", endptr_diffs);
	printf("  invalid-base semantic diffs           : %d  (legacy BSD behavior; no kernel caller passes these)\n", base_sem_diffs);
	printf("  saturation w/o ERANGE (DF-2230 class) : %d  (known, not re-reported)\n", erange_only);
	printf("  patched-kernel residual ENDPTR diffs  : %d  (fix validation; must be 0)\n", fix_endptr_diffs);
	printf("  patched-kernel VALUE regressions      : %d  (must be 0)\n", fix_value_diffs);
	return (value_diffs == 0 && fix_value_diffs == 0 && fix_endptr_diffs == 0) ? 0 : 1;
}
