/*
 * DF-5553 — structural verification harness for
 * sys/bus/u4b/template/usb_template_phone.c (pass 2)
 *
 * build.sh extracts the ENTIRE body of the audited file (from the `enum`
 * at line 58 through the end of phone_get_string_desc at line 416) into
 * phone_body.inc, so every table, callback and byte below is literally the
 * audited production code, not a transcription.
 *
 * The harness then:
 *
 *  (1) replays, byte-for-byte, the descriptor-classification predicate of
 *      usb_make_raw_desc() (sys/bus/u4b/template/usb_template.c:127-174)
 *      — including its CDC union / CDC call-management / IAD patching —
 *      over the phone template's raw tables, using the same interface
 *      numbering walk as usb_make_interface_desc()
 *      (usb_template.c:307-314, usb_template.c:379);
 *  (2) checks the structural invariants the template core trusts:
 *        S1  raw[0] (bLength) == sizeof(table) for every raw descriptor
 *            (usb_make_raw_desc memcpy's raw[0] bytes: a lie here is an
 *             OOB read of static kernel data into the generated config,
 *             which a hostile USB host can then read back over EP0 —
 *             clamped only by wLength, usb_handle_request.c:744-748);
 *        S2  every NULL-terminated pointer table actually carries its
 *            terminator and has no interior NULL hole (early truncation
 *            class);
 *        S3  USB_MAKE_STRING_DESC objects are self-sized
 *            (bLength == sizeof(struct) == 2 + bData bytes, bData even —
 *             usb.h:569-577; tr_valid uses len = buf[0],
 *             usb_template.c:1199-1203);
 *        S4  phone_get_string_desc() returns non-NULL exactly for
 *            index 0 (LANG) and indexes 1..5 @ lang 0x0409, NULL for
 *            every other (index,lang) pair reachable from EP0
 *            (usb_template.c:1184-1187 -> usb_template.c:1095-1109);
 *        S5  phone_get_vendor_desc() length contract:
 *            *plen == sizeof(phone_hid_descriptor) == the
 *            wDescriptorLength embedded in phone_hid_raw_desc_0;
 *            non-matching requests (wrong iface/type/index) -> NULL;
 *        S6  interface-numbering walk gives the HID interface #3, the
 *            number hardcoded in phone_get_vendor_desc()
 *            (usb_template_phone.c:379);
 *        S7  endpoint speed tables have nonzero mps for the speeds the
 *            template targets and land inside mps[USB_SPEED_MAX]
 *            (usb_template.c:221-229 fails closed on 0).
 *
 *  Exit code 0 = all structural checks pass.  The misfire report of (1)
 *  is the finding: it always prints (deterministic), it is not a FAIL —
 *  for this template the patcher's byte delta is provably 0 because all
 *  colliding descriptors live in interface 0.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

/* ---- minimal shim of the kernel USB headers the body needs ---------- */

typedef uint8_t  uByte;
typedef uint16_t uWord;

#define	UDESC_STRING		0x03
#define	UDESC_CS_INTERFACE	0x24	/* usb.h:205 */
#define	UDESC_IFACE_ASSOC	0x0B	/* usb.h:199 */
#define	UDESCSUB_CDC_CM		1	/* usb_cdc.h:38 */
#define	UDESCSUB_CDC_UNION	6	/* usb_cdc.h:43 */

#define	UE_DIR_IN	0x80
#define	UE_DIR_OUT	0x00
#define	UE_ISOCHRONOUS	0x01
#define	UE_INTERRUPT	0x03
#define	UE_ADDR		0x0f

#define	UC_BUS_POWERED	0x80
#define	UDCLASS_IN_INTERFACE 0x02
#define	USB_TEMPLATE_VENDOR 0x0001

enum { USB_SPEED_VARIABLE, USB_SPEED_LOW, USB_SPEED_FULL,
       USB_SPEED_HIGH, USB_SPEED_SUPER };
#define	USB_SPEED_MAX	(USB_SPEED_SUPER + 1)

struct usb_string_lang {
	uByte bLength;
	uByte bDescriptorType;
	uByte bData[2];
} __attribute__((packed));

/* the real one lives in usb_core.c:52 — same content, checked in S3 */
static const struct usb_string_lang usb_string_lang_en = {
	sizeof(usb_string_lang_en), UDESC_STRING, { 0x09, 0x04 }
};

