/*
 * DF-5555 — structural verification harness for
 * sys/bus/u4b/template/usb_template_audio.c (pass 2)
 *
 * build.sh extracts the ENTIRE body of the audited file (from the `enum`
 * at line 58 through the end of audio_get_string_desc at line 396) into
 * audio_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-generation algorithm of
 *      usb_make_raw_desc() (sys/bus/u4b/template/usb_template.c:127-174)
 *      — including its CDC union / CDC call-management / IAD patching —
 *      into a heap buffer using the same interface numbering walk as
 *      usb_make_interface_desc() (usb_template.c:307-314,379) and the
 *      same raw-desc scan order (iface ppRawDesc first, then endpoint
 *      ppRawDesc, usb_template.c:318-338 / :209-215), then diffs every
 *      generated descriptor against its authored source table: the
 *      "<-- CORRUPTED" bytes are exactly what a USB host would read;
 *  (2) checks the structural invariants the template core trusts:
 *        S1  raw[0] (bLength) == sizeof(table) for all 22 raw descriptor
 *            tables (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 read back over EP0 —
 *            clamped only by wLength, usb_handle_request.c:744-748);
 *        S2  every NULL-terminated pointer table (9 of them) 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;
 *            tr_valid uses len = buf[0], usb_template.c:1199-1203);
 *        S4  audio_get_string_desc() returns non-NULL exactly for index 0
 *            (LANG, any lang_id) and indexes 1..4 @ lang 0x0409, NULL for
 *            every other (index,lang) pair reachable from EP0
 *            (usb_template.c:1184-1187 -> 1086-1109; audio.c:386-395);
 *        S5  no vendor/HID surface: usb_template_audio.getVendorDesc ==
 *            NULL, so the core's fallback returns NULL for every stalled
 *            request (usb_template.c:1050-1060); getStringDesc wired;
 *        S6  interface numbers hardcoded INSIDE raw descriptors match the
 *            core's numbering walk: AC header bInCollection == 2 and
 *            baInterfaceNr == {1,2} == the walk's AudioStreaming iface
 *            numbers, and wTotalLength(0x00a9) == sum of the 16
 *            AC-interface CS_INTERFACE descriptor lengths;
 *        S7  endpoint speed tables in-bounds and nonzero for FULL/HIGH;
 *            LOW/VARIABLE/SUPER -> mps==0 -> core fails closed
 *            (usb_template.c:221-229);
 *        S8  audio graph integrity: every bSourceID/bTerminalLink /
 *            mixer+selector source pin referenced inside the AudioControl
 *            interface resolves to a unit/terminal ID defined by the same
 *            interface (the bytes the CDC-union misfire would corrupt are
 *            exactly these bUnitID/bSourceID fields).
 *
 *  Exit code 0 = all structural checks pass.  The CORRUPTED lines of (1)
 *  are the finding: they always print (deterministic) and are not FAILs.
 */

#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_CS_ENDPOINT	0x25	/* usb.h:206 */
#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	/* usb.h:532 */
#define	UE_ISO_ADAPT	0x08	/* usb.h:540 */
#define	UE_ADDR		0x0f

#define	UC_BUS_POWERED	0x80	/* usb.h:382 */
#define	UDCLASS_COMM	0x02	/* usb.h:360 */
#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 — 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, 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 (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 "audio_body.inc"

/* ---- (1) faithful two-pass replay of the raw-desc generator ---------- */

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)

/* exactly the guards of usb_template.c:135-137 / :150-151 / :162-164 */
static int
classify(const uint8_t *raw, uint8_t iface_class)
{
	int 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);
}

/* per-descriptor provenance, so the diff can name names */
struct ent {
	const char *tname;	/* source table name */
	const uint8_t *src;	/* authored bytes */
	size_t	off;		/* offset in generated buffer */
	uint8_t ifnum;		/* bInterfaceNumber current at generation */
	uint8_t iface_class;
};

