/*
 *  Copyright (c) 2016, The OpenThread Authors.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. Neither the name of the copyright holder nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

#include "precomp.h"
#include "device.tmh"

// IoControl Device Object from IoCreateDeviceSecure
PDEVICE_OBJECT IoDeviceObject = NULL;

// Global context for device control callbacks
POTLWF_DEVICE_EXTENSION FilterDeviceExtension = NULL;

/*

Powershell script to generate security desciptors:

$sddl = "D:P(A;;GA;;;SY)(A;;GA;;;NS)(A;;GA;;;BA)(A;;GA;;;WD)(A;;GA;;;S-1-15-3-3)"
$blob = ([wmiclass]"Win32_SecurityDescriptorHelper").SDDLToBinarySD($sddl).BinarySD
$string = [BitConverter]::ToString($blob)
$string = $string -replace '-', ''
$string = $string -replace '(..)(..)(..)(..)', '0x$4$3$2$1, '
$string -replace '(.{10}, .{10}, .{10}, .{10},) ', "$&`n"

*/
const unsigned long c_sdThreadLwf[] =
{
    0x90040001, 0x00000000, 0x00000000, 0x00000000,
    0x00000014, 0x00740002, 0x00000005, 0x00140000,
    0x10000000, 0x00000101, 0x05000000, 0x00000012,
    0x00140000, 0x10000000, 0x00000101, 0x05000000,
    0x00000014, 0x00180000, 0x10000000, 0x00000201,
    0x05000000, 0x00000020, 0x00000220, 0x00140000,
    0x10000000, 0x00000101, 0x01000000, 0x00000000,
    0x00180000, 0x10000000, 0x00000201, 0x0F000000,
    0x00000003, 0x00000003
};

