/*
 * DF-5703 differential harness: DragonFly kernel strtol/strtoul
 * (sys/libkern/strtol.c, sys/libkern/strtoul.c) vs the same-named libc
 * API (built from lib/libc/stdlib/_strtol.h / _strtoul.h NetBSD template,
 * both of which gate the 0x prefix on isxdigit_l(s[1])).
 *
 * 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-5703:
 *                 0x-prefix-without-xdigit class in the int-width twins,
 *                 same body as DF-5702's quad twins)
 *   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)
 * Corpus adds the 32-bit-long boundary rows (2147483647/2147483648/
 * -2147483648/-2147483649/4294967295/4294967296 + hex forms) on top of
 * DF-5702's corpus to stress the int-width cutoff math, plus the live
 * 64-bit-long boundaries (amd64 guest: long==quad_t width).
 * Fix validation: k_strtol_fix/k_strtoul_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>

long           k_strtol(const char *, char **, int);
unsigned long  k_strtoul(const char *, char **, int);
long           k_strtol_fix(const char *, char **, int);
unsigned long  k_strtoul_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",
	/* 32-bit long boundaries (i386 width; ordinary on amd64) */
	"2147483647", "2147483648", "-2147483648", "-2147483649",
	"4294967295", "4294967296",
	"0x7fffffff", "0x80000000", "0xffffffff", "0x100000000",
	/* 64-bit long boundaries (amd64 width, the live case) */
	"9223372036854775807", "9223372036854775808",
	"-9223372036854775808", "-9223372036854775809",
	"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;
	unsigned long 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_strtoul(in, &kp, base); else kv = (unsigned long)k_strtol(in, &kp, base);
	ko = (kp == sentinel) ? -99 : (int)(kp - in);
	errno = 0;
	if (uns) lv = strtoul(in, &lp, base); else lv = (unsigned long)strtol(in, &lp, base);
	lo = (lp == sentinel) ? -99 : (int)(lp - in);
	lerr = (errno == ERANGE);
	errno = 0;
	if (uns) fv = k_strtoul_fix(in, &fp, base); else fv = (unsigned long)k_strtol_fix(in, &fp, base);
	fo = (fp == sentinel) ? -99 : (int)(fp - in);

	if (!uns && (long)kv != (long)lv) {
		value_diffs++;
		report("VALUE-DIFF ", "s", in, base,
		    (intmax_t)(long)kv, ko, (intmax_t)(long)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 && ((long)fv != (long)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/strtol.c + strtoul.c vs libc strtol/strtoul ==\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-5703: 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;
}