/* usb.h:569-577, verbatim modulo __packed spelling */
#define	USB_MAKE_STRING_DESC(m,name)	\
static const struct {			\
  uByte bLength;			\
  uByte bDescriptorType;		\
  uByte bData[sizeof((uint8_t []){m})];	\
} __attribute__((packed)) name = {	\
  .bLength = sizeof(name),		\
  .bDescriptorType = UDESC_STRING,	\
  .bData = { m },			\
}

struct usb_device_request {
	uByte	bmRequestType;
	uByte	bRequest;
	uByte	wValue[2];
	uByte	wIndex[2];
	uWord	wLength;
} __attribute__((packed));

typedef const void *(usb_temp_get_string_desc_t)(uint16_t, uint8_t);
typedef const void *(usb_temp_get_vendor_desc_t)(
    const struct usb_device_request *, uint16_t *);

/* field-for-field identical to usb_template.h:40-91 (pointers verbatim) */
struct usb_temp_packet_size { uint16_t mps[USB_SPEED_MAX]; };
struct usb_temp_interval   { uint8_t bInterval[USB_SPEED_MAX]; };
struct usb_temp_endpoint_desc {
	const void **ppRawDesc;
	const struct usb_temp_packet_size *pPacketSize;
	const struct usb_temp_interval *pIntervals;
	uint8_t bEndpointAddress, bmAttributes;
};
struct usb_temp_interface_desc {
	const void **ppRawDesc;
	const struct usb_temp_endpoint_desc **ppEndpoints;
	uint8_t bInterfaceClass, bInterfaceSubClass, bInterfaceProtocol;
	uint8_t iInterface, isAltInterface;
};
struct usb_temp_config_desc {
	const struct usb_temp_interface_desc **ppIfaceDesc;
	uint8_t bmAttributes, bMaxPower, iConfiguration;
};
struct usb_temp_device_desc {
	usb_temp_get_string_desc_t *getStringDesc;
	usb_temp_get_vendor_desc_t *getVendorDesc;
	const struct usb_temp_config_desc **ppConfigDesc;
	uint16_t idVendor, idProduct, bcdDevice;
	uint8_t bDeviceClass, bDeviceSubClass, bDeviceProtocol;
	uint8_t iManufacturer, iProduct, iSerialNumber;
};

/* ---- the audited file's body, extracted verbatim by build.sh -------- */
#include "phone_body.inc"

/* ---- (1) faithful replay of usb_make_raw_desc() classification ------ */

static int fails;

#define	CHECK(cond, ...) do {						\
	if (cond) { printf("PASS " __VA_ARGS__); printf("\n"); }		\
	else { printf("FAIL " __VA_ARGS__); printf("\n"); fails++;; }	\
} while (0)

struct patch {
	const char *name;
	size_t	off;		/* byte offset patched */
	int	collide;	/* CDC_CM=1, CDC_UNION=2 */
};

/* exactly the guards of usb_template.c:135-137 / :150-151 / :162-164 */
static int
classify(const uint8_t *raw, int *collide, uint8_t iface_class)
{
	*collide = 0;
#ifdef FIX_CLASSGATE
	/* proposed fix.diff: only renumber CDC descriptors inside CDC
	 * (class 0x02) / CDC-data (class 0x0a) interfaces — audio
	 * CS_INTERFACE descriptors (class 0x01) are left untouched */
	if ((iface_class != 0x02) && (iface_class != 0x0a))
		return (0);
#endif
	if ((raw[0] >= 8) &&			/* sizeof(usb_interface_assoc_desc) */
	    (raw[1] == UDESC_IFACE_ASSOC))
		*collide = 3;		/* IAD branch */
	if ((raw[0] >= 5) &&		/* sizeof(usb_cdc_union_descriptor) */
	    (raw[1] == UDESC_CS_INTERFACE) &&
	    (raw[2] == UDESCSUB_CDC_UNION))
		*collide = 2;		/* CDC union branch */
	if ((raw[0] >= 5) &&		/* sizeof(usb_cdc_cm_descriptor) */
	    (raw[1] == UDESC_CS_INTERFACE) &&
	    (raw[2] == UDESCSUB_CDC_CM))
		*collide = 1;		/* CDC call-management branch */
	return (*collide != 0);
}

