/*
 * if_fw.c -- minimal software "wifi radio" driver for net80211 PoC work.
 *
 * Registers an ieee80211com named "fw0" with no real hardware:
 *  - hostap/ibss capable, advertises IEEE80211_C_TXFRAG (like ath/wpi/ral)
 *  - on reaching RUN it allocates a beacon with ieee80211_beacon_alloc()
 *    and keeps it alive with a 100ms callout calling
 *    ieee80211_beacon_update()  (exactly what real MAC drivers do at TBTT)
 *  - sysctl fw.fire=1 builds a 5000-byte Ethernet frame and pushes it
 *    through the exported TX entry ieee80211_vap_pkt_send_dest()
 *    (=> ieee80211_encap => ieee80211_fragment with the vap's default
 *     iv_fragthreshold = IEEE80211_FRAG_MAX = 2346)
 *
 * PoC harness for DF-3282 / DF-3283 (ieee80211_output.c audit).
 */
#include "opt_wlan.h"

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/socket.h>
#include <sys/sockio.h>

#include <net/if.h>
#include <net/if_var.h>
#include <net/if_media.h>
#include <net/ifq_var.h>
#include <net/ethernet.h>

#include <netproto/802_11/ieee80211_var.h>
#include <netproto/802_11/ieee80211_proto.h>
#include <netproto/802_11/ieee80211_regdomain.h>
#include <netproto/802_11/ieee80211_ratectl.h>
#include <netproto/802_11/ieee80211_dragonfly.h>

struct fw_vap {
	struct ieee80211vap		fw_vap;
	int				(*fw_newstate)(struct ieee80211vap *,
					    enum ieee80211_state, int);
};
#define	FW_VAP(vap)	((struct fw_vap *)(vap))

struct fw_softc {
	struct ieee80211com		sc_ic;
	struct callout			fw_tick;
	struct mbuf			*fw_bcbuf;
	int				fw_running;
};

static int	fw_fire = 0;

static int	fw_newstate(struct ieee80211vap *, enum ieee80211_state, int);
static void	fw_beacon_tick(void *);
static void	fw_beacon_update(struct ieee80211vap *, int);
static struct ieee80211vap *
		fw_vap_create(struct ieee80211com *, const char [IFNAMSIZ], int,
		    enum ieee80211_opmode, int, const uint8_t [IEEE80211_ADDR_LEN],
		    const uint8_t [IEEE80211_ADDR_LEN]);
static void	fw_vap_delete(struct ieee80211vap *);
static void	fw_parent(struct ieee80211com *);
static int	fw_raw_xmit(struct ieee80211_node *, struct mbuf *,
		    const struct ieee80211_bpf_params *);
static int	fw_transmit(struct ieee80211com *, struct mbuf *);
static void	fw_nop_ic(struct ieee80211com *);
static int	fw_wme_nop(struct ieee80211com *);

static struct fw_softc *fw_sc = NULL;

static int
fw_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
{
	struct fw_softc *sc = vap->iv_ic->ic_softc;
	struct fw_vap *nvp = FW_VAP(vap);
	int error;

	error = nvp->fw_newstate(vap, nstate, arg);

	if (error == 0 && nstate == IEEE80211_S_RUN &&
	    (vap->iv_opmode == IEEE80211_M_HOSTAP ||
	     vap->iv_opmode == IEEE80211_M_IBSS ||
	     vap->iv_opmode == IEEE80211_M_MBSS)) {
		if (sc->fw_bcbuf == NULL) {
			sc->fw_bcbuf = ieee80211_beacon_alloc(vap->iv_bss);
			kprintf("fw: %s RUN, beacon %s\n", vap->iv_ifp->if_xname,
			    sc->fw_bcbuf != NULL ? "allocated" : "FAILED");
			if (sc->fw_bcbuf != NULL)
				callout_reset(&sc->fw_tick, hz / 10,
				    fw_beacon_tick, sc);
		}
	}
	return error;
}

static void
fw_beacon_tick(void *xsc)
{
	struct fw_softc *sc = xsc;

	callout_reset(&sc->fw_tick, hz / 10, fw_beacon_tick, sc);

	wlan_serialize_enter();
	if (sc->fw_bcbuf != NULL) {
		struct ieee80211vap *vap;
		/* find our vap */
		TAILQ_FOREACH(vap, &sc->sc_ic.ic_vaps, iv_next)
			break;
		if (vap != NULL)
			ieee80211_beacon_update(vap->iv_bss, sc->fw_bcbuf, 0);
	}
	wlan_serialize_exit();
}

