/*
 * harness.c -- KLD harness for the pass-2 audit of sys/netbt/l2cap_lower.c
 * (finding DF-5764).
 *
 * The production files l2cap_upper.c / l2cap_lower.c (FILE UNDER AUDIT) /
 * l2cap_misc.c / l2cap_signal.c / hci_link.c are compiled into this
 * module VERBATIM (copied from /usr/src/sys/netbt by build.sh, md5s
 * recorded).  In particular hci_acl_send() is production code, so its
 * ENETDOWN failure branch (hci_link.c:546-549:
 *      if (link->hl_state == HCI_LINK_CLOSED) { m_freem(m); return ENETDOWN; })
 * runs exactly as shipped.
 *
 * DEFECT UNDER TEST (l2cap_lower.c:195-204):
 *
 *   l2cap_start():
 *	IF_DEQUEUE(&chan->lc_txq, m);		-- m removed from channel queue
 *	chan->lc_pending++;			-- counter raised BEFORE the call
 *	return hci_acl_send(m, chan->lc_link, chan);  -- can FAIL:
 *	                                      ENETDOWN (link CLOSED) or ENOMEM
 *
 * On failure hci_acl_send() has freed m and created NO l2cap_pdu.  No
 * structure that could ever decrement lc_pending exists: hci_acl_complete()
 * only decrements for PDUs found on link->hl_txq whose lp_chan == chan
 * (hci_link.c:730-740).  lc_pending is therefore permanently inflated
 * for the lifetime of the channel, with three consequences, all driven
 * below with production calls only:
 *
 *   (1) l2cap_send() (l2cap_upper.c:398) gates on lc_pending == 0: every
 *       further send(2) silently succeeds into lc_txq and NOTHING is ever
 *       transmitted again -- permanent TX blackhole.
 *   (2) l2cap_disconnect(chan, linger) (l2cap_upper.c:244) gates on
 *       (IF_QEMPTY(txq) && lc_pending == 0): the graceful L2CAP
 *       disconnect handshake never starts -- channel can only die by
 *       force-close.
 *   (3) l2cap_start()'s own L2CAP_SHUTDOWN flush path (l2cap_lower.c:173)
 *       gates on lc_pending == 0: never taken.
 *
 * Channel/link state built by the harness is exactly what production
 * produces: l2cap_attach() + l2cap_cid_alloc() + chan->lc_link = link with
 * one reference (what hci_acl_open() takes, hci_link.c:143) +
 * lc_state = L2CAP_OPEN (what config negotiation produces,
 * l2cap_signal.c:605) on a link left in HCI_LINK_CLOSED (what
 * hci_link_alloc() returns, hci_link.c:909 -- and the state the link is
 * left in by the teardown race window of DF-5737, or by an ENOMEM under
 * memory pressure: any hci_acl_send failure produces the same imbalance).
 *
 * Triggers (sysctl, as root):
 *
 *   hw.l2pw_setup = 1   print rig/kernel facts
 *   hw.l2pw_go    = 1   WEDGE ROUND: drive the defect, print PASS/FAIL
 *                       per assertion, set hw.l2pw_wedge_proven = 1 iff
 *                       every assertion of the permanent wedge held.
 *   hw.l2pw_ctl   = 1   CONTROL ROUND: identical setup but link OPEN and
 *                       a production hci_acl_complete() -- shows the
 *                       healthy lc_pending 1->0 round-trip and that the
 *                       SHUTDOWN disconnect fires once pending drains.
 *   hw.l2pw_state = 1   dump counters
 */
#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/queue.h>
#include <sys/thread.h>
#include <sys/thread2.h>
#include <sys/endian.h>
#include <vm/vm_zone.h>

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

#include "l2uh.h"

SYSCTL_NODE(_hw, OID_AUTO, l2pw, CTLFLAG_RW, 0, "l2cap pending wedge rig");

