/*
 * eqfuzz.c — pass-2 adversarial harness for sys/dev/sound/pcm/feeder_eq.c
 * (GLM 5.3 re-audit). Compiles the REAL, UNMODIFIED feeder_eq.c in userland
 * (the file is written to allow exactly this) and drives:
 *
 *   T1 generated-table sanity (feed_eq_preamp/coefficient table sizes)
 *   T2 feed_eq_scan_preamp_arg: exhaustive grid + 300k-string fuzz; every
 *      accepted value must map (after the sysctl clamp to +-PREAMP_MAX) to an
 *      in-range feed_eq_preamp index via FEEDEQ_PREAMP2IDX
 *   T3 feed_eq_set full boundary sweep (channels/rate/treble/bass/preamp/state)
 *   T4 feed_eq_feed + biquad soak: every biquad format x channels 1..8 x
 *      all 19 preamp entries x all 19x19 gain pairs, extreme sample patterns,
 *      unaligned counts, partial/zero source returns
 *   T5 feed_eq_init/free churn (leak check via LSan at exit)
 *
 * Build: cc -O1 -g -fsanitize=address,undefined -o eqfuzz eqfuzz.c
 */
#include "eqshim.h"

/* pull in the file under audit, verbatim */
#include "feeder_eq.c"

int eqshim_canary_hit;
const char *eqshim_canary_where = "none";

/* metadata snapshot: fields that no feed must ever modify */
struct eqmeta {
	uintptr_t coeff, biquad;
	uint32_t channels, rate, align;
	int32_t preamp;
	int state;
	int tgain, bgain;
};

static struct eqmeta
snap(struct feed_eq_info *i)
{
	struct eqmeta m;
	m.coeff = (uintptr_t)i->coeff;
	m.biquad = (uintptr_t)i->biquad;
	m.channels = i->channels;
	m.rate = i->rate;
	m.align = i->align;
	m.preamp = i->preamp;
	m.state = i->state;
	m.tgain = i->treble.gain;
	m.bgain = i->bass.gain;
	return (m);
}

static int
verify(struct feed_eq_info *i, struct eqmeta *m)
{
	struct eqmeta now = snap(i);

	kfree_canary_check(i, sizeof(*i));
	if (eqshim_canary_hit)
		return (-1);
	return (memcmp(&now, m, sizeof(now)));
}

/* ---------------- source-feeder shim ---------------- */
static uint32_t src_remaining;
static uint32_t src_pattern;	/* 0=zero 1=max 2=min 3=square 4=prng 5=ramp */
static uint32_t src_partial;	/* return count-partial (0 = exact) */
static uint32_t prng_state = 0x12345678u;

static uint32_t
prng(void)
{
	prng_state ^= prng_state << 13;
	prng_state ^= prng_state >> 17;
	prng_state ^= prng_state << 5;
	return (prng_state);
}

static void
fill_pattern(uint8_t *b, uint32_t bytes)
{
	uint32_t i;
	int32_t v;

	for (i = 0; i + 4 <= bytes; i += 4) {
		switch (src_pattern) {
		case 0: v = 0; break;
		case 1: v = INT32_MAX; break;
		case 2: v = INT32_MIN; break;
		case 3: v = ((i >> 2) & 1) ? INT32_MAX : INT32_MIN; break;
		case 4: v = (int32_t)prng(); break;
		default: v = (int32_t)(i << 8); break;
		}
		memcpy(b + i, &v, 4);
	}
	for (; i < bytes; i++)
		b[i] = (uint8_t)prng();
}

uint32_t
eqshim_source_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b,
    uint32_t count, void *src)
{
	uint32_t give = count;

	(void)f; (void)c; (void)src;
	if (give > src_remaining)
		give = src_remaining;
	if (src_partial != 0 && give > src_partial)
		give = src_partial;
	fill_pattern(b, give);
	src_remaining -= give;
	return (give);
}

int
eqshim_set(struct pcm_feeder *f, int what, int value)
{
	/* the only feeder in the harness is feeder_eq */
	return (feed_eq_set(f, what, value));
}

/* ---------------- assertions ---------------- */
static unsigned long ncheck, nfail;
static void
chk(int cond, const char *what, long a, long b)
{
	ncheck++;
	if (!cond) {
		nfail++;
		printf("FAIL: %s (%ld vs %ld)\n", what, a, b);
	}
}