/* driver hook: net80211 signals beacon content changes here */
static void
fw_beacon_update(struct ieee80211vap *vap, int item)
{
	setbit(vap->iv_bcn_off.bo_flags, item);
}

static struct ieee80211vap *
fw_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit,
    enum ieee80211_opmode opmode, int flags,
    const uint8_t bssid[IEEE80211_ADDR_LEN],
    const uint8_t mac[IEEE80211_ADDR_LEN])
{
	struct fw_vap *nvp;
	struct ieee80211vap *vap;

	switch (opmode) {
	case IEEE80211_M_STA:
	case IEEE80211_M_IBSS:
	case IEEE80211_M_HOSTAP:
	case IEEE80211_M_MONITOR:
		if (!TAILQ_EMPTY(&ic->ic_vaps)) {
			kprintf("fw: only 1 vap supported\n");
			return NULL;
		}
		break;
	default:
		kprintf("fw: opmode %d not supported\n", opmode);
		return NULL;
	}
	nvp = kmalloc(sizeof(struct fw_vap), M_80211_VAP, M_WAITOK | M_ZERO);
	vap = &nvp->fw_vap;
	ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid);

	/* override state transition machine */
	nvp->fw_newstate = vap->iv_newstate;
	vap->iv_newstate = fw_newstate;
	vap->iv_update_beacon = fw_beacon_update;

	ieee80211_ratectl_init(vap);
	/* complete setup */
	ieee80211_vap_attach(vap, ieee80211_media_change,
	    ieee80211_media_status, mac);
	ic->ic_opmode = opmode;
	return vap;
}

static void
fw_vap_delete(struct ieee80211vap *vap)
{
	struct fw_softc *sc = vap->iv_ic->ic_softc;
	struct fw_vap *nvp = FW_VAP(vap);

	callout_stop(&sc->fw_tick);
	if (sc->fw_bcbuf != NULL) {
		m_freem(sc->fw_bcbuf);
		sc->fw_bcbuf = NULL;
	}
	ieee80211_ratectl_deinit(vap);
	ieee80211_vap_detach(vap);
	kfree(nvp, M_80211_VAP);
}

static void
fw_parent(struct ieee80211com *ic)
{
	struct fw_softc *sc = ic->ic_softc;
	int startall = 0;

	if (ic->ic_nrunning > 0) {
		if (!sc->fw_running) {
			sc->fw_running = 1;
			startall = 1;
		}
	} else
		sc->fw_running = 0;
	if (startall)
		ieee80211_start_all(ic);
}

static int
fw_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
    const struct ieee80211_bpf_params *params)
{
	if (params != NULL)
		; /* unused */
	m_freem(m);
	ieee80211_free_node(ni);
	return 0;
}

static int
fw_transmit(struct ieee80211com *ic, struct mbuf *m)
{
	struct mbuf *n;

	while (m != NULL) {
		n = m->m_nextpkt;
		m->m_nextpkt = NULL;
		if (m->m_pkthdr.rcvif != NULL)
			ieee80211_tx_complete(
			    (struct ieee80211_node *)m->m_pkthdr.rcvif, m, 0);
		else
			m_freem(m);
		m = n;
	}
	return 0;
}

static void
fw_nop_ic(struct ieee80211com *ic)
{
}

static int
fw_wme_nop(struct ieee80211com *ic)
{
	return 0;
}

/*
 * sysctl fw.fire=1 -- hand a 5000-byte 802.3 frame to the real
 * exported TX entry (ieee80211_vap_pkt_send_dest).  This is the same
 * privilege level as a BPF write to the wlan interface; it bypasses
 * only the ifnet MTU accounting that normally caps frames at
 * IEEE80211_MTU_MAX=2290 (the length gate that keeps
 * ieee80211_fragment() from seeing >4KB frames).
 */
