/*
 * usbshare - intercepting the class driver while the device is claimed
 *
 * This is what makes the filter approach worth the trouble. While no client
 * holds the device, every request is forwarded untouched and the device
 * behaves exactly as if this driver were not installed. Only once a client
 * claims it do the class driver's requests get swallowed, so the two do not
 * fight over the same endpoints.
 */

#include "usbshare.h"

/*
 * Forwards a request to the driver below unchanged.
 *
 * Send-and-forget is right here: we have no interest in the answer, and not
 * setting a completion routine avoids holding a reference on a request that
 * may outlive our interest in it.
 */
static VOID
UsbShareForward(
    _In_ WDFDEVICE Device,
    _In_ WDFREQUEST Request
)
{
    WDF_REQUEST_SEND_OPTIONS options;

    WDF_REQUEST_SEND_OPTIONS_INIT(&options, WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET);
    WdfRequestFormatRequestUsingCurrentType(Request);

    if (!WdfRequestSend(Request, WdfDeviceGetIoTarget(Device), &options)) {
        WdfRequestComplete(Request, WdfRequestGetStatus(Request));
    }
}

VOID
UsbShareEvtIoDefault(
    _In_ WDFQUEUE Queue,
    _In_ WDFREQUEST Request
)
{
    WDFDEVICE device = WdfIoQueueGetDevice(Queue);

    /*
     * Reads and writes are not intercepted even while claimed. They come from
     * user mode against the class driver's own interface, and failing them
     * would surface as application errors rather than a device that is simply
     * busy elsewhere.
     */
    UsbShareForward(device, Request);
}

VOID
UsbShareEvtIoInternalDeviceControl(
    _In_ WDFQUEUE Queue,
    _In_ WDFREQUEST Request,
    _In_ size_t OutputBufferLength,
    _In_ size_t InputBufferLength,
    _In_ ULONG IoControlCode
)
{
    WDFDEVICE device = WdfIoQueueGetDevice(Queue);
    PDEVICE_CONTEXT context = GetDeviceContext(device);

    UNREFERENCED_PARAMETER(OutputBufferLength);
    UNREFERENCED_PARAMETER(InputBufferLength);

    /*
     * IOCTL_INTERNAL_USB_SUBMIT_URB is how the class driver above us talks to
     * the USB stack. Letting those through while a client holds the device
     * would mean two parties submitting to the same endpoints: transfers
     * would be answered to whoever asked last, and a keyboard would appear to
     * type on both machines at once.
     */
    if (IoControlCode == IOCTL_INTERNAL_USB_SUBMIT_URB && UsbShareIsClaimed(context)) {
        /*
         * STATUS_DEVICE_NOT_CONNECTED rather than STATUS_DEVICE_BUSY: class
         * drivers treat "busy" as a reason to retry in a tight loop, whereas
         * "not connected" makes them stand down until PnP says otherwise —
         * which is exactly the state the device is in from their point of view.
         */
        WdfRequestComplete(Request, STATUS_DEVICE_NOT_CONNECTED);
        return;
    }

    /*
     * Everything else — PnP queries, port status, idle notifications — is
     * forwarded even while claimed. Blocking those would confuse the stack
     * about the device's existence, and it does still exist.
     */
    UsbShareForward(device, Request);
}
