/*
 * DF-5578 shim -- deliberately its own compilation unit (family standard,
 * see DF-5455 run.1.fold.log: a same-TU inlinable shim let GCC fold the
 * provably-uninitialized load to a constant; production's usbd_do_request
 * is an opaque cross-module macro/call, so the shim must be opaque too).
 *
 * It reproduces the exact usbd_do_request_flags() completion semantics for
 * the 1-byte vendor control-IN that ufm_do_req() issues (ufm.c:185-191,
 * flags=0 via the plain usbd_do_request macro at usbdi.h:511-512):
 *
 *   mode 0  "short":     device answers the DATA stage with a zero-length
 *                        packet, status stage OK.  For flags=0 the stack
 *                        converts this to USB_ERR_SHORT_XFER
 *                        (usb_transfer.c:2979-2985: actlen 8 < sumlen 9,
 *                        short_xfer_ok NOT set -- the default EP0 template
 *                        usb_control_ep_cfg carries no short-ok flags,
 *                        usb_transfer.c:76-88) -> ufm_do_req() maps it to
 *                        ENXIO (ufm.c:196-198) and BOTH callers discard
 *                        `ret' (ufm.c:253-256, 288-291).  PRODUCTION
 *                        CONTAINMENT -- this is what a real evil radio
 *                        gets on the current stack.
 *   mode 1  "timeout":   device never answers; xfer->error = USB_ERR_TIMEOUT
 *                        (usb_request.c:697-704).  Same containment.
 *   mode 2  "full":      device returns the full 1-byte DATA stage; the
 *                        stack copies it out (usb_request.c:680-681) and
 *                        returns 0.  Happy path / request-capture mode.
 *   mode 3  "short-ok":  COUNTERFACTUAL -- the DF-1062 delta.  What the
 *                        SAME call site would observe if ufm passed
 *                        USB_SHORT_XFER_OK (as uchcom.c:398 does) or used
 *                        usbd_do_request_proc with actlen=NULL (uchcom's
 *                        ucom_cfg_do_request, usb_serial.h:220): a short
 *                        DATA stage completes with err==0 and NOTHING is
 *                        copied -- the stack's own zero-pad-on-short only
 *                        exists behind a non-NULL actlen
 *                        (usb_request.c:782-787).  ufm_do_req() then
 *                        returns 0 and *retbuf receives stale kernel
 *                        stack.  NOT reachable for ufm today (that is
 *                        the finding's containment claim); exercised to
 *                        demonstrate what one flag of drift turns it into.
 *
 * Every mode also records the request tuple (bmRequestType/bRequest/
 * wValue/wIndex/wLength) into a capture log -- the "synthetic usbd
 * request capture" family evidence.
 */
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <bus/u4b/usb.h>
#include <bus/u4b/usbdi.h>

int		ufm_shim_mode = 0;
int		ufm_shim_requests = 0;
uint8_t		ufm_shim_devbyte = 0x5c;	/* mode 2 answer */

struct ufm_shim_cap {
	uint8_t		bm;
	uint8_t		breq;
	uint16_t	wval;
	uint16_t	widx;
	uint16_t	wlen;
	int		mode;
	int		err;
};

/* prototypes (appease -Wmissing-prototypes; definitions below) */
void		ufm_shim_cap_reset(void);
int		ufm_shim_cap_dump(struct ufm_shim_cap *, int);
usb_error_t	ufm_shim_request(struct usb_device_request *, uint8_t *);

#define UFM_SHIM_CAPMAX 64
static struct ufm_shim_cap ufm_shim_log[UFM_SHIM_CAPMAX];
static int ufm_shim_logn;

void
ufm_shim_cap_reset(void)
{
	ufm_shim_logn = 0;
}

int
ufm_shim_cap_dump(struct ufm_shim_cap *out, int max)
{
	int n = ufm_shim_logn;

	if (n > max)
		n = max;
	for (int i = 0; i < n; ++i)
		out[i] = ufm_shim_log[i];
	return (ufm_shim_logn);
}

usb_error_t
ufm_shim_request(struct usb_device_request *req, uint8_t *data)
{
	struct ufm_shim_cap *cap;
	usb_error_t err = 0;

	if (ufm_shim_logn < UFM_SHIM_CAPMAX) {
		cap = &ufm_shim_log[ufm_shim_logn++];
		cap->bm = req->bmRequestType;
		cap->breq = req->bRequest;
		cap->wval = UGETW(req->wValue);
		cap->widx = UGETW(req->wIndex);
		cap->wlen = UGETW(req->wLength);
		cap->mode = ufm_shim_mode;
	} else {
		cap = NULL;
	}
	ufm_shim_requests++;

	switch (ufm_shim_mode) {
	case 0:				/* short ZLP -> SHORT_XFER (error) */
		err = USB_ERR_SHORT_XFER;	/* *data deliberately untouched */
		break;
	case 1:				/* silence -> TIMEOUT (error) */
		err = USB_ERR_TIMEOUT;
		break;
	case 2:				/* full 1-byte DATA stage */
		*data = ufm_shim_devbyte;
		err = 0;
		break;
	case 3:				/* COUNTERFACTUAL short-ok success */
		err = 0;			/* *data deliberately untouched */
		break;
	default:
		err = USB_ERR_INVAL;
		break;
	}
	if (cap)
		cap->err = err;
	return (err);
}
