/*
 * ffmat.c — adversarial harness for the REAL sys/dev/sound/pcm/feeder_matrix.c
 * (GLM 5.3 pass-2, FXROUND follow-up; DF-4847).
 *
 * T1  entry-rounding overshoot (:479): counts inside the claimed-exact
 *     domain where SND_FXROUND(count, oalign) > count => pass sized past the
 *     window; source writes past the window and/or return > count.
 * T2  ialign>oalign (:501): FXROUND(count - oalign, ialign) misrounds =>
 *     j > count - oalign (+ up to ialign-1) => src = dst + count - j < dst
 *     (write BEFORE the window) and source window j exceeds the destination.
 * T3  ialign<oalign (:504): (FXDIV(count, oalign) - 1) * ialign is exact
 *     unless FXDIV itself misrounds (counts > domain top) — covered by soak.
 */
#include "fxshim.h"
#include "feeder_matrix.c"

static struct pcm_feeder f;

static int
mat_setup(uint32_t in, uint32_t out)
{
	memset(&f, 0, sizeof(f));
	f.desc = &f.desc_static;
	f.desc->in = in;
	f.desc->out = out;
	if (feed_matrix_init(&f) != 0)
		return (-1);
	return (0);
}

int
fx_feeder_set(struct pcm_feeder *fe, int what, int value)
{
	(void)fe; (void)what; (void)value;
	return (0);
}

static uint64_t fails, checks;

#define RESET_AUDIT(win, sz) do {					\
	fx_audit_max_off = 0; fx_audit_max_ask = 0; fx_audit_given = 0;	\
	fx_audit_asks = 0; fx_audit_wild = 0;				\
	fx_win_base = (uintptr_t)(win);					\
	fx_win_limit = (uintptr_t)(win) + (sz);				\
	fx_arena_base = fx_win_base - FX_GUARD_FRONT;			\
	fx_arena_limit = fx_win_base + (sz) + 65536;			\
} while (0)

static void
report(const char *tag, uint32_t count, uint32_t ret, const char *where,
    uint64_t maxoff, uint64_t maxask, uint32_t inch, uint32_t outch)
{
	printf("FAIL[%s] in_ch=%u out_ch=%u count=%u ret=%u %s "
	    "maxwrite_off=%llu maxask=%llu\n",
	    tag, inch, outch, count, ret, where ? where : "-",
	    (unsigned long long)maxoff, (unsigned long long)maxask);
	fails++;
}

static const uint32_t bad_aligns[] = { 3, 5, 6, 7, 10, 12, 14, 15, 20, 21, 24, 28, 30 };

int
main(void)
{
	struct feed_matrix_info *info;
	uint32_t inch, outch, count, ret, d, found, ai;
	uint8_t *win;
	const char *where;
	uint32_t prng = 0x85ebca6bu;
	int t;

	/* S16 throughout (matrix keeps bps): ialign=2*inch, oalign=2*outch */
	static const struct { uint32_t i, o; const char *tag; } combos[] = {
		{ 6, 2, "T1 downmix oalign=4? no:4 pow2 -> safe" },
		{ 2, 6, "T2 upmix ialign=4<oalign=12" },
		{ 7, 5, "T2b ialign=14>oalign=10" },
		{ 5, 7, "T1b ialign=10<oalign=14" },
		{ 6, 5, "T2c ialign=12>oalign=10" },
		{ 5, 6, "T1c ialign=10<oalign=12" },
		{ 8, 7, "T2d ialign=16>oalign=14" },
		{ 7, 8, "T1d ialign=14<oalign=16(pw2)" },
		{ 3, 6, "T1e ialign=6<oalign=12" },
		{ 6, 3, "T2e ialign=12>oalign=6" },
	};

	for (t = 0; t < (int)(sizeof(combos) / sizeof(combos[0])); t++) {
		inch = combos[t].i;
		outch = combos[t].o;
		uint32_t in  = SND_FORMAT(AFMT_S16_LE, inch, 0);
		uint32_t out = SND_FORMAT(AFMT_S16_LE, outch, 0);
		if (mat_setup(in, out) != 0) {
			printf("setup failed in=%08x out=%08x\n", in, out);
			continue;
		}
		info = f.data;
		for (ai = 0; ai < sizeof(bad_aligns) / sizeof(bad_aligns[0]); ai++) {
			d = bad_aligns[ai];
			if (d != info->oalign && d != info->ialign)
				continue;
			found = 0;
			for (count = 1; count <= 65536 && found < 300; count++) {
				if (SND_FXROUND(count, d) <= count)
					continue;
				found++;
				fx_src_ready = 1 << 20;
				fx_src_give = 0;
				win = fx_win_alloc(count);
				RESET_AUDIT(win, count);
				ret = feed_matrix_feed(&f, NULL, win, count, NULL);
				where = NULL;
				checks++;
				if (fx_win_check(win, count, &where) != 0)
					report(combos[t].tag, count, ret, where,
					    fx_audit_max_off, fx_audit_max_ask, inch, outch);
				else if (ret > count)
					report(combos[t].tag, count, ret,
					    "return > count (sndbuf_acquire ring overflow)",
					    fx_audit_max_off, fx_audit_max_ask, inch, outch);
				else if (fx_audit_max_off > count)
					report(combos[t].tag, count, ret,
					    "source wrote past window",
					    fx_audit_max_off, fx_audit_max_ask, inch, outch);
				fx_win_free(win);
			}
		}
		feed_matrix_free(&f);
	}

	/* random soak across channel combos and partial sources */
	for (t = 0; t < 30000; t++) {
		prng ^= prng << 13; prng ^= prng >> 17; prng ^= prng << 5;
		inch = 1 + (prng % 8);
		prng ^= prng << 13; prng ^= prng >> 17; prng ^= prng << 5;
		outch = 1 + (prng % 8);
		if (inch == outch)
			continue;
		uint32_t in  = SND_FORMAT(AFMT_S16_LE, inch, 0);
		uint32_t out = SND_FORMAT(AFMT_S16_LE, outch, 0);
		if (mat_setup(in, out) != 0)
			continue;
		prng ^= prng << 13; prng ^= prng >> 17; prng ^= prng << 5;
		count = 4096 + (prng % 61441);
		prng ^= prng << 13; prng ^= prng >> 17; prng ^= prng << 5;
		fx_src_ready = (prng % 3 == 0) ? (prng % count) : (1 << 20);
		fx_src_give = 0;
		win = fx_win_alloc(count);
		RESET_AUDIT(win, count);
		ret = feed_matrix_feed(&f, NULL, win, count, NULL);
		where = NULL;
		checks++;
		if (fx_win_check(win, count, &where) != 0)
			report("soak", count, ret, where, fx_audit_max_off,
			    fx_audit_max_ask, inch, outch);
		else if (ret > count)
			report("soak", count, ret, "return > count",
			    fx_audit_max_off, fx_audit_max_ask, inch, outch);
		fx_win_free(win);
		feed_matrix_free(&f);
	}

	printf("ffmat: checks=%llu failures=%llu -> %s\n",
	    (unsigned long long)checks, (unsigned long long)fails,
	    fails ? "FAIL" : "ALL PASS");
	return (fails != 0);
}
