/*
 * arc4runs.c -- DF-2244 verification KLD.
 *
 * Demonstrates, purely by observation on the running stock kernel, that
 * karc4random_buf(ptr, len) advances the per-cpu arc4 run accounting by
 * exactly ONE run regardless of len (sys/libkern/arc4random.c:152), so a
 * single large request emits an unbounded number of keystream bytes under
 * a single RC4 key and never crosses the ARC4_MAXRUNS (16384) reseed
 * threshold.
 *
 * Method: a reseed (arc4_randomstir) costs a read_random() call (chacha +
 * spinlock) plus an RC4 KSA plus a 768*4-byte keystream discard -- tens of
 * microseconds -- while a plain karc4random() costs a few hundred cycles
 * at most.  We measure rdtsc deltas around every karc4random() call and
 * around karc4random_buf(1MB).
 *
 * Protocol (all on one cpu, verified per phase):
 *   settle : 1 karc4random() (time-based reseed lands here if boot > 300s)
 *   phaseA : 20000 karc4random() -> expect exactly one spike at i ~= 16384
 *            (proves threshold behavior + spike detectability)
 *   phaseB : karc4random_buf(1MB) x5 -> expect NO spike in the first 100
 *            calls of phaseC (proves 5MB charged only 5 runs, not
 *            5*262144 runs); next spike expected near i ~= 12766
 *   phaseC : 20000 karc4random()
 *
 * If accounting were byte-proportional (the fixed behavior), phaseC's very
 * first call would spike (262144 >> 16384 runs pending).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <machine/cpufunc.h>

#define NPH 20000

static uint64_t deltas[NPH];

static MALLOC_DEFINE(M_ARC4RUNS, "arc4runs", "DF-2244 test");

static void
report(const char *tag, int n)
{
	uint64_t mean = 0, maxv = 0, thresh;
	int i, nspike = 0;
	int top_i[5];
	uint64_t top_v[5];
	int t, j;

	for (i = 0; i < n; i++) {
		mean += deltas[i];
		if (deltas[i] > maxv)
			maxv = deltas[i];
	}
	mean /= n;
	thresh = 30 * mean;
	if (thresh < 20000)
		thresh = 20000;

	for (t = 0; t < 5; t++) {
		top_v[t] = 0;
		top_i[t] = -1;
	}
	for (i = 0; i < n; i++) {
		for (t = 0; t < 5; t++) {
			if (deltas[i] > top_v[t]) {
				for (j = 4; j > t; j--) {
					top_v[j] = top_v[j-1];
					top_i[j] = top_i[j-1];
				}
				top_v[t] = deltas[i];
				top_i[t] = i;
				break;
			}
		}
	}

	kprintf("arc4runs: %s n=%d mean=%llu max=%llu thresh=%llu\n",
	    tag, n, (unsigned long long)mean,
	    (unsigned long long)maxv, (unsigned long long)thresh);
	kprintf("arc4runs: %s top5:", tag);
	for (t = 0; t < 5; t++)
		kprintf(" [%d]=%llu", top_i[t], (unsigned long long)top_v[t]);
	kprintf("\n");
	kprintf("arc4runs: %s spikes(i>thresh):", tag);
	for (i = 0; i < n; i++) {
		if (deltas[i] > thresh) {
			kprintf(" %d", i);
			nspike++;
		}
	}
	kprintf("  (count=%d)\n", nspike);
}

static int
phase(const char *tag, int n)
{
	uint64_t t0;
	int i, cpu0 = mycpuid;

	for (i = 0; i < n; i++) {
		t0 = rdtsc();
		karc4random();
		deltas[i] = rdtsc() - t0;
	}
	if (mycpuid != cpu0)
		kprintf("arc4runs: %s WARNING migrated cpu%d->cpu%d, "
		    "results unreliable\n", tag, cpu0, mycpuid);
	report(tag, n);
	return (mycpuid == cpu0 ? 0 : 1);
}

static int
spikes_in_first(const char *tag, int n, int first)
{
	uint64_t mean = 0, thresh;
	int i, cnt = 0;

	for (i = 0; i < n; i++)
		mean += deltas[i];
	mean /= n;
	thresh = 30 * mean;
	if (thresh < 20000)
		thresh = 20000;
	for (i = 0; i < first && i < n; i++)
		if (deltas[i] > thresh)
			cnt++;
	kprintf("arc4runs: %s spikes in FIRST %d calls: %d\n", tag, first, cnt);
	return (cnt);
}

static int
arc4runs_load(struct module *m, int what, void *arg)
{
	uint8_t *mb;
	uint64_t t0, t1;
	int rc = 0;

	switch (what) {
	case MOD_LOAD:
		kprintf("arc4runs: === DF-2244 karc4random_buf run-accounting "
		    "experiment (cpu%d) ===\n", mycpuid);

		/* settle: absorbs any pending time-based reseed */
		t0 = rdtsc();
		karc4random();
		t1 = rdtsc() - t0;
		kprintf("arc4runs: settle delta=%llu%s\n",
		    (unsigned long long)t1,
		    t1 > 20000 ? "  <== time-based reseed spike" : "");

		mb = kmalloc(1 << 20, M_ARC4RUNS, M_WAITOK | M_ZERO);
		if (mb == NULL) {
			kprintf("arc4runs: no mem\n");
			return (ENOMEM);
		}

		rc |= phase("phaseA(20000x karc4random)", NPH);

		/* 5 x 1MiB through karc4random_buf */
		for (int k = 0; k < 5; k++) {
			t0 = rdtsc();
			karc4random_buf(mb, 1 << 20);
			t1 = rdtsc() - t0;
			kprintf("arc4runs: phaseB[%d] karc4random_buf(1MiB) "
			    "delta=%llu first16=%02x%02x%02x%02x%02x%02x%02x%02x"
			    "%02x%02x%02x%02x%02x%02x%02x%02x\n", k,
			    (unsigned long long)t1,
			    mb[0],mb[1],mb[2],mb[3],mb[4],mb[5],mb[6],mb[7],
			    mb[8],mb[9],mb[10],mb[11],mb[12],mb[13],mb[14],mb[15]);
		}

		rc |= phase("phaseC(20000x karc4random)", NPH);
		spikes_in_first("phaseC", NPH, 100);

		/*
		 * Expected on STOCK kernel: phaseA 1 spike near i=16384;
		 * phaseC 0 spikes in first 100 calls (5MiB charged only 5
		 * runs), next spike near i~12700-12800.
		 * Expected on FIXED kernel: phaseC spikes in first 100 != 0
		 * (262144*5 pending runs force immediate reseed).
		 */
		kfree(mb, M_ARC4RUNS);
		kprintf("arc4runs: === done (cpu%d) ===\n", mycpuid);
		break;
	}
	return (rc);
}

DECLARE_MODULE(arc4runs, arc4runs_load, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(arc4runs, 1);
