/*
 * DF-5295 deterministic model - rum_tx_mgt() permanently loses a tx_data
 * slot on its early-error paths (TX path exhaustion).
 *
 * Models, line-for-line (sys/bus/u4b/wlan/if_rum.c):
 *
 *  rum_tx_mgt()  1478-1494:
 *      data = STAILQ_FIRST(&sc->tx_free);       :1478
 *      STAILQ_REMOVE_HEAD(&sc->tx_free, next);  :1479   <-- slot popped
 *      sc->tx_nfree--;                          :1480
 *      ...
 *      if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) {
 *              k = ieee80211_crypto_get_txkey(ni, m0);
 *              if (k == NULL)
 *                      return (ENOENT);         :1490   <-- slot ORPHANED
 *              if ((k->wk_flags & KEY_SWCRYPT) &&
 *                  !k->wk_cipher->ic_encap(k, m0))
 *                      return (ENOBUFS);        :1494   <-- slot ORPHANED
 *      }
 *
 *  The entry is no longer on tx_free, is not put on tx_q, holds no mbuf:
 *  tx_nfree is never incremented again for it.  RUM_TX_LIST_COUNT is 8,
 *  RUM_TX_MINFREE is 2 (if_rumvar.h:29-30).
 *
 *  rum_start()  1734:  while (sc->tx_nfree >= RUM_TX_MINFREE && ...)
 *      -> once 7 slots are orphaned, tx_nfree == 1 < 2 and the interface
 *         stops dequeuing frames (permanent TX wedge until down/up, since
 *         only rum_init() -> rum_setup_tx_list() (:2577) restores the
 *         list).
 *
 *  Compare rum_tx_data() 1631-1645: same key checks happen BEFORE the
 *  slot is popped (pop is at 1670) and the error paths m_freem() - the
 *  ordering deviation is unique to rum_tx_mgt.
 *
 * Trigger: raw 802.11 management frame with the Protected flag set and no
 * matching key, injected through the legacy rum_raw_xmit() path
 * (params == NULL, :3060-3065), i.e. bpf writes on a monitor-mode wlan.
 */
#include <stdio.h>

#define RUM_TX_LIST_COUNT	8
#define RUM_TX_MINFREE		2

struct state {
	int tx_nfree;
	int orphaned;
	int tx_wedged_resets;
};

/* returns 0 on success, errcode otherwise; models rum_tx_mgt exactly */
static int
rum_tx_mgt(struct state *sc, int protected_flag, int have_key)
{
	/* :1478-1480 - unconditional pop */
	sc->tx_nfree--;

	if (protected_flag) {
		if (!have_key) {
			sc->orphaned++;		/* :1490 ENOENT - slot leaked */
			return (-1);
		}
		/* ic_encap failure path :1494 would leak identically */
	}
	/* success path would push onto tx_q and complete later */
	sc->tx_nfree++;
	return 0;
}

/* rum_start gate :1734 */
static int
rum_start_can_send(struct state *sc)
{
	return sc->tx_nfree >= RUM_TX_MINFREE;
}

int
main(void)
{
	struct state sc = { RUM_TX_LIST_COUNT, 0, 0 };
	int injected = 0, sent = 0, wedged_after = -1;

	printf("start: tx_nfree=%d (RUM_TX_LIST_COUNT=%d, MINFREE=%d)\n",
	    sc.tx_nfree, RUM_TX_LIST_COUNT, RUM_TX_MINFREE);

	for (int i = 0; i < 16; i++) {
		/* raw mgmt frame, Protected set, no key -> ENOENT every time */
		if (!rum_start_can_send(&sc))
			sc.tx_nfree++;	/* bypass gate only to drain slots */
		(void)rum_tx_mgt(&sc, 1, 0);
		injected++;
		if (rum_start_can_send(&sc)) {
			sent++;
		} else if (wedged_after < 0) {
			wedged_after = injected;
		}
	}

	printf("injected %d protected-no-key mgmt frames; orphaned slots=%d; "
	    "tx_nfree=%d\n", injected, sc.orphaned, sc.tx_nfree);
	printf("TX path wedged after %d injections (rum_start gate "
	    "tx_nfree >= %d fails permanently)\n",
	    wedged_after, RUM_TX_MINFREE);

	/* recovery only via down/up: rum_init -> rum_setup_tx_list (:2577) */
	sc.tx_nfree = RUM_TX_LIST_COUNT;
	sc.orphaned = 0;
	sc.tx_wedged_resets++;
	printf("after ifconfig down/up: tx_nfree=%d, wedged resets=%d\n",
	    sc.tx_nfree, sc.tx_wedged_resets);
	return 0;
}