#define NPREAMP	((int)(sizeof(feed_eq_preamp) / sizeof(feed_eq_preamp[0])))

/* ---------------- T1: tables ---------------- */
static void
t1_tables(void)
{
	chk(NPREAMP == FEEDEQ_LEVELS, "preamp table size == FEEDEQ_LEVELS",
	    NPREAMP, FEEDEQ_LEVELS);
	chk(FEEDEQ_TAB_SIZE == 6, "rate table size", FEEDEQ_TAB_SIZE, 6);
	/* every coefficient table pointer must be a 19-entry block: all rates */
	for (int i = 0; i < FEEDEQ_TAB_SIZE; i++) {
		chk(feed_eq_tab[i].rate >= FEEDEQ_RATE_MIN &&
		    feed_eq_tab[i].rate <= FEEDEQ_RATE_MAX, "tab rate in range",
		    feed_eq_tab[i].rate, 0);
		chk(feed_eq_tab[i].coeff != NULL, "coeff non-null", i, 0);
	}
	/* preamp mul/shift sane: mul > 0, shift in [0,31] */
	for (int i = 0; i < NPREAMP; i++) {
		chk(feed_eq_preamp[i].mul > 0, "preamp mul > 0", i,
		    feed_eq_preamp[i].mul);
		chk(feed_eq_preamp[i].shift >= 0 &&
		    feed_eq_preamp[i].shift <= 31, "preamp shift range", i,
		    feed_eq_preamp[i].shift);
	}
}

/* ---------------- T2: scan_preamp ---------------- */
static int
idx_of(int v)		/* apply the sysctl clamp, then PREAMP2IDX */
{
	if (v < FEEDEQ_PREAMP_MIN)
		v = FEEDEQ_PREAMP_MIN;
	if (v > FEEDEQ_PREAMP_MAX)
		v = FEEDEQ_PREAMP_MAX;
	return (FEEDEQ_PREAMP2IDX(v));
}

static unsigned long nvalid_scan;

static void
scan_check(const char *s)
{
	int32_t v = feed_eq_scan_preamp_arg(s);
	int idx;

	if (v == FEEDEQ_PREAMP_INVALID)
		return;
	nvalid_scan++;
	/* raw scan output must be a plausible IF2PREAMP magnitude */
	chk(v >= -(FEEDEQ_PREAMP_IMAX * 16 + 15) - 1 &&
	    v <= (FEEDEQ_PREAMP_IMAX * 16 + 15) + 1,
	    "scan magnitude sane", v, 0);
	idx = idx_of((int)v);
	chk(idx >= 0 && idx < NPREAMP, "scan->preamp idx in range", idx, v);
}

static void
t2_scan(void)
{
	char s[64];
	int i, f, dB;
	static const char *signs[] = { "", "-", "+" };

	/* exhaustive i x f x dB x sign grid */
	for (i = -200; i <= 200; i++) {
		for (f = -1; f <= 12; f++) {
			for (dB = 0; dB <= 1; dB++) {
				for (size_t sg = 0; sg < 3; sg++) {
					if (f < 0)
						snprintf(s, sizeof(s),
						    "%s%d%s", signs[sg], i,
						    dB ? "dB" : "");
					else
						snprintf(s, sizeof(s),
						    "%s%d.%d%s", signs[sg], i,
						    f, dB ? "dB" : "");
					scan_check(s);
				}
			}
		}
	}

	/* adversarial literals */
	const char *lit[] = {
		"", ".", "-", "+", ".5", "-.", "0.", "0.0dB", "dB", "0.0 db",
		"00.0", "0.00", "1.2.3", "1..2", "--1", "++1", "-+1", "1-",
		"192.0dB", "-192.0dB", "193.0dB", "99999999999999999999.9dB",
		"-99999999999999999999", "0.9999999999dB", "2147483647",
		"-2147483648", "1e3", "0x10", " 1", "1 ", "\t1.1", "1.1d",
		"1.1db", "1.1DB", "1.1Db", "0.0DB", "9.9dB", "-9.9dB",
		"10.0dB", "-10.0dB", "0.10dB", "1.10dB", "6.0dB", "-6.0dB",
		NULL
	};
	for (int k = 0; lit[k] != NULL; k++)
		scan_check(lit[k]);

	/* random fuzz: grammar mix */
	const char *alpha = "0123456789.+-dD bB\x01\xff";
	for (int n = 0; n < 300000; n++) {
		size_t len = prng() % 12;
		for (size_t k = 0; k < len; k++)
			s[k] = alpha[prng() % (sizeof(alpha) - 1)];
		s[len] = '\0';
		scan_check(s);
	}

	/* PREAMP2IDX directly over the entire accepted clamp domain */
	for (int v = FEEDEQ_PREAMP_MIN; v <= FEEDEQ_PREAMP_MAX; v++) {
		int idx = FEEDEQ_PREAMP2IDX(v);
		chk(idx >= 0 && idx < NPREAMP, "PREAMP2IDX in range for every "
		    "in-band v", idx, v);
	}

	printf("T2: %lu valid scans accepted, all -> in-range idx\n",
	    nvalid_scan);
}

