/*
 * df3478_drive.c — DF-3478 driver: L2CAP SEQPACKET listener whose inbound
 * connection is driven to OPEN by the btsbx.ko fake lower layer, then a
 * flood/read race against the socket receive buffer (ng_btsocket_l2cap_
 * data_input appends without ssb_token/sb lock; soreceive unlinks under
 * ssb_token — no common lock).
 *
 * build: cc -pthread -o df3478_drive df3478_drive.c
 * run (root): ./df3478_drive <seconds> [nreaders]
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#include <pthread.h>

#define AF_BT		33
#define PROTO_L2CAP	135

struct sockaddr_l2cap_bt {
	u_char	l2cap_len;
	u_char	l2cap_family;
	u_short	l2cap_psm;	/* little-endian on the wire */
	u_char	l2cap_bdaddr[6];
};

static int	acc_fd = -1;
static volatile int	stop_readers;

static FILE	*logf;

static void
logprintf(const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vprintf(fmt, ap);
	va_end(ap);
	if (logf != NULL) {
		va_start(ap, fmt);
		vfprintf(logf, fmt, ap);
		va_end(ap);
		fflush(logf);
		fsync(fileno(logf));
	}
}

static void
cmd_set(int c)
{
	int v = c;

	while (1) {
		int cur = 0;
		size_t cl = sizeof(cur);

		if (sysctlbyname("debug.btsbx_cmd", &cur, &cl, NULL, 0) == 0 &&
		    cur == 0)
			break;
		usleep(10000);
	}
	if (sysctlbyname("debug.btsbx_cmd", NULL, NULL, &v, sizeof(v)) != 0) {
		logprintf("sysctl btsbx_cmd=%d: %s\n", c, strerror(errno));
		exit(1);
	}
	while (1) {
		int cur = 42;
		size_t cl = sizeof(cur);

		if (sysctlbyname("debug.btsbx_cmd", &cur, &cl, NULL, 0) == 0 &&
		    cur == 0)
			break;
		usleep(10000);
	}
}

static int
state_get(void)
{
	int v = -1;
	size_t l = sizeof(v);

	sysctlbyname("debug.btsbx_state", &v, &l, NULL, 0);
	return (v);
}

static void *
reader(void *x)
{
	char buf[512];		/* small: exercise partial-record paths */
	long n = 0, bytes = 0;
	(void)x;

	while (!stop_readers) {
		ssize_t r = read(acc_fd, buf, sizeof(buf));
		if (r > 0) {
			n++;
			bytes += r;
		} else if (r < 0 && errno != EAGAIN) {
			logprintf("reader: read: %s\n", strerror(errno));
			break;
		}
	}
	logprintf("reader done: %ld reads, %ld bytes\n", n, bytes);
	return (NULL);
}

int
main(int argc, char **argv)
{
	struct sockaddr_l2cap_bt sa;
	struct timespec ts;
	pthread_t tid[8];
	int fd, i, secs = 60, nrd = 1;
	void *join;

	logf = fopen("/root/btsbx/run.log", "w");
	setvbuf(stdout, NULL, _IONBF, 0);
	if (argc > 1)
		secs = atoi(argv[1]);
	if (argc > 2)
		nrd = atoi(argv[2]);
	if (nrd > 8)
		nrd = 8;

	fd = socket(AF_BT, SOCK_SEQPACKET, PROTO_L2CAP);
	if (fd < 0) {
		logprintf("socket: %s\n", strerror(errno));
		return (1);
	}
	memset(&sa, 0, sizeof(sa));
	sa.l2cap_len = sizeof(sa);
	sa.l2cap_family = AF_BT;
	sa.l2cap_psm = 0x1234;		/* native order: le16toh is a no-op on LE */
	if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) != 0) {
		logprintf("bind: %s\n", strerror(errno));
		return (1);
	}
	if (listen(fd, 4) != 0) {
		logprintf("listen: %s\n", strerror(errno));
		return (1);
	}
	logprintf("listener ready (psm 0x1234)\n");

	cmd_set(1);		/* setup hooks + hook_info */
	logprintf("setup state=%d\n", state_get());
	cmd_set(2);		/* con_ind */
	logprintf("con_ind state=%d\n", state_get());
	usleep(300000);
	cmd_set(3);		/* cfg_ind */
	logprintf("cfg_ind state=%d\n", state_get());
	usleep(300000);
	cmd_set(4);		/* cfg response */
	logprintf("cfg state=%d\n", state_get());
	usleep(300000);

	logprintf("accepting...\n");
	acc_fd = accept(fd, NULL, NULL);
	if (acc_fd < 0) {
		logprintf("accept: %s\n", strerror(errno));
		return (1);
	}
	logprintf("ACCEPTED fd=%d — child socket is OPEN; starting race\n", acc_fd);
	stop_readers = 0;
	for (i = 0; i < nrd; i++)
		pthread_create(&tid[i], NULL, reader, NULL);

	cmd_set(5);		/* flood on */
	for (i = 0; i < secs; i++) {
		int sent = 0, errs = 0;
		size_t l = sizeof(int);

		sleep(1);
		sysctlbyname("debug.btsbx_sent", &sent, &l, NULL, 0);
		sysctlbyname("debug.btsbx_errs", &errs, &l, NULL, 0);
		logprintf("t+%02d sent=%d errs=%d\n", i + 1, sent, errs);
	}
	cmd_set(6);		/* flood off */
	stop_readers = 1;

	/* DF-3480 probe: is the sockbuf internally consistent?
	 * FIONREAD returns so_rcv.sb_cc. If sb_cc is large while every
	 * reader is starved, records were appended into a torn queue. */
	{
		int n = -1;
		usleep(200000);
		if (ioctl(acc_fd, FIONREAD, &n) == 0)
			logprintf("FIONREAD: %d bytes queued\n", n);
		else
			logprintf("FIONREAD: %s\n", strerror(errno));
	}
	/* one more read attempt with a timeout to catch a wedged queue */
	{
		char buf[512];
		ssize_t r;
		int n2 = -1;
		{
			struct timeval tv;
			fd_set rfds;

			FD_ZERO(&rfds);
			FD_SET(acc_fd, &rfds);
			tv.tv_sec = 5;
			tv.tv_usec = 0;
			if (select(acc_fd + 1, &rfds, NULL, NULL, &tv) > 0) {
				int got = 0;
				while ((r = read(acc_fd, buf, sizeof(buf))) > 0)
					got++;
				logprintf("post-race drain: %d records readable, "
				    "then %s\n", got, strerror(errno));
			} else
				logprintf("post-race: no data readable in 5s "
				    "(wedge confirmed if FIONREAD > 0)\n");
			ioctl(acc_fd, FIONREAD, &n2);
			logprintf("FIONREAD after drain: %d bytes queued\n", n2);
		}
	}

	/* wake readers: shutdown the socket */
	shutdown(acc_fd, SHUT_RDWR);
	for (i = 0; i < nrd; i++)
		pthread_join(tid[i], &join);
	logprintf("DF-3478 run complete (no crash)\n");
	return (0);
}