_No_competing_thread_
INITCODE
NDIS_STATUS
otLwfRegisterDevice(
    VOID
    )
{
    NTSTATUS                        Status = NDIS_STATUS_SUCCESS;
    UNICODE_STRING                  DeviceName;
    UNICODE_STRING                  DeviceLinkUnicodeString;
    PDEVICE_OBJECT                  DeviceObject;

    LogFuncEntry(DRIVER_DEFAULT);

    NT_ASSERT(FilterDeviceExtension == NULL);

    NdisInitUnicodeString(&DeviceName, NTDEVICE_STRING);
    NdisInitUnicodeString(&DeviceLinkUnicodeString, LINKNAME_STRING);

    Status = IoCreateDeviceSecure(FilterDriverObject,                     // DriverObject
                                  sizeof(OTLWF_DEVICE_EXTENSION),        // DeviceExtension
                                  &DeviceName,                            // DeviceName
                                  FILE_DEVICE_NETWORK,                    // DeviceType
                                  FILE_DEVICE_SECURE_OPEN,                // DeviceCharacteristics
                                  FALSE,                                  // Exclusive
                                  &SDDL_DEVOBJ_KERNEL_ONLY,               // security attributes
                                  NULL,                                   // security override device class
                                  &DeviceObject);                         // DeviceObject

    if (NT_SUCCESS(Status))
    {
        DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
        Status = IoCreateSymbolicLink(&DeviceLinkUnicodeString, &DeviceName);

        if (!NT_SUCCESS(Status))
        {
            LogError(DRIVER_DEFAULT, "IoCreateSymbolicLink failed, %!STATUS!", Status);
            IoDeleteDevice(DeviceObject);
        }
        else
        {
            FilterDeviceExtension = (POTLWF_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
            RtlZeroMemory(FilterDeviceExtension, sizeof(OTLWF_DEVICE_EXTENSION));

            FilterDeviceExtension->Signature = 'FTDR';
            FilterDeviceExtension->Handle = FilterDriverHandle;

            NdisAllocateSpinLock(&FilterDeviceExtension->Lock);
            InitializeListHead(&FilterDeviceExtension->ClientList);

            #pragma push
            #pragma warning(disable:28168) // The function 'otLwfDispatch' does not have a _Dispatch_type_ annotation matching dispatch table position *
            FilterDriverObject->MajorFunction[IRP_MJ_CREATE] = otLwfDispatch;
            FilterDriverObject->MajorFunction[IRP_MJ_CLEANUP] = otLwfDispatch;
            FilterDriverObject->MajorFunction[IRP_MJ_CLOSE] = otLwfDispatch;
            FilterDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = otLwfDeviceIoControl;
            #pragma pop

            HANDLE fileHandle;
            Status = ObOpenObjectByPointer(DeviceObject,
                                           OBJ_KERNEL_HANDLE,
                                           NULL,
                                           WRITE_DAC,
                                           0,
                                           KernelMode,
                                           &fileHandle);
            if (NT_SUCCESS(Status))
            {
                Status = ZwSetSecurityObject(fileHandle, 
                                             DACL_SECURITY_INFORMATION, 
                                             (PSECURITY_DESCRIPTOR)c_sdThreadLwf);

                if (!NT_SUCCESS(Status))
                {
                    LogError(DRIVER_DEFAULT, "ZwSetSecurityObject failed, %!STATUS!", Status);
                }

                ZwClose(fileHandle);
            }
            else
            {
                LogError(DRIVER_DEFAULT, "ObOpenObjectByPointer failed, %!STATUS!", Status);
            }

            IoDeviceObject = DeviceObject;
        }
    }
    else
    {
        LogError(DRIVER_DEFAULT, "IoCreateDeviceSecure failed, %!STATUS!", Status);
    }

    LogFuncExitNT(DRIVER_DEFAULT, Status);

    return (NDIS_STATUS)Status;
}

PIRP
otLwfDeviceClientCleanup(
    POTLWF_DEVICE_CLIENT DeviceClient
    )
{
    PIRP IrpToCancel = NULL;

    // Clean the FileObject context
    DeviceClient->FileObject->FsContext2 = NULL;

    // Release pending IRP
    if (DeviceClient->PendingNotificationIRP)
    {
        IrpToCancel = DeviceClient->PendingNotificationIRP;
        DeviceClient->PendingNotificationIRP = NULL;
    }

    // Free all pending notifications
    NT_ASSERT(DeviceClient->NotificationSize <= OTLWF_MAX_PENDING_NOTIFICATIONS_PER_CLIENT);
    for (UCHAR i = 0; i < DeviceClient->NotificationSize; i++)
    {
        UCHAR index = (DeviceClient->NotificationOffset + i) % OTLWF_MAX_PENDING_NOTIFICATIONS_PER_CLIENT;
        otLwfReleaseNotification(DeviceClient->PendingNotifications[index]);
    }

    return IrpToCancel;
}

_No_competing_thread_
_IRQL_requires_max_(PASSIVE_LEVEL)
VOID
otLwfDeregisterDevice(
    VOID
    )
{
    LogFuncEntry(DRIVER_DEFAULT);

    if (IoDeviceObject != NULL)
    {
        NT_ASSERT(FilterDeviceExtension);
        NdisFreeSpinLock(&FilterDeviceExtension->Lock);
        
        // Clean up all pending clients
        PLIST_ENTRY Link = FilterDeviceExtension->ClientList.Flink;
        while (Link != &FilterDeviceExtension->ClientList)
        {
            POTLWF_DEVICE_CLIENT DeviceClient = CONTAINING_RECORD(Link, OTLWF_DEVICE_CLIENT, Link);
            PIRP IrpToCancel = NULL;

            // Set next link
            Link = Link->Flink;
            
            // Make sure to clean up any left overs from the device client
            IrpToCancel = otLwfDeviceClientCleanup(DeviceClient);

            // Complete the pending IRP since we are shutting down
            if (IrpToCancel)
            {
                // Before we are allowed to complete the pending IRP, we must remove the cancel routine
                KIRQL irql;
                IoAcquireCancelSpinLock(&irql);
                IoSetCancelRoutine(IrpToCancel, NULL);
                IoReleaseCancelSpinLock(irql);

                IrpToCancel->IoStatus.Status = STATUS_CANCELLED;
                IrpToCancel->IoStatus.Information = 0;
                IoCompleteRequest(IrpToCancel, IO_NO_INCREMENT);
            }

            // Remove the device client from the list
            RemoveEntryList(&DeviceClient->Link);

            // Delete the device client
            NdisFreeMemory(DeviceClient, 0, 0);
        }

        IoDeleteDevice(IoDeviceObject);
    }

    IoDeviceObject = NULL;

    LogFuncExit(DRIVER_DEFAULT);
}

_Use_decl_annotations_
NTSTATUS
otLwfDispatch(
    PDEVICE_OBJECT       DeviceObject,
    PIRP                 Irp
    )
{
    PIO_STACK_LOCATION      IrpStack;
    NTSTATUS                Status = STATUS_SUCCESS;
    PIRP                    IrpToCancel = NULL;
    POTLWF_DEVICE_CLIENT    DeviceClient = NULL;

    UNREFERENCED_PARAMETER(DeviceObject);

    LogFuncEntry(DRIVER_IOCTL);

    IrpStack = IoGetCurrentIrpStackLocation(Irp);

    NdisAcquireSpinLock(&FilterDeviceExtension->Lock);

    switch (IrpStack->MajorFunction)
    {
        case IRP_MJ_CREATE:
            LogInfo(DRIVER_IOCTL, "Client %p attached.", IrpStack->FileObject);

            if (FilterDeviceExtension->ClientListSize >= OTLWF_MAX_CLIENTS)
            {
                LogError(DRIVER_IOCTL, "Already have max clients!");
                Status = STATUS_TOO_MANY_SESSIONS;
                break;
            }

            DeviceClient = FILTER_ALLOC_DEVICE_CLIENT();
            if (DeviceClient)
            {
                RtlZeroMemory(DeviceClient, sizeof(OTLWF_DEVICE_CLIENT));
                DeviceClient->FileObject = IrpStack->FileObject;

                NT_ASSERT(IrpStack->FileObject->FsContext2 == NULL);
                IrpStack->FileObject->FsContext2 = DeviceClient;
                
                // Insert into the client list
                InsertTailList(&FilterDeviceExtension->ClientList, &DeviceClient->Link);
                FilterDeviceExtension->ClientListSize++;
            }
            else
            {
                Status = STATUS_INSUFFICIENT_RESOURCES;
            }
            break;

        case IRP_MJ_CLEANUP:
            LogInfo(DRIVER_IOCTL, "Client %p cleaning up.", IrpStack->FileObject);

            DeviceClient = (POTLWF_DEVICE_CLIENT)IrpStack->FileObject->FsContext2;

            // Make sure to clean up any left overs from the device client
            IrpToCancel = otLwfDeviceClientCleanup(DeviceClient);

            // Remove the device client from the list
            RemoveEntryList(&DeviceClient->Link);
            FilterDeviceExtension->ClientListSize--;

            // Delete the device client
            NdisFreeMemory(DeviceClient, 0, 0);
            break;

        case IRP_MJ_CLOSE:
            LogInfo(DRIVER_IOCTL, "Client %p detatched.", IrpStack->FileObject);
            break;

        default:
            break;
    }

    NdisReleaseSpinLock(&FilterDeviceExtension->Lock);

    // Cancel the pending notification IRP if set
    if (IrpToCancel)
    {
        // Complete the pending IRP
        IrpToCancel->IoStatus.Status = STATUS_CANCELLED;
        IrpToCancel->IoStatus.Information = 0;
        IoCompleteRequest(IrpToCancel, IO_NO_INCREMENT);
    }

    Irp->IoStatus.Status = Status;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    LogFuncExitNT(DRIVER_IOCTL, Status);

    return Status;
}

_Use_decl_annotations_
NTSTATUS
otLwfDeviceIoControl(
    PDEVICE_OBJECT        DeviceObject,
    PIRP                  Irp
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    BOOLEAN CompleteIRP = TRUE;

    PVOID               IoBuffer = Irp->AssociatedIrp.SystemBuffer;

    PIO_STACK_LOCATION  IrpSp = IoGetCurrentIrpStackLocation(Irp);
    ULONG               InputBufferLength = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
    ULONG               OutputBufferLength = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;
    ULONG               IoControlCode = IrpSp->Parameters.DeviceIoControl.IoControlCode;

	ULONG               FuncCode = (IoControlCode >> 2) & 0xFFF;

    LogFuncEntryMsg(DRIVER_IOCTL, "%p", IrpSp->FileObject);

#if DBG
    ASSERT(((POTLWF_DEVICE_EXTENSION)DeviceObject->DeviceExtension)->Signature == 'FTDR');
#else
    UNREFERENCED_PARAMETER(DeviceObject);
#endif

    // We only allow PASSIVE_LEVEL calls
    if (KeGetCurrentIrql() > PASSIVE_LEVEL)
    {
        LogWarning(DRIVER_IOCTL, "FilterDeviceIoControl called higher than PASSIVE.");
        status = STATUS_NOT_SUPPORTED;
        RtlZeroMemory(IoBuffer, OutputBufferLength);
        OutputBufferLength = 0;
        goto error;
    }

    if (FuncCode >= MIN_OTLWF_IOCTL_FUNC_CODE && FuncCode <= MAX_OTLWF_IOCTL_FUNC_CODE)
    {
        CompleteIRP = FALSE;
        status = otLwfIoCtlOpenThreadControl(Irp);
        goto error;
    }

    // Check the IoControlCode to determine which IOCTL we are processing
    switch (IoControlCode)
    {
        case IOCTL_OTLWF_QUERY_NOTIFICATION:
            CompleteIRP = FALSE;
            status = otLwfQueryNextNotification(Irp);
            break;

        case IOCTL_OTLWF_ENUMERATE_DEVICES:
            status =
                otLwfIoCtlEnumerateInterfaces(
                    IoBuffer, InputBufferLength,
                    IoBuffer, &OutputBufferLength
                    );
            break;

        case IOCTL_OTLWF_QUERY_DEVICE:
            status =
                otLwfIoCtlQueryInterface(
                    IoBuffer, InputBufferLength,
                    IoBuffer, &OutputBufferLength
                    );
            break;

        default:
            status = STATUS_NOT_IMPLEMENTED;
            RtlZeroMemory(IoBuffer, OutputBufferLength);
            OutputBufferLength = 0;
            break;
    }

error:

    if (CompleteIRP)
    {
        Irp->IoStatus.Status = status;
        Irp->IoStatus.Information = OutputBufferLength;

        IoCompleteRequest(Irp, IO_NO_INCREMENT);
    }

    LogFuncExitNT(DRIVER_IOCTL, status);

    return status;
}

_Use_decl_annotations_
PMS_FILTER
otLwfFindAndRefInterface(
    _In_ PGUID  InterfaceGuid
    )
{
    PMS_FILTER pOutput = NULL;

    NdisAcquireSpinLock(&FilterListLock);

    for (PLIST_ENTRY Link = FilterModuleList.Flink; Link != &FilterModuleList; Link = Link->Flink)
    {
        PMS_FILTER pFilter = CONTAINING_RECORD(Link, MS_FILTER, FilterModuleLink);

        if (pFilter->State == FilterRunning &&
            memcmp(InterfaceGuid, &pFilter->InterfaceGuid, sizeof(GUID)) == 0)
        {
            // Increment ref count on interface
            RtlIncrementReferenceCount(&pFilter->IoControlReferences);
            pOutput = pFilter;
            break;
        }
    }

    NdisReleaseSpinLock(&FilterListLock);

    return pOutput;
}

_Use_decl_annotations_
VOID
otLwfReleaseInterface(
    _In_ PMS_FILTER pFilter
    )
{
    // Decrement ref count, and set shutdown event if it goes to 0
    if (RtlDecrementReferenceCount(&pFilter->IoControlReferences))
    {
        NdisSetEvent(&pFilter->IoControlShutdownComplete);
    }
}

//
// Notification Functions
//

_IRQL_requires_max_(DISPATCH_LEVEL)
VOID
otLwfReleaseNotification(
    _In_ PFILTER_NOTIFICATION_ENTRY NotifEntry
    )
{
    if (RtlDecrementReferenceCount(&NotifEntry->RefCount))
    {
        NdisFreeMemory(NotifEntry, 0, 0);
    }
}

// Indicates a new notification
_IRQL_requires_max_(DISPATCH_LEVEL)
VOID
otLwfIndicateNotification(
    _In_ PFILTER_NOTIFICATION_ENTRY NotifEntry
    )
{
    PIRP IrpsToComplete[OTLWF_MAX_CLIENTS] = {0};
    UCHAR IrpOffset = 0;

    LogFuncEntry(DRIVER_IOCTL);

    // Initialize with a local ref
    NotifEntry->RefCount = 1;

    if (FilterDeviceExtension == NULL) goto error;

    NdisAcquireSpinLock(&FilterDeviceExtension->Lock);

    // Pend the notification for each client
    PLIST_ENTRY Link = FilterDeviceExtension->ClientList.Flink;
    while (Link != &FilterDeviceExtension->ClientList)
    {
        POTLWF_DEVICE_CLIENT DeviceClient = CONTAINING_RECORD(Link, OTLWF_DEVICE_CLIENT, Link);

        // Set next link
        Link = Link->Flink;
        
        KIRQL irql;
        IoAcquireCancelSpinLock(&irql);

        // If there are other pending notifications or we don't have a pending IRP saved
        // then just go ahead and add the notification to the list
        NT_ASSERT(DeviceClient->NotificationSize <= OTLWF_MAX_PENDING_NOTIFICATIONS_PER_CLIENT);
        if (DeviceClient->NotificationSize != 0 ||
            DeviceClient->PendingNotificationIRP == NULL
            )
        {
            // Calculate the next index
            UCHAR Index = (DeviceClient->NotificationOffset + DeviceClient->NotificationSize) % OTLWF_MAX_PENDING_NOTIFICATIONS_PER_CLIENT;

            // Add additional ref to the notif
            RtlIncrementReferenceCount(&NotifEntry->RefCount);

            // If we are at the max already, release the oldest
            if (DeviceClient->NotificationSize == OTLWF_MAX_PENDING_NOTIFICATIONS_PER_CLIENT)
            {
                LogWarning(DRIVER_IOCTL, "Dropping old notification!");
                otLwfReleaseNotification(DeviceClient->PendingNotifications[DeviceClient->NotificationOffset]);
                DeviceClient->NotificationOffset = (DeviceClient->NotificationOffset + 1) % OTLWF_MAX_PENDING_NOTIFICATIONS_PER_CLIENT;
            }
            else
            {
                DeviceClient->NotificationSize++;
            }

            // Copy the notification to the next space
            DeviceClient->PendingNotifications[Index] = NotifEntry;
        }
        else
        {
            // Before we are allowed to complete the pending IRP, we must remove the cancel routine
            IoSetCancelRoutine(DeviceClient->PendingNotificationIRP, NULL);

            IrpsToComplete[IrpOffset] = DeviceClient->PendingNotificationIRP;
            IrpOffset++;

            DeviceClient->PendingNotificationIRP = NULL;
        }
        
        // Release the cancel spin lock
        IoReleaseCancelSpinLock(irql);
    }

    NdisReleaseSpinLock(&FilterDeviceExtension->Lock);

    // Complete any IRPs now, outside the lock
    for (UCHAR i = 0; i < IrpOffset; i++)
    {
        PIRP IrpToComplete = IrpsToComplete[i];

        // Copy the notification payload
        PVOID IoBuffer = IrpToComplete->AssociatedIrp.SystemBuffer;
        memcpy(IoBuffer, &NotifEntry->Notif, sizeof(OTLWF_NOTIFICATION));
        IrpToComplete->IoStatus.Information = sizeof(OTLWF_NOTIFICATION);

        // Complete the IRP
        IrpToComplete->IoStatus.Status = STATUS_SUCCESS;
        IoCompleteRequest(IrpToComplete, IO_NO_INCREMENT);
    }

error:
    
    // Release local ref on the notification
    otLwfReleaseNotification(NotifEntry);

    LogFuncExit(DRIVER_IOCTL);
}

DRIVER_CANCEL otLwfQueryNotificationCancelled;

_Use_decl_annotations_
VOID
otLwfQueryNotificationCancelled(
    _Inout_ PDEVICE_OBJECT DeviceObject,
    _Inout_ _IRQL_uses_cancel_ struct _IRP *Irp
)
{
    UNREFERENCED_PARAMETER(DeviceObject);

    LogFuncEntry(DRIVER_IOCTL);
    
    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
    POTLWF_DEVICE_CLIENT DeviceClient = (POTLWF_DEVICE_CLIENT)IrpSp->FileObject->FsContext2;

    if (DeviceClient)
    {
        DeviceClient->PendingNotificationIRP = NULL;
    }

    IoReleaseCancelSpinLock(Irp->CancelIrql);

    Irp->IoStatus.Status = STATUS_CANCELLED;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    LogFuncExit(DRIVER_IOCTL);
}

// Queries the next notification
_IRQL_requires_max_(DISPATCH_LEVEL)
NTSTATUS
otLwfQueryNextNotification(
    _In_ PIRP Irp
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    POTLWF_DEVICE_CLIENT DeviceClient = NULL;
    PFILTER_NOTIFICATION_ENTRY NotifEntry = NULL;

    LogFuncEntry(DRIVER_IOCTL);

    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
    ULONG OutputBufferLength = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;

    // Validate we have a big enough buffer
    if (OutputBufferLength < sizeof(OTLWF_NOTIFICATION))
    {
        RtlZeroMemory(Irp->AssociatedIrp.SystemBuffer, OutputBufferLength);
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto error;
    }
    
    DeviceClient = (POTLWF_DEVICE_CLIENT)IrpSp->FileObject->FsContext2;
    if (DeviceClient == NULL)
    {
        status = STATUS_DEVICE_NOT_READY;
        goto error;
    }

    NdisAcquireSpinLock(&FilterDeviceExtension->Lock);

    // Check to see if there are any notifications available
    if (DeviceClient->NotificationSize == 0)
    {
        // Set the cancel routine
        IoSetCancelRoutine(Irp, otLwfQueryNotificationCancelled);

        // Mark the Irp as pending
        IoMarkIrpPending(Irp);

        // Save the IRP to complete later, when we have a notification
        DeviceClient->PendingNotificationIRP = Irp;
    }
    else
    {
        // Get the notification
        NotifEntry = DeviceClient->PendingNotifications[DeviceClient->NotificationOffset];
        DeviceClient->PendingNotifications[DeviceClient->NotificationOffset] = NULL;

        // Increment the offset and decrement the size
        DeviceClient->NotificationOffset = (DeviceClient->NotificationOffset + 1) % OTLWF_MAX_PENDING_NOTIFICATIONS_PER_CLIENT;
        DeviceClient->NotificationSize--;
    }

    NdisReleaseSpinLock(&FilterDeviceExtension->Lock);

    // If we found a notification, complete the IRP with it
    if (NotifEntry)
    {
        // Copy the notification payload
        PVOID IoBuffer = Irp->AssociatedIrp.SystemBuffer;
        memcpy(IoBuffer, &NotifEntry->Notif, sizeof(OTLWF_NOTIFICATION));
        Irp->IoStatus.Information = sizeof(OTLWF_NOTIFICATION);

        // Free the notification
        otLwfReleaseNotification(NotifEntry);
    }
    else
    {
        // Otherwise, set status to indicate we are pending the IRP
        status = STATUS_PENDING;
    }

error:

    // Complete the IRP if we aren't pending
    if (status != STATUS_PENDING)
    {
        Irp->IoStatus.Status = status;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
    }

    LogFuncExitNT(DRIVER_IOCTL, status);

    return status;
}