static void
scan_table(const char *tname, const void **tbl, uint8_t ifnum,
    uint8_t iface_class)
{
	int i, c;

	for (i = 0; tbl[i] != NULL; i++) {
		const uint8_t *raw = tbl[i];

		if (!classify(raw, &c, iface_class))
			continue;
		switch (c) {
		case 1:
			printf("  MISFIRE %-14s[%d] bLength=%2u type=0x%02x "
			    "subtype=0x%02x -> patched as CDC CALL MANAGEMENT:"
			    " byte[%zu] (bDataInterface slot) += %u%s\n",
			    tname, i, raw[0], raw[1], raw[2], (size_t)4,
			    ifnum, ifnum ? "  <-- CORRUPTED" : "");
			break;
		case 2:
			printf("  MISFIRE %-14s[%d] bLength=%2u type=0x%02x "
			    "subtype=0x%02x -> patched as CDC UNION:"
			    " byte[%zu] (bMasterInterface) += %u, "
			    "byte[%zu] (bSlaveInterface[0]) += %u%s\n",
			    tname, i, raw[0], raw[1], raw[2], (size_t)3,
			    ifnum, (size_t)4, ifnum,
			    ifnum ? "  <-- CORRUPTED" : "");
			break;
		default:
			printf("  MISFIRE %-14s[%d] -> patched as IAD\n",
			    tname, i);
			break;
		}
	}
}

/* interface numbering walk, verbatim semantics of usb_template.c:307-314 */
static void
walk_ifaces(const struct usb_temp_interface_desc **ifs)
{
	const struct usb_temp_interface_desc *tid;
	uint8_t ifnum = 0xFF;		/* usb_template.c:379 */
	uint8_t alt = 0;
	int i, hid_iface = -1;

	printf("\n== (1) usb_make_raw_desc() CDC/IAD patcher replay ==\n");
	for (i = 0; (tid = ifs[i]) != NULL; i++) {
		if (tid->isAltInterface == 0) {
			alt = 0;
			ifnum++;
		} else {
			alt++;
		}
		if (tid->ppRawDesc)
			scan_table("ppRawDesc", tid->ppRawDesc, ifnum,
			    tid->bInterfaceClass);
		if (tid->bInterfaceClass == 3)	/* HID */
			hid_iface = ifnum;
		printf("  iface#%u alt%u class=%u sub=%u iInterface=%u%s\n",
		    ifnum, alt, tid->bInterfaceClass, tid->bInterfaceSubClass,
		    tid->iInterface, tid->isAltInterface ? " (alt)" : "");
	}
	printf("  -> HID interface number: %d\n", hid_iface);
	CHECK(hid_iface == 3,
	    "S6 phone_get_vendor_desc() hardcodes wIndex==3 (phone.c:379) "
	    "and the numbering walk produces exactly 3");
}

/* ---- structural checks ---------------------------------------------- */

static void
check_raw(const char *name, const uint8_t *raw, size_t n)
{
	CHECK(n >= 2 && raw[0] == n,
	    "S1 %s: bLength(0x%02x) == sizeof(table) (%zu) — "
	    "usb_make_raw_desc memcpy of raw[0] bytes stays in-bounds",
	    name, raw ? raw[0] : 0, n);
}

static void
check_table(const char *name, const void **tbl, size_t n)
{
	size_t i;

	for (i = 0; i < n; i++) {
		if (tbl[i] == NULL) {
			CHECK(i == n - 1,
			    "S2 %s: NULL terminator at slot %zu of %zu — "
			    "no interior hole", name, i, n);
			return;
		}
	}
	CHECK(0, "S2 %s: MISSING NULL terminator (%zu slots)", name, n);
}

struct sdesc {
	uByte bLength;
	uByte bDescriptorType;
	uByte bData[252];
} __attribute__((packed));

static void
check_str(const char *name, const void *p)
{
	const struct sdesc *s = p;

	CHECK(s->bDescriptorType == UDESC_STRING &&
	    s->bLength >= 2 && (s->bLength - 2) % 2 == 0,
	    "S3 %s: bLength=%u type=0x%02x bData=%u bytes (even) — "
	    "self-sized by USB_MAKE_STRING_DESC (usb.h:569)", name,
	    s->bLength, s->bDescriptorType, s->bLength - 2);
}