/* ---------------- T3: feed_eq_set sweep ---------------- */
static void
mkfeeder(struct pcm_feeder *f, struct pcm_feederdesc *d, uint32_t afmt)
{
	memset(f, 0, sizeof(*f));
	memset(d, 0, sizeof(*d));
	d->type = FEEDER_EQ;
	d->in = d->out = afmt;
	f->desc = d;
	chk(feed_eq_init(f) == 0, "feed_eq_init ok", afmt, 0);
}

static void
t3_set(void)
{
	struct pcm_feeder f;
	struct pcm_feederdesc d;
	struct feed_eq_info *info;
	static const int ch[] = { -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 64,
				  0x7fffffff, -2147483648 };
	static const int rates[] = { -1, 0, 1, 44099, 44100, 44101, 46050,
	    46051, 47999, 48000, 88200, 96000, 176400, 191999, 192000, 192001,
    2016000, 0x7fffffff, -2147483648 };
	static const int tones[] = { -1, 0, 1, 49, 50, 51, 99, 100, 101, 127,
				     0x7fffffff, -2147483648 };
	static const int preamps[] = { -2147483648, -1000, -145, -144, -143,
	    -17, -16, -15, -1, 0, 1, 15, 16, 17, 143, 144, 145, 1000,
	    0x7fffffff };
	static const int states[] = { -1, 0, 1, 2, 3, 6, 7, 8, 9, 255,
				      0x7fffffff };

	mkfeeder(&f, &d, SND_FORMAT(AFMT_S16_LE, 2, 0));
	info = f.data;
	chk(info->channels == 2, "init channels", info->channels, 2);
	chk(info->preamp == FEEDEQ_PREAMP2IDX(FEEDEQ_PREAMP_DEFAULT),
	    "init preamp", info->preamp, 0);

	for (size_t k = 0; k < sizeof(ch) / sizeof(ch[0]); k++) {
		int r = feed_eq_set(&f, FEEDEQ_CHANNELS, ch[k]);
		chk((ch[k] >= 1 && ch[k] <= 8) ? r == 0 : r == EINVAL,
		    "FEEDEQ_CHANNELS bounds", ch[k], r);
		if (r == 0)
			chk(info->channels == (uint32_t)ch[k], "channels set",
			    info->channels, ch[k]);
	}
	for (size_t k = 0; k < sizeof(rates) / sizeof(rates[0]); k++) {
		int r = feed_eq_set(&f, FEEDEQ_RATE, rates[k]);
		int expect = feeder_eq_validrate((uint32_t)rates[k]) ? 0 : EINVAL;
		chk(r == expect, "FEEDEQ_RATE valid/invalid", rates[k], r);
		if (r == 0)
			chk(info->coeff != NULL, "coeff set after rate", 0, 0);
	}
	for (size_t k = 0; k < sizeof(tones) / sizeof(tones[0]); k++) {
		int r = feed_eq_set(&f, FEEDEQ_TREBLE, tones[k]);
		chk((tones[k] >= 0 && tones[k] <= 100) ? r == 0 : r == EINVAL,
		    "FEEDEQ_TREBLE bounds", tones[k], r);
		if (r == 0)
			chk(info->treble.gain >= 0 &&
			    info->treble.gain < FEEDEQ_LEVELS,
			    "treble gain idx", info->treble.gain, tones[k]);
		r = feed_eq_set(&f, FEEDEQ_BASS, tones[k]);
		chk((tones[k] >= 0 && tones[k] <= 100) ? r == 0 : r == EINVAL,
		    "FEEDEQ_BASS bounds", tones[k], r);
	}
	for (size_t k = 0; k < sizeof(preamps) / sizeof(preamps[0]); k++) {
		int r = feed_eq_set(&f, FEEDEQ_PREAMP, preamps[k]);
		int v = preamps[k];
		chk((v >= FEEDEQ_PREAMP_MIN && v <= FEEDEQ_PREAMP_MAX) ?
		    r == 0 : r == EINVAL, "FEEDEQ_PREAMP bounds", v, r);
		if (r == 0) {
			chk(info->preamp >= 0 && info->preamp < NPREAMP,
			    "preamp idx in range", info->preamp, v);
			chk(FEEDEQ_PREAMP2IDX(v) == info->preamp,
			    "preamp idx matches macro", info->preamp, v);
		}
	}
	for (size_t k = 0; k < sizeof(states) / sizeof(states[0]); k++) {
		int v = states[k];
		int r = feed_eq_set(&f, FEEDEQ_STATE, v);
		int ok = (v == FEEDEQ_DISABLE || v == FEEDEQ_ENABLE ||
		    v == FEEDEQ_BYPASS);
		chk(ok ? r == 0 : r == EINVAL, "FEEDEQ_STATE bounds", v, r);
	}
	/* unknown op */
	chk(feed_eq_set(&f, 12345, 0) == EINVAL, "unknown set op", 0, 0);

	feed_eq_free(&f);
	chk(f.data == NULL, "free NULLs data", 0, 0);
	chk(eqshim_canary_hit == 0, "t3 no canary hit", 0, 0);
}