/* resolve a raw table pointer back to its declared name (census) */
static const char *
name_of(const uint8_t *p)
{
	struct { const uint8_t *p; const char *n; } tab[] = {
		{ audio_raw_desc_0,  "audio_raw_desc_0"  },
		{ audio_raw_desc_1,  "audio_raw_desc_1"  },
		{ audio_raw_desc_2,  "audio_raw_desc_2"  },
		{ audio_raw_desc_3,  "audio_raw_desc_3"  },
		{ audio_raw_desc_4,  "audio_raw_desc_4"  },
		{ audio_raw_desc_5,  "audio_raw_desc_5"  },
		{ audio_raw_desc_6,  "audio_raw_desc_6"  },
		{ audio_raw_desc_7,  "audio_raw_desc_7"  },
		{ audio_raw_desc_8,  "audio_raw_desc_8"  },
		{ audio_raw_desc_9,  "audio_raw_desc_9"  },
		{ audio_raw_desc_10, "audio_raw_desc_10" },
		{ audio_raw_desc_11, "audio_raw_desc_11" },
		{ audio_raw_desc_12, "audio_raw_desc_12" },
		{ audio_raw_desc_13, "audio_raw_desc_13" },
		{ audio_raw_desc_14, "audio_raw_desc_14" },
		{ audio_raw_desc_15, "audio_raw_desc_15" },
		{ audio_raw_desc_20, "audio_raw_desc_20" },
		{ audio_raw_desc_21, "audio_raw_desc_21" },
		{ audio_raw_desc_22, "audio_raw_desc_22" },
		{ audio_raw_desc_30, "audio_raw_desc_30" },
		{ audio_raw_desc_31, "audio_raw_desc_31" },
		{ audio_raw_desc_32, "audio_raw_desc_32" },
	};
	size_t i;

	for (i = 0; i < sizeof(tab) / sizeof(tab[0]); i++)
		if (tab[i].p == p)
			return (tab[i].n);
	return ("<unknown>");
}

static int n_hit;		/* census: every predicate hit */
static int n_hit_real;		/* hits in nonzero interfaces */

static const char *field_of(const uint8_t *raw, size_t off);

static struct ent ents[128];
static int nents;
static uint8_t genbuf[4096];
static size_t gensize;

/* usb_make_raw_desc() replay: memcpy + CDC/IAD patching, byte-exact */
static void
gen_raw_desc(const uint8_t *raw, const char *tname, uint8_t ifnum,
    uint8_t iface_class)
{
	uint8_t len = raw[0];
	uint8_t *dst = genbuf + gensize;
	int c;

	memcpy(dst, raw, len);

	c = classify(raw, iface_class);
	if (c) {
		/* full census: every descriptor the CDC heuristic fires on */
		n_hit++;
		n_hit_real += ifnum ? 1 : 0;
		if (c == 1)
			printf("  HIT    %-19s bLength=%2u type=0x%02x "
			    "subtype=0x%02x in iface %u: CDC-CM patch "
			    "byte[4] (%s) delta=+%u%s\n",
			    name_of(raw), raw[0], raw[1], raw[2], ifnum,
			    field_of(raw, 4), ifnum,
			    ifnum ? "  <-- REAL DELTA" : " (zero delta)");
		else if (c == 2)
			printf("  HIT    %-19s bLength=%2u type=0x%02x "
			    "subtype=0x%02x in iface %u: CDC-UNION patch "
			    "byte[3]+byte[4] (%s/%s) delta=+%u%s\n",
			    name_of(raw), raw[0], raw[1], raw[2], ifnum,
			    field_of(raw, 3), field_of(raw, 4), ifnum,
			    ifnum ? "  <-- REAL DELTA" : " (zero delta)");
		else
			printf("  HIT    %-19s bLength=%2u type=0x%02x "
			    "subtype=0x%02x in iface %u: IAD patch "
			    "byte[3] delta=+%u%s\n",
			    name_of(raw), raw[0], raw[1], raw[2], ifnum,
			    ifnum, ifnum ? "  <-- REAL DELTA" :
			    " (zero delta)");
	}
	if (c == 2) {			/* CDC union: bytes 3,4 += ifnum */
		dst[3] += ifnum;
		dst[4] += ifnum;
	} else if (c == 1) {		/* CDC CM: byte 4 += ifnum */
		dst[4] += ifnum;
	} else if (c == 3) {		/* IAD: byte 3 += ifnum */
		dst[3] += ifnum;
	}
	ents[nents].tname = tname;
	ents[nents].src = raw;
	ents[nents].off = gensize;
	ents[nents].ifnum = ifnum;
	ents[nents].iface_class = iface_class;
	nents++;
	gensize += len;
}