int
main(void)
{
	uint16_t plen;
	const void *p;
	int idx, bad = 0;
	const uint16_t LANG = 0x0409;

	printf("DF-5553 structural table check — usb_template_phone.c\n");
	printf("======================================================\n");

	walk_ifaces(phone_config_desc.ppIfaceDesc);

	printf("\n== structural invariants ==\n");
	/* S1: every raw descriptor table */
	check_raw("phone_raw_desc_0", phone_raw_desc_0, sizeof(phone_raw_desc_0));
	check_raw("phone_raw_desc_1", phone_raw_desc_1, sizeof(phone_raw_desc_1));
	check_raw("phone_raw_desc_2", phone_raw_desc_2, sizeof(phone_raw_desc_2));
	check_raw("phone_raw_desc_3", phone_raw_desc_3, sizeof(phone_raw_desc_3));
	check_raw("phone_raw_desc_4", phone_raw_desc_4, sizeof(phone_raw_desc_4));
	check_raw("phone_raw_desc_5", phone_raw_desc_5, sizeof(phone_raw_desc_5));
	check_raw("phone_raw_desc_6", phone_raw_desc_6, sizeof(phone_raw_desc_6));
	check_raw("phone_raw_desc_20", phone_raw_desc_20, sizeof(phone_raw_desc_20));
	check_raw("phone_raw_desc_21", phone_raw_desc_21, sizeof(phone_raw_desc_21));
	check_raw("phone_raw_desc_22", phone_raw_desc_22, sizeof(phone_raw_desc_22));
	check_raw("phone_raw_desc_30", phone_raw_desc_30, sizeof(phone_raw_desc_30));
	check_raw("phone_raw_desc_31", phone_raw_desc_31, sizeof(phone_raw_desc_31));
	check_raw("phone_raw_desc_32", phone_raw_desc_32, sizeof(phone_raw_desc_32));
	check_raw("phone_hid_raw_desc_0", phone_hid_raw_desc_0, sizeof(phone_hid_raw_desc_0));

	/* S2: every NULL-terminated pointer table */
	check_table("phone_raw_iface_0_desc", phone_raw_iface_0_desc,
	    sizeof(phone_raw_iface_0_desc) / sizeof(void *));
	check_table("phone_raw_iface_1_desc", phone_raw_iface_1_desc,
	    sizeof(phone_raw_iface_1_desc) / sizeof(void *));
	check_table("phone_raw_ep_1_desc", phone_raw_ep_1_desc,
	    sizeof(phone_raw_ep_1_desc) / sizeof(void *));
	check_table("phone_raw_iface_2_desc", phone_raw_iface_2_desc,
	    sizeof(phone_raw_iface_2_desc) / sizeof(void *));
	check_table("phone_raw_ep_2_desc", phone_raw_ep_2_desc,
	    sizeof(phone_raw_ep_2_desc) / sizeof(void *));
	check_table("phone_hid_desc_0", phone_hid_desc_0,
	    sizeof(phone_hid_desc_0) / sizeof(void *));
	check_table("phone_iface_1_ep", (const void **)(void *)phone_iface_1_ep,
	    sizeof(phone_iface_1_ep) / sizeof(void *));
	check_table("phone_iface_2_ep", (const void **)(void *)phone_iface_2_ep,
	    sizeof(phone_iface_2_ep) / sizeof(void *));
	check_table("phone_iface_3_ep", (const void **)(void *)phone_iface_3_ep,
	    sizeof(phone_iface_3_ep) / sizeof(void *));
	check_table("phone_interfaces", (const void **)(void *)phone_interfaces,
	    sizeof(phone_interfaces) / sizeof(void *));
	check_table("phone_configs", (const void **)(void *)phone_configs,
	    sizeof(phone_configs) / sizeof(void *));

	/* S3: string descriptors self-sized */
	check_str("usb_string_lang_en", &usb_string_lang_en);
	check_str("string_phone_mixer", &string_phone_mixer);
	check_str("string_phone_record", &string_phone_record);
	check_str("string_phone_playback", &string_phone_playback);
	check_str("string_phone_product", &string_phone_product);
	check_str("string_phone_hid", &string_phone_hid);

	/* S4: full 256-index sweep of the EP0-reachable callback */
	for (idx = 0; idx < 256; idx++) {
		p = phone_get_string_desc(LANG, (uint8_t)idx);
		if (idx == 0 || (idx >= 1 && idx < INDEX_PHONE_MAX)) {
			if (p == NULL)
				bad++;
		} else if (p != NULL)
			bad++;
	}
	CHECK(bad == 0,
	    "S4 phone_get_string_desc(0x0409, idx): non-NULL exactly for "
	    "0..%d, NULL for %d..255 (bound check phone.c:412)", 
	    INDEX_PHONE_MAX - 1, INDEX_PHONE_MAX);
	bad = 0;
	for (idx = 1; idx < INDEX_PHONE_MAX; idx++)
		if (phone_get_string_desc(0x0408, (uint8_t)idx) != NULL ||
		    phone_get_string_desc(0x0000, (uint8_t)idx) != NULL ||
		    phone_get_string_desc(0xFFFF, (uint8_t)idx) != NULL)
			bad++;
	CHECK(bad == 0,
	    "S4 non-0x0409 lang_id -> NULL for all non-zero indexes "
	    "(phone.c:409-411)");
	CHECK(phone_get_string_desc(0x1234, 0) == &usb_string_lang_en,
	    "S4 index 0 returns LANG table regardless of lang_id "
	    "(USB-spec correct, phone.c:406-408)");

	/* S5: vendor descriptor length contract */
	{
		struct usb_device_request r;

		memset(&r, 0, sizeof(r));
		r.bmRequestType = 0x81; r.bRequest = 0x06;
		r.wValue[0] = 0x00; r.wValue[1] = 0x22;
		r.wIndex[0] = 3;    r.wIndex[1] = 0;
		plen = 0;
		p = phone_get_vendor_desc(&r, &plen);
		CHECK(p == phone_hid_descriptor && plen == sizeof(phone_hid_descriptor),
		    "S5 GET_DESCRIPTOR(HID report, iface 3): *plen=%u == "
		    "sizeof(phone_hid_descriptor)=%zu",
		    plen, sizeof(phone_hid_descriptor));
		CHECK((phone_hid_raw_desc_0[7] |
		    (phone_hid_raw_desc_0[8] << 8)) == plen,
		    "S5 wDescriptorLength embedded in phone_hid_raw_desc_0 "
		    "(0x%02x%02x) == *plen", phone_hid_raw_desc_0[8],
		    phone_hid_raw_desc_0[7]);
		bad = 0;
		r.wIndex[0] = 0;	if (phone_get_vendor_desc(&r, &plen)) bad++;
		r.wIndex[0] = 1;	if (phone_get_vendor_desc(&r, &plen)) bad++;
		r.wIndex[0] = 2;	if (phone_get_vendor_desc(&r, &plen)) bad++;
		r.wIndex[0] = 4;	if (phone_get_vendor_desc(&r, &plen)) bad++;
		r.wIndex[0] = 3; r.wValue[1] = 0x21;
					if (phone_get_vendor_desc(&r, &plen)) bad++;
		r.wValue[1] = 0x23;	if (phone_get_vendor_desc(&r, &plen)) bad++;
		r.wValue[1] = 0x22; r.wValue[0] = 1;
					if (phone_get_vendor_desc(&r, &plen)) bad++;
		r.wValue[0] = 0; r.bmRequestType = 0x80;
					if (phone_get_vendor_desc(&r, &plen)) bad++;
		CHECK(bad == 0,
		    "S5 non-matching requests (iface 0/1/2/4, type 0x21/0x23, "
		    "index!=0, reqtype 0x80) all -> NULL");
	}

	/* S7: speed tables */
	CHECK(phone_isoc_mps.mps[USB_SPEED_FULL] != 0 &&
	    phone_isoc_mps.mps[USB_SPEED_HIGH] != 0 &&
	    phone_hid_mps.mps[USB_SPEED_FULL] != 0 &&
	    phone_hid_mps.mps[USB_SPEED_HIGH] != 0 &&
	    USB_SPEED_FULL < USB_SPEED_MAX && USB_SPEED_HIGH < USB_SPEED_MAX,
	    "S7 mps tables: FULL/HIGH nonzero and in-bounds of "
	    "mps[USB_SPEED_MAX]; LOW/VARIABLE/SUPER -> 0 -> core fails "
	    "closed (usb_template.c:221-229)");

	printf("\n======================================================\n");
	if (fails == 0)
		printf("ALL STRUCTURAL CHECKS PASSED (%s)\n",
#ifdef FIX_CLASSGATE
		    "FIX_CLASSGATE: CDC patcher gated on CDC interface classes"
		    " -> misfires above must be absent");
#else
		    "file is clean on S1-S7; the MISFIRE lines above are "
		    "DF-5553: bDelay 1->2 (iface 1) and 1->3 (iface 2) are "
		    "real nonzero deltas; three iface-0 misfires add 0");
#endif
	else
		printf("%d STRUCTURAL CHECK(S) FAILED\n", fails);
	return (fails == 0) ? 0 : 1;
}