static int
fw_fire_sysctl(SYSCTL_HANDLER_ARGS)
{
	struct fw_softc *sc = fw_sc;
	struct ieee80211vap *vap;
	struct ieee80211_node *ni;
	struct mbuf *m, *mt;
	int error, total, len;
	uint8_t *p;

	error = sysctl_handle_int(oidp, &fw_fire, 0, req);
	if (error != 0 || req->newptr == NULL)
		return error;
	if (fw_fire == 0 || sc == NULL)
		return 0;

	TAILQ_FOREACH(vap, &sc->sc_ic.ic_vaps, iv_next)
		break;
	if (vap == NULL) {
		kprintf("fw: no vap; create one first\n");
		return ENXIO;
	}

	total = 5000;
	m = m_gethdr(M_WAITOK, MT_DATA);
	p = mtod(m, uint8_t *);
	/* unicast dst, src = vap mac, ethertype IPv4 */
	p[0]=0x02; p[1]=0xde; p[2]=0xad; p[3]=0xbe; p[4]=0xef; p[5]=0x00;
	IEEE80211_ADDR_COPY(p+6, vap->iv_myaddr);
	p[12] = 0x08; p[13] = 0x00;
	len = total > MHLEN ? MHLEN : total;
	memset(p+14, 0x41, len-14);
	m->m_len = m->m_pkthdr.len = len;
	mt = m;
	while (len < total) {
		int n = total - len;
		if (n > MCLBYTES)
			n = MCLBYTES;
		mt->m_next = m_getcl(M_WAITOK, MT_DATA, 0);
		KKASSERT(mt->m_next != NULL);
		mt = mt->m_next;
		memset(mtod(mt, void *), 0x41 + ((len/2048)&0xf), n);
		mt->m_len = n;
		m->m_pkthdr.len += n;
		len += n;
	}

	kprintf("fw.fire: vap %s, pushing %d byte frame (fragthreshold %u)\n",
	    vap->iv_ifp->if_xname, m->m_pkthdr.len, vap->iv_fragthreshold);

	ni = ieee80211_ref_node(vap->iv_bss);
	wlan_serialize_enter();
	ieee80211_vap_pkt_send_dest(vap, m, ni);
	wlan_serialize_exit();
	kprintf("fw.fire: returned (no panic)\n");
	return 0;
}
SYSCTL_PROC(_kern, OID_AUTO, fw_fire, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, fw_fire_sysctl, "I", "fw PoC: fire 5000B frame through encap");

static int
fw_modevent(module_t mod, int type, void *data)
{
	struct fw_softc *sc;
	struct ieee80211com *ic;
	uint8_t bands[IEEE80211_MODE_BYTES];

	switch (type) {
	case MOD_LOAD:
		sc = kmalloc(sizeof(struct fw_softc), M_DEVBUF,
		    M_WAITOK | M_ZERO);
		ic = &sc->sc_ic;
		ic->ic_softc = sc;
		ic->ic_name = "fw0";
		ic->ic_opmode = IEEE80211_M_STA;
		ic->ic_phytype = IEEE80211_T_DS;
		ic->ic_caps =
			  IEEE80211_C_STA
			| IEEE80211_C_IBSS
			| IEEE80211_C_HOSTAP
			| IEEE80211_C_MONITOR
			| IEEE80211_C_SHPREAMBLE
			| IEEE80211_C_SHSLOT
			| IEEE80211_C_TXFRAG	/* host tx fragmentation */
			;
		/* a single 11b channel set */
		memset(bands, 0, sizeof(bands));
		setbit(bands, IEEE80211_MODE_11B);
		ieee80211_init_channels(ic, NULL, bands);

		ic->ic_macaddr[0] = 0x02; ic->ic_macaddr[1] = 0x00;
		ic->ic_macaddr[2] = 0x00; ic->ic_macaddr[3] = 0x00;
		ic->ic_macaddr[4] = 0x00; ic->ic_macaddr[5] = 0xf1;

		callout_init(&sc->fw_tick);

		ic->ic_vap_create = fw_vap_create;
		ic->ic_vap_delete = fw_vap_delete;
		ic->ic_parent = fw_parent;
		ic->ic_raw_xmit = fw_raw_xmit;
		ic->ic_transmit = fw_transmit;
		ic->ic_scan_start = fw_nop_ic;
		ic->ic_scan_end = fw_nop_ic;
		ic->ic_set_channel = fw_nop_ic;
		ic->ic_updateslot = fw_nop_ic;
		ic->ic_wme.wme_update = fw_wme_nop;

		ieee80211_ifattach(ic);
		fw_sc = sc;
		kprintf("fw: fake wifi radio attached (fw0)\n");
		return 0;
	case MOD_UNLOAD:
		sc = fw_sc;
		if (sc == NULL)
			return 0;
		if (!TAILQ_EMPTY(&sc->sc_ic.ic_vaps)) {
			kprintf("fw: vap still active, refusing unload\n");
			return EBUSY;
		}
		callout_stop(&sc->fw_tick);
		ieee80211_ifdetach(&sc->sc_ic);
		kfree(sc, M_DEVBUF);
		fw_sc = NULL;
		kprintf("fw: detached\n");
		return 0;
	}
	return 0;
}

static moduledata_t fw_mod = { "fw", fw_modevent, 0 };
DECLARE_MODULE(fw, fw_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_DEPEND(fw, wlan, 1, 1, 1);