/* field names for the byte offsets the misfire patches, for the report */
static const char *
field_of(const uint8_t *raw, size_t off)
{
	if (raw[1] != UDESC_CS_INTERFACE)
		return ("?");
	switch (raw[2]) {
	case 0x01:	/* AC header (iface 0) / AS_GENERAL (ifaces 1,2) */
		if (off == 4)
			return (raw[0] == 10 ? "bcdADC hi" : "bDelay");
		return ("?");
	case 0x06:	/* feature unit */
		if (off == 3)
			return ("bUnitID");
		if (off == 4)
			return ("bSourceID");
		return ("?");
	}
	return ("?");
}

static int as_ifnum[8], as_n;	/* AS (subclass 2) interface numbers */
static int n_ifaces;

/* usb_make_interface_desc() numbering + scan order, verbatim semantics */
static void
walk_and_generate(const struct usb_temp_interface_desc **ifs)
{
	const struct usb_temp_interface_desc *tid;
	const struct usb_temp_endpoint_desc **ted;
	uint8_t ifnum = 0xFF;		/* usb_template.c:379 */
	uint8_t alt = 0;
	int i, j;

	for (i = 0; (tid = ifs[i]) != NULL; i++) {
		if (tid->isAltInterface == 0) {
			alt = 0;
			ifnum++;
			n_ifaces++;
			if (tid->bInterfaceSubClass == 2)
				as_ifnum[as_n++] = ifnum;
		} else {
			alt++;
		}
		printf("  iface#%u alt%u class=%u sub=%u proto=%u "
		    "iInterface=%u%s\n", ifnum, alt,
		    tid->bInterfaceClass, tid->bInterfaceSubClass,
		    tid->bInterfaceProtocol, tid->iInterface,
		    tid->isAltInterface ? " (alt)" : "");
		/* iface raw descs first (usb_template.c:318-325) */
		if (tid->ppRawDesc) {
			for (j = 0; tid->ppRawDesc[j] != NULL; j++)
				gen_raw_desc(tid->ppRawDesc[j],
				    "<iface ppRawDesc>", ifnum,
				    tid->bInterfaceClass);
		}
		/* then endpoint raw descs (usb_template.c:332-338,209-215) */
		ted = tid->ppEndpoints;
		if (ted) {
			for (j = 0; ted[j] != NULL; j++) {
				const void **rd = ted[j]->ppRawDesc;
				int k;
				if (rd)
					for (k = 0; rd[k] != NULL; k++)
						gen_raw_desc(rd[k],
						    "<endpoint ppRawDesc>",
						    ifnum,
						    tid->bInterfaceClass);
			}
		}
	}
}

static int n_misfire, n_corrupt;

