/*
 *  TAP-Windows -- A kernel driver to provide virtual tap
 *                 device functionality on Windows.
 *
 *  This code was inspired by the CIPE-Win32 driver by Damion K. Wilson.
 *
 *  This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc.,
 *  and is released under the GPL version 2 (see below).
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2
 *  as published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program (see the file COPYING included with this
 *  distribution); if not, write to the Free Software Foundation, Inc.,
 *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

//------------------
// Memory Management
//------------------

#include "tap.h"

PVOID
MemAlloc(
    __in ULONG p_Size,
    __in BOOLEAN zero
    )
{
    PVOID l_Return = NULL;

    if (p_Size)
    {
        __try
        {
            if (NdisAllocateMemoryWithTag (&l_Return, p_Size, 'APAT')
                == NDIS_STATUS_SUCCESS)
            {
                if (zero)
                {
                    NdisZeroMemory (l_Return, p_Size);
                }
            }
            else
            {
                l_Return = NULL;
            }
        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            l_Return = NULL;
        }
    }

    return l_Return;
}

VOID
MemFree(
    __in PVOID p_Addr,
    __in ULONG p_Size
    )
{
    if (p_Addr && p_Size)
    {
        __try
        {
#if DBG
            NdisZeroMemory (p_Addr, p_Size);
#endif
            NdisFreeMemory (p_Addr, p_Size, 0);
        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
        }
    }
}

//======================================================================
// TAP Packet Queue Support
//======================================================================

VOID
tapPacketQueueInsertTail(
    __in PTAP_PACKET_QUEUE  TapPacketQueue,
    __in PTAP_PACKET        TapPacket
    )
{
    KIRQL  irql;

    KeAcquireSpinLock(&TapPacketQueue->QueueLock,&irql);

    InsertTailList(&TapPacketQueue->Queue,&TapPacket->QueueLink);

    // BUGBUG!!! Enforce PACKET_QUEUE_SIZE queue count limit???
    // For NDIS 6 there is no per-packet status, so this will need to
    // be handled on per-NBL basis in AdapterSendNetBufferLists...

    // Update counts
    ++TapPacketQueue->Count;

    if(TapPacketQueue->Count > TapPacketQueue->MaxCount)
    {
        TapPacketQueue->MaxCount = TapPacketQueue->Count;

        DEBUGP (("[TAP] tapPacketQueueInsertTail: New MAX queued packet count = %d\n",
            TapPacketQueue->MaxCount));
    }

    KeReleaseSpinLock(&TapPacketQueue->QueueLock,irql);
}

// Call with QueueLock held
PTAP_PACKET
tapPacketRemoveHeadLocked(
    __in PTAP_PACKET_QUEUE  TapPacketQueue
    )
{
    PTAP_PACKET     tapPacket = NULL;
    PLIST_ENTRY     listEntry;

    listEntry = RemoveHeadList(&TapPacketQueue->Queue);

    if(listEntry != &TapPacketQueue->Queue)
    {
        tapPacket = CONTAINING_RECORD(listEntry, TAP_PACKET, QueueLink);

        // Update counts
        --TapPacketQueue->Count;
    }

    return tapPacket;
}

PTAP_PACKET
tapPacketRemoveHead(
    __in PTAP_PACKET_QUEUE  TapPacketQueue
    )
{
    PTAP_PACKET     tapPacket = NULL;
    KIRQL           irql;

    KeAcquireSpinLock(&TapPacketQueue->QueueLock,&irql);

    tapPacket = tapPacketRemoveHeadLocked(TapPacketQueue);

    KeReleaseSpinLock(&TapPacketQueue->QueueLock,irql);

    return tapPacket;
}

VOID
tapPacketQueueInitialize(
    __in PTAP_PACKET_QUEUE  TapPacketQueue
    )
{
    KeInitializeSpinLock(&TapPacketQueue->QueueLock);

    NdisInitializeListHead(&TapPacketQueue->Queue);
}

//======================================================================
// TAP Cancel-Safe Queue Support
//======================================================================

VOID
tapIrpCsqInsert (
    __in struct _IO_CSQ    *Csq,
    __in PIRP              Irp
    )
{
    PTAP_IRP_CSQ          tapIrpCsq;

    tapIrpCsq = (PTAP_IRP_CSQ )Csq;

    InsertTailList(
        &tapIrpCsq->Queue,
        &Irp->Tail.Overlay.ListEntry
        );

    // Update counts
    ++tapIrpCsq->Count;

    if(tapIrpCsq->Count > tapIrpCsq->MaxCount)
    {
        tapIrpCsq->MaxCount = tapIrpCsq->Count;

        DEBUGP (("[TAP] tapIrpCsqInsert: New MAX queued IRP count = %d\n",
            tapIrpCsq->MaxCount));
    }
}

VOID
tapIrpCsqRemoveIrp(
    __in PIO_CSQ Csq,
    __in PIRP    Irp
    )
{
    PTAP_IRP_CSQ          tapIrpCsq;

    tapIrpCsq = (PTAP_IRP_CSQ )Csq;

    // Update counts
    --tapIrpCsq->Count;

    RemoveEntryList(&Irp->Tail.Overlay.ListEntry);
}


PIRP
tapIrpCsqPeekNextIrp(
    __in PIO_CSQ Csq,
    __in PIRP    Irp,
    __in PVOID   PeekContext
    )
{
    PTAP_IRP_CSQ          tapIrpCsq;
    PIRP                    nextIrp = NULL;
    PLIST_ENTRY             nextEntry;
    PLIST_ENTRY             listHead;
    PIO_STACK_LOCATION      irpStack;

    tapIrpCsq = (PTAP_IRP_CSQ )Csq;

    listHead = &tapIrpCsq->Queue;

    //
    // If the IRP is NULL, we will start peeking from the listhead, else
    // we will start from that IRP onwards. This is done under the
    // assumption that new IRPs are always inserted at the tail.
    //

    if (Irp == NULL)
    {
        nextEntry = listHead->Flink;
    }
    else
    {
        nextEntry = Irp->Tail.Overlay.ListEntry.Flink;
    }

    while(nextEntry != listHead)
    {
        nextIrp = CONTAINING_RECORD(nextEntry, IRP, Tail.Overlay.ListEntry);

        irpStack = IoGetCurrentIrpStackLocation(nextIrp);

        //
        // If context is present, continue until you find a matching one.
        // Else you break out as you got next one.
        //
        if (PeekContext)
        {
            if (irpStack->FileObject == (PFILE_OBJECT) PeekContext)
            {
                break;
            }
        }
        else
        {
            break;
        }

        nextIrp = NULL;
        nextEntry = nextEntry->Flink;
    }

    return nextIrp;
}

//
// tapIrpCsqAcquireQueueLock modifies the execution level of the current processor.
// 
// KeAcquireSpinLock raises the execution level to Dispatch Level and stores
// the current execution level in the Irql parameter to be restored at a later
// time.  KeAcqurieSpinLock also requires us to be running at no higher than
// Dispatch level when it is called.
//
// The annotations reflect these changes and requirments.
//

__drv_raisesIRQL(DISPATCH_LEVEL)
__drv_maxIRQL(DISPATCH_LEVEL)
VOID
tapIrpCsqAcquireQueueLock(
     __in PIO_CSQ Csq,
     __out PKIRQL  Irql
    )
{
    PTAP_IRP_CSQ          tapIrpCsq;

    tapIrpCsq = (PTAP_IRP_CSQ )Csq;

    //
    // Suppressing because the address below csq is valid since it's
    // part of TAP_ADAPTER_CONTEXT structure.
    //
#pragma prefast(suppress: __WARNING_BUFFER_UNDERFLOW, "Underflow using expression 'adapter->PendingReadCsqQueueLock'")
    KeAcquireSpinLock(&tapIrpCsq->QueueLock, Irql);
}

//
// tapIrpCsqReleaseQueueLock modifies the execution level of the current processor.
// 
// KeReleaseSpinLock assumes we already hold the spin lock and are therefore
// running at Dispatch level.  It will use the Irql parameter saved in a
// previous call to KeAcquireSpinLock to return the thread back to it's original
// execution level.
//
// The annotations reflect these changes and requirments.
//

__drv_requiresIRQL(DISPATCH_LEVEL)
VOID
tapIrpCsqReleaseQueueLock(
     __in PIO_CSQ Csq,
     __in KIRQL   Irql
    )
{
    PTAP_IRP_CSQ          tapIrpCsq;

    tapIrpCsq = (PTAP_IRP_CSQ )Csq;

    //
    // Suppressing because the address below csq is valid since it's
    // part of TAP_ADAPTER_CONTEXT structure.
    //
#pragma prefast(suppress: __WARNING_BUFFER_UNDERFLOW, "Underflow using expression 'adapter->PendingReadCsqQueueLock'")
    KeReleaseSpinLock(&tapIrpCsq->QueueLock, Irql);
}

VOID
tapIrpCsqCompleteCanceledIrp(
    __in  PIO_CSQ             pCsq,
    __in  PIRP                Irp
    )
{
    UNREFERENCED_PARAMETER(pCsq);

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

VOID
tapIrpCsqInitialize(
    __in PTAP_IRP_CSQ  TapIrpCsq
    )
{
    KeInitializeSpinLock(&TapIrpCsq->QueueLock);

    NdisInitializeListHead(&TapIrpCsq->Queue);

    IoCsqInitialize(
        &TapIrpCsq->CsqQueue,
        tapIrpCsqInsert,
        tapIrpCsqRemoveIrp,
        tapIrpCsqPeekNextIrp,
        tapIrpCsqAcquireQueueLock,
        tapIrpCsqReleaseQueueLock,
        tapIrpCsqCompleteCanceledIrp
        );
}

VOID
tapIrpCsqFlush(
    __in PTAP_IRP_CSQ  TapIrpCsq
    )
{
    PIRP    pendingIrp;

    //
    // Flush the pending read IRP queue.
    //
    pendingIrp = IoCsqRemoveNextIrp(
                    &TapIrpCsq->CsqQueue,
                    NULL
                    );

    while(pendingIrp) 
    {
        // Cancel the IRP
        pendingIrp->IoStatus.Information = 0;
        pendingIrp->IoStatus.Status = STATUS_CANCELLED;
        IoCompleteRequest(pendingIrp, IO_NO_INCREMENT);

        pendingIrp = IoCsqRemoveNextIrp(
                        &TapIrpCsq->CsqQueue,
                        NULL
                        );
    }

    ASSERT(IsListEmpty(&TapIrpCsq->Queue));
}