/*
 * usbshare - driver entry, device setup and claim lifecycle
 */

#include "usbshare.h"

NTSTATUS
DriverEntry(
    _In_ PDRIVER_OBJECT DriverObject,
    _In_ PUNICODE_STRING RegistryPath
)
{
    WDF_DRIVER_CONFIG config;
    NTSTATUS status;

    WDF_DRIVER_CONFIG_INIT(&config, UsbShareEvtDeviceAdd);

    status = WdfDriverCreate(DriverObject, RegistryPath,
                             WDF_NO_OBJECT_ATTRIBUTES, &config, WDF_NO_HANDLE);
    if (!NT_SUCCESS(status)) {
        KdPrint(("usbshare: WdfDriverCreate failed 0x%x\n", status));
    }

    return status;
}

NTSTATUS
UsbShareEvtDeviceAdd(
    _In_ WDFDRIVER Driver,
    _Inout_ PWDFDEVICE_INIT DeviceInit
)
{
    NTSTATUS status;
    WDFDEVICE device;
    WDF_OBJECT_ATTRIBUTES attributes;
    WDF_PNPPOWER_EVENT_CALLBACKS pnpCallbacks;
    WDF_FILEOBJECT_CONFIG fileConfig;
    WDF_IO_QUEUE_CONFIG queueConfig;
    PDEVICE_CONTEXT context;
    WDFQUEUE queue;

    UNREFERENCED_PARAMETER(Driver);

    /*
     * Declaring ourselves a filter is what makes this driver safe to attach
     * to arbitrary devices: the framework then forwards every request we do
     * not explicitly handle to the driver below, so a device we know nothing
     * about keeps working exactly as before.
     */
    WdfFdoInitSetFilter(DeviceInit);

    WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpCallbacks);
    pnpCallbacks.EvtDevicePrepareHardware = UsbShareEvtDevicePrepareHardware;
    WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpCallbacks);

    /*
     * File create and close callbacks give us the claim lifecycle: a claim is
     * tied to a handle, so when the client exits — cleanly or not — the
     * kernel closes the handle and the device goes back to its class driver.
     * Without this a crashed client would leave hardware unusable until
     * reboot.
     */
    WDF_FILEOBJECT_CONFIG_INIT(&fileConfig,
                               UsbShareEvtDeviceFileCreate,
                               UsbShareEvtFileClose,
                               WDF_NO_EVENT_CALLBACK); /* no cleanup callback */
    WdfDeviceInitSetFileObjectConfig(DeviceInit, &fileConfig,
                                     WDF_NO_OBJECT_ATTRIBUTES);

    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DEVICE_CONTEXT);

    status = WdfDeviceCreate(&DeviceInit, &attributes, &device);
    if (!NT_SUCCESS(status)) {
        KdPrint(("usbshare: WdfDeviceCreate failed 0x%x\n", status));
        return status;
    }

    context = GetDeviceContext(device);
    RtlZeroMemory(context, sizeof(DEVICE_CONTEXT));

    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
    attributes.ParentObject = device;

    status = WdfSpinLockCreate(&attributes, &context->ClaimLock);
    if (!NT_SUCCESS(status)) {
        return status;
    }

    status = WdfSpinLockCreate(&attributes, &context->PendingLock);
    if (!NT_SUCCESS(status)) {
        return status;
    }

    status = WdfCollectionCreate(&attributes, &context->PendingTransfers);
    if (!NT_SUCCESS(status)) {
        return status;
    }

    /*
     * Default queue. Requests we do not recognise are forwarded down by the
     * framework because this is a filter device.
     */
    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchParallel);
    queueConfig.EvtIoDeviceControl = UsbShareEvtIoDeviceControl;
    queueConfig.EvtIoInternalDeviceControl = UsbShareEvtIoInternalDeviceControl;
    queueConfig.EvtIoDefault = UsbShareEvtIoDefault;

    status = WdfIoQueueCreate(device, &queueConfig, WDF_NO_OBJECT_ATTRIBUTES, &queue);
    if (!NT_SUCCESS(status)) {
        KdPrint(("usbshare: WdfIoQueueCreate failed 0x%x\n", status));
        return status;
    }

    /* Publish the interface so user mode can find this device. */
    status = WdfDeviceCreateDeviceInterface(device, &GUID_DEVINTERFACE_USBSHARE, NULL);
    if (!NT_SUCCESS(status)) {
        KdPrint(("usbshare: WdfDeviceCreateDeviceInterface failed 0x%x\n", status));
        return status;
    }

    return STATUS_SUCCESS;
}