static void
diff_generated(void)
{
	int i;
	size_t k;

	printf("\n== (1) generated-vs-authored byte diff "
	    "(usb_make_raw_desc replay) ==\n");
	for (i = 0; i < nents; i++) {
		const uint8_t *g = genbuf + ents[i].off;
		int hit = 0;
		for (k = 0; k < ents[i].src[0]; k++) {
			if (g[k] != ents[i].src[k]) {
				printf("  MISFIRE %s[%d] bLength=%2u "
				    "type=0x%02x subtype=0x%02x in iface %u:"
				    " byte[%zu] (%s) 0x%02x -> 0x%02x%s\n",
				    ents[i].tname, i, ents[i].src[0],
				    ents[i].src[1], ents[i].src[2],
				    ents[i].ifnum, k,
				    field_of(ents[i].src, k),
				    ents[i].src[k], g[k],
				    ents[i].ifnum ? "  <-- CORRUPTED" : "");
				hit = 1;
				n_corrupt += ents[i].ifnum ? 1 : 0;
			}
		}
		if (hit)
			n_misfire++;
	}
	printf("  -> census: %d descriptor(s) satisfy the CDC predicate; "
	    "%d in nonzero interfaces (real deltas); byte diff: %d "
	    "descriptor(s) patched, %d byte(s) with nonzero delta "
	    "(CORRUPTED)\n", n_hit, n_hit_real, n_misfire, n_corrupt);
}