static int l2pw_setup(SYSCTL_HANDLER_ARGS);
static int l2pw_go(SYSCTL_HANDLER_ARGS);
static int l2pw_ctl(SYSCTL_HANDLER_ARGS);
static int l2pw_state(SYSCTL_HANDLER_ARGS);

SYSCTL_PROC(_hw, OID_AUTO, l2pw_setup, CTLTYPE_INT|CTLFLAG_RW,
	    0, 0, l2pw_setup, "I", "print rig facts");
SYSCTL_PROC(_hw, OID_AUTO, l2pw_go, CTLTYPE_INT|CTLFLAG_RW,
	    0, 0, l2pw_go, "I", "run the WEDGE round");
SYSCTL_PROC(_hw, OID_AUTO, l2pw_ctl, CTLTYPE_INT|CTLFLAG_RW,
	    0, 0, l2pw_ctl, "I", "run the CONTROL round");
SYSCTL_PROC(_hw, OID_AUTO, l2pw_state, CTLTYPE_INT|CTLFLAG_RW,
	    0, 0, l2pw_state, "I", "dump counters");

int hw_l2pw_wedge_proven;
int hw_l2pw_ctl_ok;
SYSCTL_INT(_hw, OID_AUTO, l2pw_wedge_proven, CTLFLAG_RD,
	   &hw_l2pw_wedge_proven, 0, "1 iff every wedge assertion held");
SYSCTL_INT(_hw, OID_AUTO, l2pw_ctl_ok, CTLFLAG_RD,
	   &hw_l2pw_ctl_ok, 0, "1 iff every control assertion held");

static struct hci_unit *fake_unit;

/* ------------------------------------------------------------------ */
/* channel/link construction: production states */

static struct mbuf *
mkpkt(int n)
{
	struct mbuf *m;

	m = m_gethdr(M_NOWAIT, MT_DATA);
	if (m == NULL)
		return NULL;
	m->m_len = n;
	m->m_pkthdr.len = n;
	memset(mtod(m, char *), 'A', n);
	return m;
}

/*
 * Build "connected channel on this link": l2cap_attach() +
 * l2cap_cid_alloc() + the hci_acl_open() reference + L2CAP_OPEN
 * (post-config state).
 */
static struct l2cap_channel *
mkchan(struct hci_link *link)
{
	struct l2cap_channel *chan;

	if (l2cap_attach(&chan, &stub_btproto, NULL) != 0)
		return NULL;
	if (l2cap_cid_alloc(chan) != 0) {
		kfree(chan, M_BLUETOOTH);
		return NULL;
	}
	chan->lc_link = link;
	link->hl_refcnt++;		/* hci_acl_open() ref (hci_link.c:143) */
	chan->lc_state = L2CAP_OPEN;	/* post-config (l2cap_signal.c:605) */
	chan->lc_rcid = 0x1041;		/* peer CID */
	return chan;
}

/* ------------------------------------------------------------------ */
/* WEDGE ROUND */