/* ---------------- T4: biquad / feed soak ---------------- */
#define MAXB (65536 + 64)

static void
t4_soak(void)
{
	struct pcm_feeder f;
	struct pcm_feederdesc d;
	struct feed_eq_info *info;
	static uint8_t buf[MAXB] __attribute__((aligned(16)));
	static const uint32_t fmts[] = { AFMT_S16_LE, AFMT_S32_LE };
	static const uint32_t counts[] = { 1, 2, 3, 4, 5, 7, 8, 15, 16, 17,
	    31, 32, 33, 63, 64, 48, 96, 100, 253, 4095, 4096, 65535, 65536 };
	/* NB: >65536 (SND_FXDIV_MAX) is out of the kernel's precondition:
	   sndbuf_feed caps every feed at FXROUND(65536, align). */
	int tre, bas, pa, st, pat, ci;
	uint32_t fi, ch;
	unsigned long feeds = 0;

	for (fi = 0; fi < sizeof(fmts) / sizeof(fmts[0]); fi++) {
		for (ch = 1; ch <= 8; ch++) {
			uint32_t afmt = SND_FORMAT(fmts[fi], ch, 0);
			mkfeeder(&f, &d, afmt);
			info = f.data;

			for (pa = 0; pa < NPREAMP; pa++) {
				/* set preamp via raw index-equivalent value:
				   walk the exact IF2PREAMP values the sysctl
				   can produce, covering all table rows by
				   direct index selection */
				int v = (pa <= 9) ? -((9 - pa) << 4) :
				    ((pa - 9) << 4);
				chk(feed_eq_set(&f, FEEDEQ_PREAMP, v) == 0,
				    "soak preamp set", v, pa);
				chk((int)info->preamp == pa, "soak preamp idx",
				    info->preamp, pa);

				for (tre = 0; tre < FEEDEQ_LEVELS; tre += 3) {
					for (bas = 0; bas < FEEDEQ_LEVELS;
					    bas += 2) {
						feed_eq_set(&f, FEEDEQ_TREBLE,
						    (tre * 100) / 19 > 100 ?
						    100 : (tre * 100) / 19);
						feed_eq_set(&f, FEEDEQ_BASS,
						    (bas * 100) / 19 > 100 ?
						    100 : (bas * 100) / 19);
				for (st = 0; st < 3; st++) {
					int sv = st == 0 ? FEEDEQ_DISABLE :
					    (st == 1 ? FEEDEQ_ENABLE :
					    FEEDEQ_BYPASS);
					feed_eq_set(&f, FEEDEQ_STATE, sv);
				for (pat = 0; pat < 6; pat++) {
				for (ci = 0; ci < (int)(sizeof(counts) /
				    sizeof(counts[0])); ci++) {
					uint32_t cnt = counts[ci];
					uint32_t align = info->align;
					struct eqmeta m;

					if (cnt > MAXB)
						continue;
					src_pattern = (uint32_t)pat;
					src_remaining = cnt;
					src_partial = (pat == 4) ? 7 : 0;
					m = snap(info);
					uint32_t got = feed_eq_feed(&f, NULL,
					    buf, cnt, NULL);
					chk(got <= cnt, "feed <= count",
					    got, cnt);
					/* BYPASS passes raw bytes through */
					if (sv != FEEDEQ_BYPASS)
						chk((got % align) == 0,
						    "feed multiple of align",
						    got, align);
					chk(verify(info, &m) == 0,
					    "feed corrupts no metadata", 0,
					    0);
					chk(eqshim_canary_hit == 0,
					    "feed hits no canary", 0, 0);
					feeds++;
				}
				}
				}
					}
				}
			}
			/* full-rate sweep for this format/ch */
			feed_eq_set(&f, FEEDEQ_STATE, FEEDEQ_ENABLE);
			for (int i = 0; i < FEEDEQ_TAB_SIZE; i++) {
				chk(feed_eq_set(&f, FEEDEQ_RATE,
				    (int)feed_eq_tab[i].rate) == 0,
				    "soak rate", feed_eq_tab[i].rate, 0);
				src_pattern = 3;
				src_remaining = 4096;
				src_partial = 0;
				feed_eq_feed(&f, NULL, buf, 4096, NULL);
			}
			feed_eq_free(&f);
			chk(eqshim_canary_hit == 0, "t4 no canary hit",
			    ch, 0);
		}
	}
	printf("T4: %lu feeds exercised\n", feeds);
}