/* ---- 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", name,
	    s->bLength, s->bDescriptorType, s->bLength - 2);
}

/* S8: audio graph — collect defined IDs, then resolve every reference */
static void
check_graph(void)
{
	const void **tbl = audio_raw_iface_0_desc;
	uint8_t defined[256];
	char refd[256][24];	/* first referencer per ID, for messages */
	int i, bad = 0;

	memset(defined, 0, sizeof(defined));
	memset(refd, 0, sizeof(refd));
	for (i = 0; tbl[i] != NULL; i++) {
		const uint8_t *r = tbl[i];
		if (r[1] == UDESC_CS_INTERFACE && r[0] >= 4)
			defined[r[3]] = 1;	/* every unit/terminal: ID@3 */
	}
	for (i = 0; tbl[i] != NULL; i++) {
		const uint8_t *r = tbl[i];
		int npins = 0, k;
		if (r[1] != UDESC_CS_INTERFACE || r[0] < 5)
			continue;
		switch (r[2]) {
		case 0x04:				/* mixer unit */
		case 0x05:				/* selector unit */
			npins = r[4];			/* baSourceID at 5.. */
			for (k = 0; k < npins && 5 + k < r[0]; k++)
				if (!defined[r[5 + k]]) {
					printf("    %s: pin %d -> ID 0x%02x "
					    "UNRESOLVED\n",
					    r[2] == 4 ? "mixer" : "selector",
					    k, r[5 + k]);
					bad++;
				}
			break;
		case 0x06:				/* feature unit */
		case 0x03:				/* output terminal */
			if (!defined[r[4]] && r[4] != 0) {
				printf("    %s ID 0x%02x: bSourceID "
				    "0x%02x UNRESOLVED\n",
				    r[2] == 6 ? "feature" : "output-term",
				    r[3], r[4]);
				bad++;
			}
			break;
		default:
			break;
		}
	}
	CHECK(bad == 0,
	    "S8 AC-interface audio graph: every bSourceID/mixer/selector pin "
	    "resolves to a defined unit/terminal ID (%d unresolved)", bad);
}

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

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

	printf("\n== interface numbering walk (usb_template.c:307-314,379) ==\n");
	walk_and_generate(audio_config_desc.ppIfaceDesc);
	diff_generated();

	printf("\n== structural invariants ==\n");
	/* S1: every raw descriptor table (22) */
	check_raw("audio_raw_desc_0", audio_raw_desc_0, sizeof(audio_raw_desc_0));
	check_raw("audio_raw_desc_1", audio_raw_desc_1, sizeof(audio_raw_desc_1));
	check_raw("audio_raw_desc_2", audio_raw_desc_2, sizeof(audio_raw_desc_2));
	check_raw("audio_raw_desc_3", audio_raw_desc_3, sizeof(audio_raw_desc_3));
	check_raw("audio_raw_desc_4", audio_raw_desc_4, sizeof(audio_raw_desc_4));
	check_raw("audio_raw_desc_5", audio_raw_desc_5, sizeof(audio_raw_desc_5));
	check_raw("audio_raw_desc_6", audio_raw_desc_6, sizeof(audio_raw_desc_6));
	check_raw("audio_raw_desc_7", audio_raw_desc_7, sizeof(audio_raw_desc_7));
	check_raw("audio_raw_desc_8", audio_raw_desc_8, sizeof(audio_raw_desc_8));
	check_raw("audio_raw_desc_9", audio_raw_desc_9, sizeof(audio_raw_desc_9));
	check_raw("audio_raw_desc_10", audio_raw_desc_10, sizeof(audio_raw_desc_10));
	check_raw("audio_raw_desc_11", audio_raw_desc_11, sizeof(audio_raw_desc_11));
	check_raw("audio_raw_desc_12", audio_raw_desc_12, sizeof(audio_raw_desc_12));
	check_raw("audio_raw_desc_13", audio_raw_desc_13, sizeof(audio_raw_desc_13));
	check_raw("audio_raw_desc_14", audio_raw_desc_14, sizeof(audio_raw_desc_14));
	check_raw("audio_raw_desc_15", audio_raw_desc_15, sizeof(audio_raw_desc_15));
	check_raw("audio_raw_desc_20", audio_raw_desc_20, sizeof(audio_raw_desc_20));
	check_raw("audio_raw_desc_21", audio_raw_desc_21, sizeof(audio_raw_desc_21));
	check_raw("audio_raw_desc_22", audio_raw_desc_22, sizeof(audio_raw_desc_22));
	check_raw("audio_raw_desc_30", audio_raw_desc_30, sizeof(audio_raw_desc_30));
	check_raw("audio_raw_desc_31", audio_raw_desc_31, sizeof(audio_raw_desc_31));
	check_raw("audio_raw_desc_32", audio_raw_desc_32, sizeof(audio_raw_desc_32));

	/* S2: every NULL-terminated pointer table (9) */
	check_table("audio_raw_iface_0_desc", audio_raw_iface_0_desc,
	    sizeof(audio_raw_iface_0_desc) / sizeof(void *));
	check_table("audio_raw_iface_1_desc", audio_raw_iface_1_desc,
	    sizeof(audio_raw_iface_1_desc) / sizeof(void *));
	check_table("audio_raw_ep_1_desc", audio_raw_ep_1_desc,
	    sizeof(audio_raw_ep_1_desc) / sizeof(void *));
	check_table("audio_raw_iface_2_desc", audio_raw_iface_2_desc,
	    sizeof(audio_raw_iface_2_desc) / sizeof(void *));
	check_table("audio_raw_ep_2_desc", audio_raw_ep_2_desc,
	    sizeof(audio_raw_ep_2_desc) / sizeof(void *));
	check_table("audio_iface_1_ep", (const void **)(void *)audio_iface_1_ep,
	    sizeof(audio_iface_1_ep) / sizeof(void *));
	check_table("audio_iface_2_ep", (const void **)(void *)audio_iface_2_ep,
	    sizeof(audio_iface_2_ep) / sizeof(void *));
	check_table("audio_interfaces", (const void **)(void *)audio_interfaces,
	    sizeof(audio_interfaces) / sizeof(void *));
	check_table("audio_configs", (const void **)(void *)audio_configs,
	    sizeof(audio_configs) / sizeof(void *));

	/* S3: string descriptors self-sized (5) */
	check_str("usb_string_lang_en", &usb_string_lang_en);
	check_str("string_audio_mixer", &string_audio_mixer);
	check_str("string_audio_record", &string_audio_record);
	check_str("string_audio_playback", &string_audio_playback);
	check_str("string_audio_product", &string_audio_product);

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

	/* S5: no vendor/HID surface at all */
	CHECK(usb_template_audio.getVendorDesc == NULL,
	    "S5 usb_template_audio.getVendorDesc == NULL — the core's "
	    "usb_temp_get_vendor_desc() short-circuits to NULL for every "
	    "stalled EP0 request (usb_template.c:1050-1060); no vendor/"
	    "HID descriptor contract exists to violate");
	CHECK(usb_template_audio.getStringDesc == &audio_get_string_desc,
	    "S5 getStringDesc wired to audio_get_string_desc "
	    "(usb_template.c:1086-1109)");
	CHECK(usb_template_audio.iProduct == INDEX_AUDIO_PRODUCT &&
	    usb_template_audio.iManufacturer == 0 &&
	    usb_template_audio.iSerialNumber == 0,
	    "S5 device string indexes in-range of the string table "
	    "(iProduct=%u <= INDEX_AUDIO_MAX-1, iMfr/iSer 0)",
	    usb_template_audio.iProduct);

	/* S6: hardcoded iface numbers inside raw descriptors vs the walk */
	{
		size_t ac_sum = 0;
		int j;
		for (j = 0; audio_raw_iface_0_desc[j] != NULL; j++)
			ac_sum += ((const uint8_t *)audio_raw_iface_0_desc[j])[0];
		CHECK(n_ifaces == 3,
		    "S6 numbering walk yields 3 interfaces (bNumInterface = "
		    "bInterfaceNumber+1 = 3, usb_template.c:404)");
		CHECK(as_n == 2 && as_ifnum[0] == 1 && as_ifnum[1] == 2,
		    "S6 AudioStreaming (subclass 2) interfaces are #%d,#%d — "
		    "AC header baInterfaceNr must be {1,2}",
		    as_n > 0 ? as_ifnum[0] : -1, as_n > 1 ? as_ifnum[1] : -1);
		CHECK(audio_raw_desc_0[7] == 2,
		    "S6 AC header bInCollection(%u) == number of AS "
		    "interfaces (2) — audio_raw_desc_0[7]",
		    audio_raw_desc_0[7]);
		CHECK(audio_raw_desc_0[8] == 1 && audio_raw_desc_0[9] == 2,
		    "S6 AC header baInterfaceNr == {1,2} — the core never "
		    "renumbers these bytes, so they must match the walk "
		    "(audio_raw_desc_0[8..9])");
		CHECK((audio_raw_desc_0[5] | (audio_raw_desc_0[6] << 8)) ==
		    ac_sum,
		    "S6 AC header wTotalLength(0x%02x%02x = %zu) == sum of "
		    "the %d AC-interface CS_INTERFACE lengths (%zu)",
		    audio_raw_desc_0[6], audio_raw_desc_0[5],
		    (size_t)(audio_raw_desc_0[5] |
		    (audio_raw_desc_0[6] << 8)), j, ac_sum);
	}

	/* S7: speed tables */
	CHECK(audio_isoc_mps.mps[USB_SPEED_FULL] != 0 &&
	    audio_isoc_mps.mps[USB_SPEED_HIGH] != 0 &&
	    USB_SPEED_FULL < USB_SPEED_MAX && USB_SPEED_HIGH < USB_SPEED_MAX,
	    "S7 audio_isoc_mps: FULL/HIGH nonzero (0x%x/0x%x) and in-bounds "
	    "of mps[USB_SPEED_MAX]; LOW/VARIABLE/SUPER -> 0 -> core fails "
	    "closed with USB_ERR_INVAL (usb_template.c:221-229)",
	    audio_isoc_mps.mps[USB_SPEED_FULL],
	    audio_isoc_mps.mps[USB_SPEED_HIGH]);
	CHECK(audio_isoc_interval.bInterval[USB_SPEED_FULL] == 1 &&
	    audio_isoc_interval.bInterval[USB_SPEED_HIGH] == 4,
	    "S7 audio_isoc_interval: FULL=1 (1:1), HIGH=4 (1:8) — "
	    "in-bounds of bInterval[USB_SPEED_MAX]");

	/* S8: audio graph integrity */
	check_graph();

	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-S8; the CORRUPTED lines above are "
		    "DF-5555: bDelay 3->4 (iface 1, audio_raw_desc_20) and "
		    "1->3 (iface 2, audio_raw_desc_30) are real nonzero "
		    "deltas; seven iface-0 misfires add 0");
#endif
	else
		printf("%d STRUCTURAL CHECK(S) FAILED\n", fails);
	return (fails == 0) ? 0 : 1;
}