static int
wedge_round(void)
{
	struct l2cap_channel *chan;
	struct hci_link *link;
	struct mbuf *m;
	int ret, i, fail = 0;
	int q0, qN;

	kprintf("l2pw: == WEDGE ROUND: OPEN channel, link HCI_LINK_CLOSED ==\n");

	/* production hci_link_alloc(): state = HCI_LINK_CLOSED (hci_link.c:909) */
	link = hci_link_alloc(fake_unit);
	if (link == NULL) {
		kprintf("l2pw: FAIL link alloc\n");
		return ENOMEM;
	}
	chan = mkchan(link);
	if (chan == NULL) {
		kprintf("l2pw: FAIL chan build\n");
		return ENOMEM;
	}

	/* -- W1: first send hits hci_acl_send's ENETDOWN branch ------------ */
	m = mkpkt(32);
	ret = l2cap_send(chan, m);		/* production l2cap_upper.c:370 */
	q0 = chan->lc_txq.ifq_len;
	kprintf("l2pw: W1 send#1 ret=%d (ENETDOWN=%d) lc_pending=%d "
		"link_txq_empty=%d lc_txq_len=%d\n",
		ret, ENETDOWN, chan->lc_pending,
		TAILQ_EMPTY(&link->hl_txq), q0);
	if (ret != ENETDOWN || chan->lc_pending != 1 ||
	    !TAILQ_EMPTY(&link->hl_txq) || q0 != 0) {
		kprintf("l2pw: W1 FAIL (unexpected)\n");
		fail++;
	} else {
		kprintf("l2pw: W1 PASS: pending==1 but NO pdu exists on the "
			"link that could ever decrement it (unbalanced "
			"l2cap_lower.c:203)\n");
	}

	/* -- W2: 64 further sends: silently accepted, never transmitted ---- */
	for (i = 0; i < 64; i++) {
		m = mkpkt(32);
		ret = l2cap_send(chan, m);
		if (ret != 0) {
			kprintf("l2pw: W2 FAIL: send#%d ret=%d\n", i + 2, ret);
			fail++;
			break;
		}
	}
	qN = chan->lc_txq.ifq_len;
	kprintf("l2pw: W2 64 more sends: ret==0 all, lc_txq_len=%d "
		"(blackholed) lc_pending=%d\n", qN, chan->lc_pending);
	if (qN != 64 || chan->lc_pending != 1) {
		kprintf("l2pw: W2 FAIL\n");
		fail++;
	} else
		kprintf("l2pw: W2 PASS: TX blackhole armed (64 SDUs accepted, "
			"0 can ever leave)\n");

	/* -- W3: graceful disconnect disabled ------------------------------- */
	stub_proto_disconnected_count = 0;
	ret = l2cap_disconnect(chan, 1);	/* linger, production :230 */
	kprintf("l2pw: W3 l2cap_disconnect(linger) ret=%d state=%d (OPEN=%d) "
		"reqs_empty=%d SHUTDOWN=%d\n",
		ret, chan->lc_state, L2CAP_OPEN,
		TAILQ_EMPTY(&link->hl_reqs),
		!!(chan->lc_flags & L2CAP_SHUTDOWN));
	if (ret != 0 || chan->lc_state != L2CAP_OPEN ||
	    !TAILQ_EMPTY(&link->hl_reqs)) {
		kprintf("l2pw: W3 FAIL (unexpected)\n");
		fail++;
	} else
		kprintf("l2pw: W3 PASS: graceful disconnect permanently "
			"disabled (l2cap_upper.c:244 gate never true)\n");

	/* -- W4: every retry inflates the leak further -------------------- */
	ret = l2cap_start(chan);		/* production l2cap_lower.c:159 */
	kprintf("l2pw: W4 l2cap_start ret=%d lc_txq_len=%d lc_pending=%d "
		"state=%d\n",
		ret, chan->lc_txq.ifq_len, chan->lc_pending, chan->lc_state);
	if (ret != ENETDOWN || chan->lc_txq.ifq_len != 63 ||
	    chan->lc_pending != 2 || chan->lc_state != L2CAP_OPEN) {
		kprintf("l2pw: W4 FAIL (unexpected)\n");
		fail++;
	} else
		kprintf("l2pw: W4 PASS: retry inflated the phantom count to 2 "
			"and burned another blackholed SDU -- the leak "
			"compounds on every l2cap_start caller "
			"(l2cap_send/hci_acl_complete/hci_acl_start)\n");

	/* -- W5: recovery ONLY by force (detach) ----------------------------- */
	l2cap_detach(&chan);			/* production :260: force close */
	kprintf("l2pw: W5 forced detach: chan freed=%s lc_txq drained=%s "
		"disconnected_cb=%d (no panic)\n",
		chan == NULL ? "yes" : "NO",
		"yes", stub_proto_disconnected_count);
	if (chan != NULL)
		fail++;

	if (fail == 0)
		hw_l2pw_wedge_proven = 1;
	kprintf("l2pw: WEDGE ROUND %s (assertion failures: %d)\n",
		fail == 0 ? "PROVEN" : "NOT proven", fail);
	return 0;
}