/* ---------------- T5: init/free churn ---------------- */
static void
t5_churn(void)
{
	struct pcm_feeder f;
	struct pcm_feederdesc d;

	for (uint32_t ch = 1; ch <= 8; ch++) {
		for (int b = 0; b < 2; b++) {
			uint32_t afmt = SND_FORMAT(b ? AFMT_S32_LE :
			    AFMT_S16_LE, ch, 0);
			mkfeeder(&f, &d, afmt);
			feed_eq_free(&f);
		}
	}
	/* init with unsupported format must fail cleanly */
	memset(&f, 0, sizeof(f));
	memset(&d, 0, sizeof(d));
	d.type = FEEDER_EQ;
	d.in = d.out = 0x00000004;	/* AFMT_IMA_ADPCM: no biquad op */
	f.desc = &d;
	chk(feed_eq_init(&f) == EINVAL, "init rejects unsupported fmt", 0, 0);
	chk(f.data == NULL, "failed init leaves no leak", 0, 0);
	/* in != out rejected */
	d.in = AFMT_S16_LE; d.out = AFMT_S32_LE;
	chk(feed_eq_init(&f) == EINVAL, "init rejects in!=out", 0, 0);
}

int
main(int argc, char **argv)
{
	if (argc > 1 && strcmp(argv[1], "oob") == 0) {
		/*
		 * Deliberate demonstration of the missing SND_CHN_MAX guard in
		 * feed_eq_init (feeder_eq.c:362): a 16-channel format flows
		 * straight into info->channels while the tone arrays are
		 * [SND_CHN_MAX=8] (feeder_eq.c:109-115). All kernel-reachable
		 * callers clamp today (dsp.c:1499, mixer.c:260) — this run
		 * proves the consequence if that invariant ever breaks.
		 */
		struct pcm_feeder f;
		struct pcm_feederdesc d;
		struct eqmeta m;

		mkfeeder(&f, &d, SND_FORMAT(AFMT_S16_LE, 16, 0));
		struct feed_eq_info *info = f.data;
		chk(info->channels == 16, "init channels unchecked", 16,
		    (int)info->channels);
		m = snap(info);
		src_pattern = 4; src_remaining = 4096; src_partial = 0;
		feed_eq_feed(&f, NULL, (uint8_t *)malloc(4096), 4096, NULL);
		int bad = verify(info, &m);
		printf("OOB-DEMO: metadata-corruption=%d canary_hit=%d (%s)\n",
		    bad != 0, eqshim_canary_hit, eqshim_canary_where);
		feed_eq_free(&f);
		return (0);
	}

	printf("=== feeder_eq pass-2 harness (real file, canary+checksum) ===\n");
	t1_tables();
	t2_scan();
	t3_set();
	t4_soak();
	t5_churn();
	printf("checks=%lu failures=%lu  -> %s\n", ncheck, nfail,
	    nfail ? "FAILED" : "ALL PASS");
	return (nfail ? 1 : 0);
}