NTSTATUS
UsbShareEvtDevicePrepareHardware(
    _In_ WDFDEVICE Device,
    _In_ WDFCMRESLIST ResourcesRaw,
    _In_ WDFCMRESLIST ResourcesTranslated
)
{
    NTSTATUS status;
    PDEVICE_CONTEXT context = GetDeviceContext(Device);
    WDF_USB_DEVICE_CREATE_CONFIG createConfig;
    USB_DEVICE_DESCRIPTOR deviceDescriptor;
    WDF_USB_DEVICE_SELECT_CONFIG_PARAMS configParams;
    UCHAR i;

    UNREFERENCED_PARAMETER(ResourcesRaw);
    UNREFERENCED_PARAMETER(ResourcesTranslated);

    /* PrepareHardware can run more than once across power transitions. */
    if (context->UsbDevice != NULL) {
        return STATUS_SUCCESS;
    }

    WDF_USB_DEVICE_CREATE_CONFIG_INIT(&createConfig, USBD_CLIENT_CONTRACT_VERSION_602);

    status = WdfUsbTargetDeviceCreateWithParameters(Device, &createConfig,
                                                    WDF_NO_OBJECT_ATTRIBUTES,
                                                    &context->UsbDevice);
    if (!NT_SUCCESS(status)) {
        KdPrint(("usbshare: WdfUsbTargetDeviceCreateWithParameters failed 0x%x\n", status));
        return status;
    }

    WdfUsbTargetDeviceGetDeviceDescriptor(context->UsbDevice, &deviceDescriptor);

    context->Info.VendorId = deviceDescriptor.idVendor;
    context->Info.ProductId = deviceDescriptor.idProduct;
    context->Info.BcdDevice = deviceDescriptor.bcdDevice;
    context->Info.DeviceClass = deviceDescriptor.bDeviceClass;
    context->Info.DeviceSubClass = deviceDescriptor.bDeviceSubClass;
    context->Info.DeviceProtocol = deviceDescriptor.bDeviceProtocol;
    context->Info.NumConfigurations = deviceDescriptor.bNumConfigurations;
    context->Info.ConfigurationValue = 1;

    /*
     * Select a configuration so pipe handles become available.
     *
     * This is the part most likely to need adjusting: on a device the class
     * driver has already configured, selecting again may be redundant or
     * disruptive. A more careful implementation would query the current
     * configuration first and only select if none is active.
     */
    WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_SINGLE_INTERFACE(&configParams);

    status = WdfUsbTargetDeviceSelectConfig(context->UsbDevice,
                                            WDF_NO_OBJECT_ATTRIBUTES,
                                            &configParams);
    if (!NT_SUCCESS(status)) {
        KdPrint(("usbshare: WdfUsbTargetDeviceSelectConfig failed 0x%x\n", status));
        /*
         * Not fatal: without pipes only control transfers work, but the
         * filter must not break the device for the class driver either way.
         */
        return STATUS_SUCCESS;
    }

    context->UsbInterface = configParams.Types.SingleInterface.ConfiguredUsbInterface;

    /* Map pipes by full endpoint address. */
    {
        BYTE pipeCount = configParams.Types.SingleInterface.NumberConfiguredPipes;
        for (i = 0; i < pipeCount; i++) {
            WDF_USB_PIPE_INFORMATION pipeInfo;
            WDFUSBPIPE pipe;

            WDF_USB_PIPE_INFORMATION_INIT(&pipeInfo);
            pipe = WdfUsbInterfaceGetConfiguredPipe(context->UsbInterface, i, &pipeInfo);
            if (pipe != NULL) {
                context->Pipes[pipeInfo.EndpointAddress] = pipe;

                /*
                 * Let short reads through. Without this a transfer that
                 * returns fewer bytes than requested fails, which is normal
                 * and expected for interrupt endpoints.
                 */
                WdfUsbTargetPipeSetNoMaximumPacketSizeCheck(pipe);
            }
        }
    }

    return STATUS_SUCCESS;
}

VOID
UsbShareEvtDeviceFileCreate(
    _In_ WDFDEVICE Device,
    _In_ WDFREQUEST Request,
    _In_ WDFFILEOBJECT FileObject
)
{
    UNREFERENCED_PARAMETER(Device);
    UNREFERENCED_PARAMETER(FileObject);

    /* Opening the handle is always allowed; claiming is a separate step. */
    WdfRequestComplete(Request, STATUS_SUCCESS);
}

VOID
UsbShareEvtFileClose(
    _In_ WDFFILEOBJECT FileObject
)
{
    WDFDEVICE device = WdfFileObjectGetDevice(FileObject);
    PDEVICE_CONTEXT context = GetDeviceContext(device);

    /*
     * The safety net: if this handle held the claim, give the device back.
     * This runs whether the client exited cleanly or was killed.
     */
    UsbShareReleaseClaim(context, FileObject);
}

BOOLEAN
UsbShareIsClaimed(
    _In_ PDEVICE_CONTEXT Context
)
{
    BOOLEAN claimed;

    WdfSpinLockAcquire(Context->ClaimLock);
    claimed = Context->Claimed;
    WdfSpinLockRelease(Context->ClaimLock);

    return claimed;
}

VOID
UsbShareReleaseClaim(
    _In_ PDEVICE_CONTEXT Context,
    _In_opt_ WDFFILEOBJECT Owner
)
{
    BOOLEAN released = FALSE;

    WdfSpinLockAcquire(Context->ClaimLock);

    /*
     * With an owner given, only that owner may release — otherwise closing an
     * unrelated handle would hand the device back while a client is using it.
     */
    if (Context->Claimed && (Owner == NULL || Context->ClaimOwner == Owner)) {
        Context->Claimed = FALSE;
        Context->ClaimOwner = NULL;
        released = TRUE;
    }

    WdfSpinLockRelease(Context->ClaimLock);

    if (released) {
        KdPrint(("usbshare: device released\n"));
    }
}