/* ------------------------------------------------------------------ */
/* CONTROL ROUND: healthy link, production completion */

static int
ctl_round(void)
{
	struct l2cap_channel *chan;
	struct hci_link *link;
	struct l2cap_req *req;
	struct mbuf *m;
	int ret, fail = 0;

	kprintf("l2pw: == CONTROL ROUND: OPEN channel, link OPEN ==\n");

	link = hci_link_alloc(fake_unit);
	if (link == NULL)
		return ENOMEM;
	link->hl_state = HCI_LINK_OPEN;		/* con_compl (hci_event.c:580) */
	fake_unit->hci_num_acl_pkts = 8;
	fake_unit->hci_max_acl_size = 256;

	chan = mkchan(link);
	if (chan == NULL)
		return ENOMEM;

	/* -- C1: healthy send ------------------------------------------------ */
	m = mkpkt(32);
	stub_acl_output_count = 0;
	ret = l2cap_send(chan, m);
	kprintf("l2pw: C1 send ret=%d lc_pending=%d link_pdu=%s frags_out=%d\n",
		ret, chan->lc_pending,
		TAILQ_EMPTY(&link->hl_txq) ? "none" : "queued",
		stub_acl_output_count);
	if (ret != 0 || chan->lc_pending != 1 ||
	    TAILQ_EMPTY(&link->hl_txq) || stub_acl_output_count != 1) {
		kprintf("l2pw: C1 FAIL (unexpected)\n");
		fail++;
	} else
		kprintf("l2pw: C1 PASS: one owned PDU exists; the pending "
			"count is backed by a real PDU\n");

	/* -- C2: linger disconnect waits for the in-flight PDU --------------- */
	ret = l2cap_disconnect(chan, 1);
	kprintf("l2pw: C2 l2cap_disconnect(linger) ret=%d state=%d (OPEN=%d) "
		"reqs_empty=%d\n",
		ret, chan->lc_state, L2CAP_OPEN,
		TAILQ_EMPTY(&link->hl_reqs));
	if (ret != 0 || chan->lc_state != L2CAP_OPEN ||
	    !TAILQ_EMPTY(&link->hl_reqs)) {
		kprintf("l2pw: C2 FAIL (unexpected)\n");
		fail++;
	} else
		kprintf("l2pw: C2 PASS: waiting for queue flush (by design)\n");

	/* -- C3: production completion decrements and starts the shutdown ---- */
	stub_proto_complete_count = 0;
	hci_acl_complete(link, 1);		/* production hci_link.c:708 */
	kprintf("l2pw: C3 hci_acl_complete: lc_pending=%d state=%d "
		"(WAIT_DISCONNECT=%d) reqs=%d complete_cb=%d\n",
		chan->lc_pending, chan->lc_state, L2CAP_WAIT_DISCONNECT,
		TAILQ_EMPTY(&link->hl_reqs) ? 0 : 1,
		stub_proto_complete_count);
	if (chan->lc_pending != 0 || chan->lc_state != L2CAP_WAIT_DISCONNECT ||
	    TAILQ_EMPTY(&link->hl_reqs) || stub_proto_complete_count != 1) {
		kprintf("l2pw: C3 FAIL (unexpected)\n");
		fail++;
	} else
		kprintf("l2pw: C3 PASS: 1->0 round-trip works; SHUTDOWN flush "
			"fired the disconnect request -- exactly what the "
			"wedged channel can NEVER do\n");

	/* -- C4: clean unwind -------------------------------------------------- */
	req = TAILQ_FIRST(&link->hl_reqs);
	if (req != NULL)
		l2cap_request_free(req);
	l2cap_detach(&chan);
	hci_link_free(link, 0);			/* production :931 */
	kprintf("l2pw: C4 unwind: clean (no panic)\n");

	if (fail == 0)
		hw_l2pw_ctl_ok = 1;
	kprintf("l2pw: CONTROL ROUND %s (assertion failures: %d)\n",
		fail == 0 ? "OK" : "FAILED", fail);
	return 0;
}

