/*-
 * feeder_chain.c pass-2 adversarial stress: hammer every feeder_chain()
 * rebuild entry (SNDCTL_DSP_SETFMT / CHANNELS / SPEED on hw + vchan dsp
 * nodes), including garbage encodings, unmappable channel counts, extreme
 * speeds, AC3 passthrough, RESET/SYNC interleaves and short writes.
 *
 * usage: feeder_fuzz <dev> <seconds> [seed]
 */
#include <sys/soundcard.h>
#include <err.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static uint32_t encodings[] = {
	AFMT_S8, AFMT_U8, AFMT_S16_LE, AFMT_S16_BE, AFMT_U16_LE, AFMT_U16_BE,
	AFMT_S24_LE, AFMT_S24_BE, AFMT_U24_LE, AFMT_U24_BE,
	AFMT_S32_LE, AFMT_S32_BE, AFMT_U32_LE, AFMT_U32_BE,
	AFMT_MU_LAW, AFMT_A_LAW, AFMT_AC3,
	/* garbage encodings: raw user bits reach chn_setformat() verbatim */
	0xffffffff, 0xdeadbeef, 0x00000001, 0xf00fffff, 0x80000000,
	0x00300000, 0x00800000,
};
#define NENC (int)(sizeof(encodings) / sizeof(encodings[0]))

static int speeds[] = {
	1, 2, 3, 3000, 8000, 11025, 22050, 44100, 48000, 96000, 192000,
	384000, 1000000, 2016000, 2016001, 0x7fffffff,
};
#define NSPD (int)(sizeof(speeds) / sizeof(speeds[0]))

static volatile sig_atomic_t stop;

static void
onalarm(int s __unused)
{
	stop = 1;
}

int
main(int argc, char **argv)
{
	const char *dev;
	int fd, tmo, i, ret, v;
	unsigned long iters = 0, ioctl_ok = 0, ioctl_err = 0;
	unsigned seed;

	if (argc < 3) {
		fprintf(stderr, "usage: %s <dev> <seconds> [seed]\n", argv[0]);
		return (2);
	}
	dev = argv[1];
	tmo = atoi(argv[2]);
	seed = (argc > 3) ? strtoul(argv[3], NULL, 10) : 42;
	srandom(seed);

	signal(SIGALRM, onalarm);
	alarm(tmo);

	for (;;) {
		fd = open(dev, O_WRONLY | O_NONBLOCK);
		if (fd < 0) {
			if (stop)
				break;
			usleep(1000);
			continue;
		}
		while (!stop) {
			iters++;
			switch (random() % 12) {
			case 0:	/* raw encoding via SETFMT (channels kept) */
				v = encodings[random() % NENC];
				ioctl(fd, SNDCTL_DSP_SETFMT, &v);
				break;
			case 1:	/* channel count incl. unmappable/0/9..16 */
				v = random() % 17;
				ioctl(fd, SNDCTL_DSP_CHANNELS, &v);
				break;
			case 2:
				v = speeds[random() % NSPD];
				ioctl(fd, SNDCTL_DSP_SPEED, &v);
				break;
			case 3:	/* RESET (stops channel) then re-set all */
				ioctl(fd, SNDCTL_DSP_RESET, &v);
				v = encodings[random() % NENC];
				ioctl(fd, SNDCTL_DSP_SETFMT, &v);
				v = random() % 17;
				ioctl(fd, SNDCTL_DSP_CHANNELS, &v);
				v = speeds[random() % NSPD];
				ioctl(fd, SNDCTL_DSP_SPEED, &v);
				break;
			case 4:
				ioctl(fd, SNDCTL_DSP_SYNC, &v);
				break;
			case 5: {	/* short non-blocking write */
				char b[512];
				for (i = 0; i < 512; i++)
					b[i] = random();
				write(fd, b, sizeof(b));
				break;
			}
			case 6:	/* stereo+ext combos through SETFMT idiom */
				v = encodings[random() % NENC] |
				    (((random() % 9) + 1) << 20) |
				    ((random() % 3) << 25);
				/*
				 * SETFMT ioctl keeps current channels; to
				 * inject channels+ext together, use the
				 * SETFMT path that preserves them but vary
				 * via CHANNELS first.
				 */
				ioctl(fd, SNDCTL_DSP_SETFMT, &v);
				break;
			case 7:
				v = 0;
				ioctl(fd, SNDCTL_DSP_STEREO, &v);
				v = 1;
				ioctl(fd, SNDCTL_DSP_STEREO, &v);
				break;
			case 8:	/* trigger-happy start then format change */
				{
					char b[64];
					memset(b, 0x7f, sizeof(b));
					write(fd, b, sizeof(b));
					v = encodings[random() % NENC];
					ioctl(fd, SNDCTL_DSP_SETFMT, &v);
				}
				break;
			case 9:
				ioctl(fd, SNDCTL_DSP_GETOSPACE, &v);
				ioctl(fd, SNDCTL_DSP_GETFMTS, &v);
				break;
			case 10:	/* fragment churn between rebuilds */
				v = 0x000c0010;
				ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &v);
				break;
			case 11:	/* full triple storm */
				v = encodings[random() % NENC];
				ioctl(fd, SNDCTL_DSP_SETFMT, &v);
				v = random() % 17;
				ioctl(fd, SNDCTL_DSP_CHANNELS, &v);
				v = speeds[random() % NSPD];
				ioctl(fd, SNDCTL_DSP_SPEED, &v);
				ioctl(fd, SNDCTL_DSP_RESET, &v);
				break;
			}
			ret = 0; (void)ret;
			if (iters % 8192 == 0)
				usleep(100);
		}
		close(fd);
		break;
	}

	fprintf(stderr, "[%s] done: %lu iterations\n", dev, iters);
	return (0);
}