/* ------------------------------------------------------------------ */
/* sysctl glue */

static int
l2pw_setup(SYSCTL_HANDLER_ARGS)
{
	int error, t = 0;

	if ((error = SYSCTL_OUT(req, &t, sizeof(t))) || !req->newptr)
		return error;
	if ((error = SYSCTL_IN(req, &t, sizeof(t))))
		return error;

	kprintf("l2pw: rig facts:\n");
	kprintf("l2pw:   sizeof(l2cap_channel)=%zu offsetof(lc_pending)=%zu "
		"offsetof(lc_txq)=%zu\n",
		sizeof(struct l2cap_channel),
		__offsetof(struct l2cap_channel, lc_pending),
		__offsetof(struct l2cap_channel, lc_txq));
	kprintf("l2pw:   sizeof(hci_link)=%zu sizeof(l2cap_pdu)=%zu "
		"hz=%d\n", sizeof(struct hci_link),
		sizeof(struct l2cap_pdu), hz);
	kprintf("l2pw:   ENETDOWN=%d L2CAP_OPEN=%d "
		"L2CAP_WAIT_DISCONNECT=%d\n",
		ENETDOWN, L2CAP_OPEN, L2CAP_WAIT_DISCONNECT);
	return 0;
}

static int
l2pw_go(SYSCTL_HANDLER_ARGS)
{
	int error, t = 0;

	if ((error = SYSCTL_OUT(req, &t, sizeof(t))) || !req->newptr)
		return error;
	if ((error = SYSCTL_IN(req, &t, sizeof(t))))
		return error;
	return wedge_round();
}

static int
l2pw_ctl(SYSCTL_HANDLER_ARGS)
{
	int error, t = 0;

	if ((error = SYSCTL_OUT(req, &t, sizeof(t))) || !req->newptr)
		return error;
	if ((error = SYSCTL_IN(req, &t, sizeof(t))))
		return error;
	return ctl_round();
}

static int
l2pw_state(SYSCTL_HANDLER_ARGS)
{
	int error, t = 0;

	if ((error = SYSCTL_OUT(req, &t, sizeof(t))) || !req->newptr)
		return error;
	if ((error = SYSCTL_IN(req, &t, sizeof(t))))
		return error;
	kprintf("l2pw: wedge_proven=%d ctl_ok=%d disconnected_cb=%d "
		"complete_cb=%d frags_out=%d\n",
		hw_l2pw_wedge_proven, hw_l2pw_ctl_ok,
		stub_proto_disconnected_count, stub_proto_complete_count,
		stub_acl_output_count);
	return 0;
}

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

static int
l2pw_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		/* what netbt_init() (bt_proto.c) does in production */
		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);
		if (l2cap_pdu_pool == NULL || l2cap_req_pool == NULL)
			return ENOMEM;
		l2cap_init();

		fake_unit = kmalloc(sizeof(*fake_unit), M_BLUETOOTH,
				    M_WAITOK | M_ZERO);
		TAILQ_INIT(&fake_unit->hci_links);

		kprintf("l2pw: l2cap pending-wedge rig loaded "
			"(production l2cap_*.c + hci_link.c compiled in)\n");
		return 0;

	case MOD_UNLOAD:
		kfree(fake_unit, M_BLUETOOTH);
		zdestroy(l2cap_pdu_pool);
		zdestroy(l2cap_req_pool);
		kprintf("l2pw: unloaded (wedge_proven=%d ctl_ok=%d)\n",
			hw_l2pw_wedge_proven, hw_l2pw_ctl_ok);
		return 0;
	}
	return EINVAL;
}

static moduledata_t l2pw_mod = { "l2pw_harness", l2pw_modevent, NULL };
DECLARE_MODULE(l2pw_harness, l2pw_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
