/*
 *  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
 */

//
// Include files.
//

#include "tap.h"

NDIS_OID TAPSupportedOids[] =
{
        OID_GEN_HARDWARE_STATUS,
        OID_GEN_TRANSMIT_BUFFER_SPACE,
        OID_GEN_RECEIVE_BUFFER_SPACE,
        OID_GEN_TRANSMIT_BLOCK_SIZE,
        OID_GEN_RECEIVE_BLOCK_SIZE,
        OID_GEN_VENDOR_ID,
        OID_GEN_VENDOR_DESCRIPTION,
        OID_GEN_VENDOR_DRIVER_VERSION,
        OID_GEN_CURRENT_PACKET_FILTER,
        OID_GEN_CURRENT_LOOKAHEAD,
        OID_GEN_DRIVER_VERSION,
        OID_GEN_MAXIMUM_TOTAL_SIZE,
        OID_GEN_XMIT_OK,
        OID_GEN_RCV_OK,
        OID_GEN_STATISTICS,
#ifdef IMPLEMENT_OPTIONAL_OIDS
        OID_GEN_TRANSMIT_QUEUE_LENGTH,       // Optional
#endif // IMPLEMENT_OPTIONAL_OIDS
        OID_GEN_LINK_PARAMETERS,
        OID_GEN_INTERRUPT_MODERATION,
        OID_GEN_MEDIA_SUPPORTED,
        OID_GEN_MEDIA_IN_USE,
        OID_GEN_MAXIMUM_SEND_PACKETS,
        OID_GEN_XMIT_ERROR,
        OID_GEN_RCV_ERROR,
        OID_GEN_RCV_NO_BUFFER,
        OID_802_3_PERMANENT_ADDRESS,
        OID_802_3_CURRENT_ADDRESS,
        OID_802_3_MULTICAST_LIST,
        OID_802_3_MAXIMUM_LIST_SIZE,
        OID_802_3_RCV_ERROR_ALIGNMENT,
        OID_802_3_XMIT_ONE_COLLISION,
        OID_802_3_XMIT_MORE_COLLISIONS,
#ifdef IMPLEMENT_OPTIONAL_OIDS
        OID_802_3_XMIT_DEFERRED,             // Optional
        OID_802_3_XMIT_MAX_COLLISIONS,       // Optional
        OID_802_3_RCV_OVERRUN,               // Optional
        OID_802_3_XMIT_UNDERRUN,             // Optional
        OID_802_3_XMIT_HEARTBEAT_FAILURE,    // Optional
        OID_802_3_XMIT_TIMES_CRS_LOST,       // Optional
        OID_802_3_XMIT_LATE_COLLISIONS,      // Optional
        OID_PNP_CAPABILITIES,                // Optional
#endif // IMPLEMENT_OPTIONAL_OIDS
};

//======================================================================
// TAP NDIS 6 Miniport Callbacks
//======================================================================

// Returns with reference count initialized to one.
PTAP_ADAPTER_CONTEXT
tapAdapterContextAllocate(
    __in NDIS_HANDLE        MiniportAdapterHandle
)
{
    PTAP_ADAPTER_CONTEXT   adapter = NULL;

    adapter = (PTAP_ADAPTER_CONTEXT )NdisAllocateMemoryWithTagPriority(
        GlobalData.NdisDriverHandle,
        sizeof(TAP_ADAPTER_CONTEXT),
        TAP_ADAPTER_TAG,
        NormalPoolPriority
        );

    if(adapter)
    {
        NET_BUFFER_LIST_POOL_PARAMETERS  nblPoolParameters = {0};

        NdisZeroMemory(adapter,sizeof(TAP_ADAPTER_CONTEXT));

        adapter->MiniportAdapterHandle = MiniportAdapterHandle;

        // Initialize cancel-safe IRP queue
        tapIrpCsqInitialize(&adapter->PendingReadIrpQueue);

        // Initialize TAP send packet queue.
        tapPacketQueueInitialize(&adapter->SendPacketQueue);

        // Allocate the adapter lock.
        NdisAllocateSpinLock(&adapter->AdapterLock);

        // NBL pool for making TAP receive indications.
        NdisZeroMemory(&nblPoolParameters, sizeof(NET_BUFFER_LIST_POOL_PARAMETERS));

        // Initialize event used to determine when all receive NBLs have been returned.
        NdisInitializeEvent(&adapter->ReceiveNblInFlightCountZeroEvent);

        nblPoolParameters.Header.Type = NDIS_OBJECT_TYPE_DEFAULT;
        nblPoolParameters.Header.Revision = NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1;
        nblPoolParameters.Header.Size = NDIS_SIZEOF_NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1;
        nblPoolParameters.ProtocolId = NDIS_PROTOCOL_ID_DEFAULT;
        nblPoolParameters.ContextSize = 0;
        //nblPoolParameters.ContextSize = sizeof(RX_NETBUFLIST_RSVD);
        nblPoolParameters.fAllocateNetBuffer = TRUE;
        nblPoolParameters.PoolTag = TAP_RX_NBL_TAG;

#pragma warning( suppress : 28197 )
        adapter->ReceiveNblPool = NdisAllocateNetBufferListPool(
            adapter->MiniportAdapterHandle,
            &nblPoolParameters); 

        if (adapter->ReceiveNblPool == NULL)
        {
            DEBUGP (("[TAP] Couldn't allocate adapter receive NBL pool\n"));
            NdisFreeMemory(adapter,0,0);
        }

        // Add initial reference. Normally removed in AdapterHalt.
        adapter->RefCount = 1;

        // Safe for multiple removes.
        NdisInitializeListHead(&adapter->AdapterListLink);

        //
        // The miniport adapter is initially powered up
        //
        adapter->CurrentPowerState = NdisDeviceStateD0;
    }

    return adapter;
}

VOID
tapReadPermanentAddress(
    __in PTAP_ADAPTER_CONTEXT   Adapter,
    __in NDIS_HANDLE            ConfigurationHandle,
    __out MACADDR               PermanentAddress
    )
{
    NDIS_STATUS status;
    NDIS_CONFIGURATION_PARAMETER *configParameter;
    NDIS_STRING macKey = NDIS_STRING_CONST("MAC");
    ANSI_STRING macString;
    BOOLEAN macFromRegistry = FALSE;

    // Read MAC parameter from registry.
    NdisReadConfiguration(
        &status,
        &configParameter,
        ConfigurationHandle,
        &macKey,
        NdisParameterString
        );

    if (status == NDIS_STATUS_SUCCESS)
    {
        if( (configParameter->ParameterType == NdisParameterString)
            && (configParameter->ParameterData.StringData.Length >= 12)
            )
        {
            if (RtlUnicodeStringToAnsiString(
                    &macString,
                    &configParameter->ParameterData.StringData,
                    TRUE) == STATUS_SUCCESS
                    )
            {
                macFromRegistry = ParseMAC (PermanentAddress, macString.Buffer);
                RtlFreeAnsiString (&macString);
            }
        }
    }

    if(!macFromRegistry)
    {
        //
        // There is no (valid) address stashed in the registry parameter.
        //
        // Make up a dummy mac address based on the ANSI representation of the
        // NetCfgInstanceId GUID.
        //
        GenerateRandomMac(PermanentAddress, MINIPORT_INSTANCE_ID(Adapter));
    }
}

NDIS_STATUS
tapReadConfiguration(
    __in PTAP_ADAPTER_CONTEXT     Adapter
    )
{
    NDIS_STATUS                 status = NDIS_STATUS_SUCCESS;
    NDIS_CONFIGURATION_OBJECT   configObject;
    NDIS_HANDLE                 configHandle;

    DEBUGP (("[TAP] --> tapReadConfiguration\n"));

    //
    // Setup defaults in case configuration cannot be opened.
    //
    Adapter->MtuSize = ETHERNET_MTU;
    Adapter->MediaStateAlwaysConnected = FALSE;
    Adapter->LogicalMediaState = FALSE;
    Adapter->AllowNonAdmin = FALSE;
    //
    // Open the registry for this adapter to read advanced
    // configuration parameters stored by the INF file.
    //
    NdisZeroMemory(&configObject, sizeof(configObject));

    {C_ASSERT(sizeof(configObject) >= NDIS_SIZEOF_CONFIGURATION_OBJECT_REVISION_1);}
    configObject.Header.Type = NDIS_OBJECT_TYPE_CONFIGURATION_OBJECT;
    configObject.Header.Size = NDIS_SIZEOF_CONFIGURATION_OBJECT_REVISION_1;
    configObject.Header.Revision = NDIS_CONFIGURATION_OBJECT_REVISION_1;

    configObject.NdisHandle = Adapter->MiniportAdapterHandle;
    configObject.Flags = 0;

    status = NdisOpenConfigurationEx(
                &configObject,
                &configHandle
                );

    // Read on the opened configuration handle.
    if(status == NDIS_STATUS_SUCCESS)
    {
        NDIS_CONFIGURATION_PARAMETER *configParameter;
        NDIS_STRING mkey = NDIS_STRING_CONST("NetCfgInstanceId");

        //
        // Read NetCfgInstanceId from the registry.
        // ------------------------------------
        // NetCfgInstanceId is required to create device and associated
        // symbolic link for the adapter device.
        //
        // NetCfgInstanceId is  a GUID string provided by NDIS that identifies
        // the adapter instance. An example is:
        // 
        //    NetCfgInstanceId={410EB49D-2381-4FE7-9B36-498E22619DF0}
        //
        // Other names are derived from NetCfgInstanceId. For example, MiniportName:
        //
        //    MiniportName=\DEVICE\{410EB49D-2381-4FE7-9B36-498E22619DF0}
        //
        NdisReadConfiguration (
            &status,
            &configParameter,
            configHandle,
            &mkey,
            NdisParameterString
            );

        if (status == NDIS_STATUS_SUCCESS)
        {
            if (configParameter->ParameterType == NdisParameterString
                && configParameter->ParameterData.StringData.Length <= sizeof(Adapter->NetCfgInstanceIdBuffer) - sizeof(WCHAR))
            {
                DEBUGP (("[TAP] NdisReadConfiguration (NetCfgInstanceId=%wZ)\n",
                    &configParameter->ParameterData.StringData ));

                // Save NetCfgInstanceId as UNICODE_STRING.
                Adapter->NetCfgInstanceId.Length = Adapter->NetCfgInstanceId.MaximumLength
                    = configParameter->ParameterData.StringData.Length;

                Adapter->NetCfgInstanceId.Buffer = Adapter->NetCfgInstanceIdBuffer;

                NdisMoveMemory(
                    Adapter->NetCfgInstanceId.Buffer, 
                    configParameter->ParameterData.StringData.Buffer,
                    Adapter->NetCfgInstanceId.Length
                    );

                // Save NetCfgInstanceId as ANSI_STRING as well.
                if (RtlUnicodeStringToAnsiString (
                        &Adapter->NetCfgInstanceIdAnsi,
                        &configParameter->ParameterData.StringData,
                        TRUE) != STATUS_SUCCESS
                    )
                {
                    DEBUGP (("[TAP] NetCfgInstanceId ANSI name conversion failed\n"));
                    status = NDIS_STATUS_RESOURCES;
                }
            }
            else
            {
                DEBUGP (("[TAP] NetCfgInstanceId has invalid type\n"));
                status = NDIS_STATUS_INVALID_DATA;
            }
        }
        else
        {
            DEBUGP (("[TAP] NetCfgInstanceId failed\n"));
            status = NDIS_STATUS_INVALID_DATA;
        }

        if (status == NDIS_STATUS_SUCCESS)
        {
            NDIS_STATUS localStatus;    // Use default if these fail.
            NDIS_CONFIGURATION_PARAMETER *configParameter;
            NDIS_STRING mtuKey = NDIS_STRING_CONST("MTU");
            NDIS_STRING mediaStatusKey = NDIS_STRING_CONST("MediaStatus");
#if ENABLE_NONADMIN
            NDIS_STRING allowNonAdminKey = NDIS_STRING_CONST("AllowNonAdmin");
#endif

            // Read MTU from the registry.
            NdisReadConfiguration (
                &localStatus,
                &configParameter,
                configHandle,
                &mtuKey,
                NdisParameterInteger
                );

            if (localStatus == NDIS_STATUS_SUCCESS)
            {
                if (configParameter->ParameterType == NdisParameterInteger)
                {
                    int mtu = configParameter->ParameterData.IntegerData;

                    if(mtu == 0)
                    {
                        mtu = ETHERNET_MTU;
                    }

                    // Sanity check
                    if (mtu < MINIMUM_MTU)
                    {
                        mtu = MINIMUM_MTU;
                    }
                    else if (mtu > MAXIMUM_MTU)
                    {
                        mtu = MAXIMUM_MTU;
                    }

                    Adapter->MtuSize = mtu;
                }
            }

            DEBUGP (("[%s] Using MTU %d\n",
                MINIPORT_INSTANCE_ID (Adapter),
                Adapter->MtuSize
                ));

            // Read MediaStatus setting from registry.
            NdisReadConfiguration (
                &localStatus,
                &configParameter,
                configHandle,
                &mediaStatusKey,
                NdisParameterInteger
                );

            if (localStatus == NDIS_STATUS_SUCCESS)
            {
                if (configParameter->ParameterType == NdisParameterInteger)
                {
                    if(configParameter->ParameterData.IntegerData == 0)
                    {
                        // Connect state is appplication controlled.
                        DEBUGP(("[%s] Initial MediaConnectState: Application Controlled\n",
                            MINIPORT_INSTANCE_ID (Adapter)));

                        Adapter->MediaStateAlwaysConnected = FALSE;
                        Adapter->LogicalMediaState = FALSE;
                    }
                    else
                    {
                        // Connect state is always connected.
                        DEBUGP(("[%s] Initial MediaConnectState: Always Connected\n",
                            MINIPORT_INSTANCE_ID (Adapter)));

                        Adapter->MediaStateAlwaysConnected = TRUE;
                        Adapter->LogicalMediaState = TRUE;
                    }
                }
            }

            // Read MAC PermanentAddress setting from registry.
            tapReadPermanentAddress(
                Adapter,
                configHandle,
                Adapter->PermanentAddress
                );

            DEBUGP (("[%s] Using MAC PermanentAddress %2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x\n",
                MINIPORT_INSTANCE_ID (Adapter),
                Adapter->PermanentAddress[0],
                Adapter->PermanentAddress[1],
                Adapter->PermanentAddress[2],
                Adapter->PermanentAddress[3],
                Adapter->PermanentAddress[4],
                Adapter->PermanentAddress[5])
                );

            // Now seed the current MAC address with the permanent address.
            ETH_COPY_NETWORK_ADDRESS(Adapter->CurrentAddress, Adapter->PermanentAddress);

            DEBUGP (("[%s] Using MAC CurrentAddress %2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x\n",
                MINIPORT_INSTANCE_ID (Adapter),
                Adapter->CurrentAddress[0],
                Adapter->CurrentAddress[1],
                Adapter->CurrentAddress[2],
                Adapter->CurrentAddress[3],
                Adapter->CurrentAddress[4],
                Adapter->CurrentAddress[5])
                );

            // Read optional AllowNonAdmin setting from registry.
#if ENABLE_NONADMIN
            NdisReadConfiguration (
                &localStatus,
                &configParameter,
                configHandle,
                &allowNonAdminKey,
                NdisParameterInteger
                );

            if (localStatus == NDIS_STATUS_SUCCESS)
            {
                if (configParameter->ParameterType == NdisParameterInteger)
                {
                    Adapter->AllowNonAdmin = TRUE;
                }
            }
#endif
        }

        // Close the configuration handle.
        NdisCloseConfiguration(configHandle);
    }
    else
    {
        DEBUGP (("[TAP] Couldn't open adapter registry\n"));
    }

    DEBUGP (("[TAP] <-- tapReadConfiguration; status = %8.8X\n",status));

    return status;
}

VOID
tapAdapterContextAddToGlobalList(
    __in PTAP_ADAPTER_CONTEXT       Adapter
    )
{
    LOCK_STATE      lockState;
    PLIST_ENTRY     listEntry = &Adapter->AdapterListLink;

    // Acquire global adapter list lock.
    NdisAcquireReadWriteLock(
        &GlobalData.Lock,
        TRUE,      // Acquire for write
        &lockState
        );

    // Adapter context should NOT be in any list.
    ASSERT( (listEntry->Flink == listEntry) && (listEntry->Blink == listEntry ) );

    // Add reference to persist until after removal.
    tapAdapterContextReference(Adapter);

    // Add the adapter context to the global list.
    InsertTailList(&GlobalData.AdapterList,&Adapter->AdapterListLink);

    // Release global adapter list lock.
    NdisReleaseReadWriteLock(&GlobalData.Lock,&lockState);
}

VOID
tapAdapterContextRemoveFromGlobalList(
    __in PTAP_ADAPTER_CONTEXT       Adapter
    )
{
    LOCK_STATE              lockState;

    // Acquire global adapter list lock.
    NdisAcquireReadWriteLock(
        &GlobalData.Lock,
        TRUE,      // Acquire for write
        &lockState
        );

    // Remove the adapter context from the global list.
    RemoveEntryList(&Adapter->AdapterListLink);

    // Safe for multiple removes.
    NdisInitializeListHead(&Adapter->AdapterListLink);

    // Remove reference added in tapAdapterContextAddToGlobalList.
    tapAdapterContextDereference(Adapter);

    // Release global adapter list lock.
    NdisReleaseReadWriteLock(&GlobalData.Lock,&lockState);
}

// Returns with added reference on adapter context.
PTAP_ADAPTER_CONTEXT
tapAdapterContextFromDeviceObject(
    __in PDEVICE_OBJECT DeviceObject
    )
{
    LOCK_STATE              lockState;

    // Acquire global adapter list lock.
    NdisAcquireReadWriteLock(
        &GlobalData.Lock,
        FALSE,      // Acquire for read
        &lockState
        );

    if (!IsListEmpty(&GlobalData.AdapterList))
    {
        PLIST_ENTRY             entry = GlobalData.AdapterList.Flink;
        PTAP_ADAPTER_CONTEXT    adapter;

        while (entry != &GlobalData.AdapterList)
        {
            adapter = CONTAINING_RECORD(entry, TAP_ADAPTER_CONTEXT, AdapterListLink);

            // Match on DeviceObject
            if(adapter->DeviceObject == DeviceObject )
            {
                // Add reference to adapter context.
                tapAdapterContextReference(adapter);

                // Release global adapter list lock.
                NdisReleaseReadWriteLock(&GlobalData.Lock,&lockState);

                return adapter;
            }

            // Move to next entry
            entry = entry->Flink;
        }
    }

    // Release global adapter list lock.
    NdisReleaseReadWriteLock(&GlobalData.Lock,&lockState);

    return (PTAP_ADAPTER_CONTEXT )NULL;
}

NDIS_STATUS
AdapterSetOptions(
    __in  NDIS_HANDLE             NdisDriverHandle,
    __in  NDIS_HANDLE             DriverContext
    )
/*++
Routine Description:

    The MiniportSetOptions function registers optional handlers.  For each
    optional handler that should be registered, this function makes a call
    to NdisSetOptionalHandlers.

    MiniportSetOptions runs at IRQL = PASSIVE_LEVEL.

Arguments:

    DriverContext  The context handle

Return Value:

    NDIS_STATUS_xxx code

--*/
{
    NDIS_STATUS status;

    DEBUGP (("[TAP] --> AdapterSetOptions\n"));

    //
    // Set any optional handlers by filling out the appropriate struct and
    // calling NdisSetOptionalHandlers here.
    //

    status = NDIS_STATUS_SUCCESS;

    DEBUGP (("[TAP] <-- AdapterSetOptions; status = %8.8X\n",status));

    return status;
}

NDIS_STATUS
AdapterCreate(
    __in  NDIS_HANDLE                         MiniportAdapterHandle,
    __in  NDIS_HANDLE                         MiniportDriverContext,
    __in  PNDIS_MINIPORT_INIT_PARAMETERS      MiniportInitParameters
    )
{
    PTAP_ADAPTER_CONTEXT    adapter = NULL;
    NDIS_STATUS             status;

    UNREFERENCED_PARAMETER(MiniportDriverContext);
    UNREFERENCED_PARAMETER(MiniportInitParameters);

    DEBUGP (("[TAP] --> AdapterCreate\n"));

    do
    {
        NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES regAttributes = {0};
        NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES genAttributes = {0};
        NDIS_PNP_CAPABILITIES pnpCapabilities = {0};

        //
        // Allocate adapter context structure and initialize all the
        // memory resources for sending and receiving packets.
        //
        // Returns with reference count initialized to one.
        //
        adapter = tapAdapterContextAllocate(MiniportAdapterHandle);

        if(adapter == NULL)
        {
            DEBUGP (("[TAP] Couldn't allocate adapter memory\n"));
            status = NDIS_STATUS_RESOURCES;
            break;
        }

        // Enter the Initializing state.
        DEBUGP (("[TAP] Miniport State: Initializing\n"));

        tapAdapterAcquireLock(adapter,FALSE);
        adapter->Locked.AdapterState = MiniportInitializingState;
        tapAdapterReleaseLock(adapter,FALSE);

        //
        // First read adapter configuration from registry.
        // -----------------------------------------------
        // Subsequent device registration will fail if NetCfgInstanceId
        // has not been successfully read.
        //
        status = tapReadConfiguration(adapter);

        //
        // Set the registration attributes.
        //
        {C_ASSERT(sizeof(regAttributes) >= NDIS_SIZEOF_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_1);}
        regAttributes.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES;
        regAttributes.Header.Size = NDIS_SIZEOF_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_1;
        regAttributes.Header.Revision = NDIS_SIZEOF_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_1;

        regAttributes.MiniportAdapterContext = adapter;
        regAttributes.AttributeFlags = TAP_ADAPTER_ATTRIBUTES_FLAGS;

        regAttributes.CheckForHangTimeInSeconds = TAP_ADAPTER_CHECK_FOR_HANG_TIME_IN_SECONDS;
        regAttributes.InterfaceType = TAP_INTERFACE_TYPE;

        //NDIS_DECLARE_MINIPORT_ADAPTER_CONTEXT(TAP_ADAPTER_CONTEXT);
        status = NdisMSetMiniportAttributes(
                    MiniportAdapterHandle,
                    (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&regAttributes
                    );

        if (status != NDIS_STATUS_SUCCESS)
        {
            DEBUGP (("[TAP] NdisSetOptionalHandlers failed; Status 0x%08x\n",status));
            break;
        }

        //
        // Next, set the general attributes.
        //
        {C_ASSERT(sizeof(genAttributes) >= NDIS_SIZEOF_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES_REVISION_1);}
        genAttributes.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES;
        genAttributes.Header.Size = NDIS_SIZEOF_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES_REVISION_1;
        genAttributes.Header.Revision = NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES_REVISION_1;

        //
        // Specify the medium type that the NIC can support but not
        // necessarily the medium type that the NIC currently uses.
        //
        genAttributes.MediaType = TAP_MEDIUM_TYPE;

        //
        // Specifiy medium type that the NIC currently uses.
        //
        genAttributes.PhysicalMediumType = TAP_PHYSICAL_MEDIUM;

        //
        // Specifiy the maximum network frame size, in bytes, that the NIC
        // supports excluding the header.
        //
        genAttributes.MtuSize = TAP_FRAME_MAX_DATA_SIZE;
        genAttributes.MaxXmitLinkSpeed = TAP_XMIT_SPEED;
        genAttributes.XmitLinkSpeed = TAP_XMIT_SPEED;
        genAttributes.MaxRcvLinkSpeed = TAP_RECV_SPEED;
        genAttributes.RcvLinkSpeed = TAP_RECV_SPEED;

        if(adapter->MediaStateAlwaysConnected)
        {
            DEBUGP(("[%s] Initial MediaConnectState: Connected\n",
                MINIPORT_INSTANCE_ID (adapter)));

            genAttributes.MediaConnectState = MediaConnectStateConnected;
        }
        else
        {
            DEBUGP(("[%s] Initial MediaConnectState: Disconnected\n",
                MINIPORT_INSTANCE_ID (adapter)));

            genAttributes.MediaConnectState = MediaConnectStateDisconnected;
        }

        genAttributes.MediaDuplexState = MediaDuplexStateFull;

        //
        // The maximum number of bytes the NIC can provide as lookahead data.
        // If that value is different from the size of the lookahead buffer
        // supported by bound protocols, NDIS will call MiniportOidRequest to
        // set the size of the lookahead buffer provided by the miniport driver
        // to the minimum of the miniport driver and protocol(s) values. If the
        // driver always indicates up full packets with
        // NdisMIndicateReceiveNetBufferLists, it should set this value to the
        // maximum total frame size, which excludes the header.
        //
        // Upper-layer drivers examine lookahead data to determine whether a
        // packet that is associated with the lookahead data is intended for
        // one or more of their clients. If the underlying driver supports
        // multipacket receive indications, bound protocols are given full net
        // packets on every indication. Consequently, this value is identical
        // to that returned for OID_GEN_RECEIVE_BLOCK_SIZE.
        //
        genAttributes.LookaheadSize = TAP_MAX_LOOKAHEAD;
        genAttributes.MacOptions = TAP_MAC_OPTIONS;
        genAttributes.SupportedPacketFilters = TAP_SUPPORTED_FILTERS;

        //
        // The maximum number of multicast addresses the NIC driver can manage.
        // This list is global for all protocols bound to (or above) the NIC.
        // Consequently, a protocol can receive NDIS_STATUS_MULTICAST_FULL from
        // the NIC driver when attempting to set the multicast address list,
        // even if the number of elements in the given list is less than the
        // number originally returned for this query.
        //
        genAttributes.MaxMulticastListSize = TAP_MAX_MCAST_LIST;
        genAttributes.MacAddressLength = MACADDR_SIZE;

        //
        // Return the MAC address of the NIC burnt in the hardware.
        //
        ETH_COPY_NETWORK_ADDRESS(genAttributes.PermanentMacAddress, adapter->PermanentAddress);

        //
        // Return the MAC address the NIC is currently programmed to use. Note
        // that this address could be different from the permananent address as
        // the user can override using registry. Read NdisReadNetworkAddress
        // doc for more info.
        //
        ETH_COPY_NETWORK_ADDRESS(genAttributes.CurrentMacAddress, adapter->CurrentAddress);

        genAttributes.RecvScaleCapabilities = NULL;
        genAttributes.AccessType = TAP_ACCESS_TYPE;
        genAttributes.DirectionType = TAP_DIRECTION_TYPE;
        genAttributes.ConnectionType = TAP_CONNECTION_TYPE;
        genAttributes.IfType = TAP_IFTYPE;
        genAttributes.IfConnectorPresent = TAP_HAS_PHYSICAL_CONNECTOR;
        genAttributes.SupportedStatistics = TAP_SUPPORTED_STATISTICS;
        genAttributes.SupportedPauseFunctions = NdisPauseFunctionsUnsupported; // IEEE 802.3 pause frames 
        genAttributes.DataBackFillSize = 0;
        genAttributes.ContextBackFillSize = 0;

        //
        // The SupportedOidList is an array of OIDs for objects that the
        // underlying driver or its NIC supports.  Objects include general,
        // media-specific, and implementation-specific objects. NDIS forwards a
        // subset of the returned list to protocols that make this query. That
        // is, NDIS filters any supported statistics OIDs out of the list
        // because protocols never make statistics queries.
        //
        genAttributes.SupportedOidList = TAPSupportedOids;
        genAttributes.SupportedOidListLength = sizeof(TAPSupportedOids);
        genAttributes.AutoNegotiationFlags = NDIS_LINK_STATE_DUPLEX_AUTO_NEGOTIATED;

        //
        // Set power management capabilities
        //
        NdisZeroMemory(&pnpCapabilities, sizeof(pnpCapabilities));
        pnpCapabilities.WakeUpCapabilities.MinMagicPacketWakeUp = NdisDeviceStateUnspecified;
        pnpCapabilities.WakeUpCapabilities.MinPatternWakeUp = NdisDeviceStateUnspecified;
        genAttributes.PowerManagementCapabilities = &pnpCapabilities;

        status = NdisMSetMiniportAttributes(
                    MiniportAdapterHandle,
                    (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&genAttributes
                    );

        if (status != NDIS_STATUS_SUCCESS)
        {
            DEBUGP (("[TAP] NdisMSetMiniportAttributes failed; Status 0x%08x\n",status));
            break;
        }

        //
        // Create the Win32 device I/O interface.
        //
        status = CreateTapDevice(adapter);

        if (status == NDIS_STATUS_SUCCESS)
        {
            // Add this adapter to the global adapter list.
            tapAdapterContextAddToGlobalList(adapter);
        }
        else
        {
            DEBUGP (("[TAP] CreateTapDevice failed; Status 0x%08x\n",status));
            break;
        }
    } while(FALSE);

    if(status == NDIS_STATUS_SUCCESS)
    {
        // Enter the Paused state if initialization is complete.
        DEBUGP (("[TAP] Miniport State: Paused\n"));

        tapAdapterAcquireLock(adapter,FALSE);
        adapter->Locked.AdapterState = MiniportPausedState;
        tapAdapterReleaseLock(adapter,FALSE);
    }
    else
    {
        if(adapter != NULL)
        {
            DEBUGP (("[TAP] Miniport State: Halted\n"));

            //
            // Remove reference when adapter context was allocated
            // ---------------------------------------------------
            // This should result in freeing adapter context memory
            // and assiciated resources.
            //
            tapAdapterContextDereference(adapter);
            adapter = NULL;
        }
    }

    DEBUGP (("[TAP] <-- AdapterCreate; status = %8.8X\n",status));

    return status;
}

VOID
AdapterHalt(
    __in  NDIS_HANDLE             MiniportAdapterContext,
    __in  NDIS_HALT_ACTION        HaltAction
    )
/*++

Routine Description:

    Halt handler is called when NDIS receives IRP_MN_STOP_DEVICE,
    IRP_MN_SUPRISE_REMOVE or IRP_MN_REMOVE_DEVICE requests from the PNP
    manager. Here, the driver should free all the resources acquired in
    MiniportInitialize and stop access to the hardware. NDIS will not submit
    any further request once this handler is invoked.

    1) Free and unmap all I/O resources.
    2) Disable interrupt and deregister interrupt handler.
    3) Deregister shutdown handler regsitered by
        NdisMRegisterAdapterShutdownHandler .
    4) Cancel all queued up timer callbacks.
    5) Finally wait indefinitely for all the outstanding receive
        packets indicated to the protocol to return.

    MiniportHalt runs at IRQL = PASSIVE_LEVEL.


Arguments:

    MiniportAdapterContext  Pointer to the Adapter
    HaltAction  The reason for halting the adapter

Return Value:

    None.

--*/
{
    PTAP_ADAPTER_CONTEXT   adapter = (PTAP_ADAPTER_CONTEXT )MiniportAdapterContext;

    UNREFERENCED_PARAMETER(HaltAction);

    DEBUGP (("[TAP] --> AdapterHalt\n"));

    // Enter the Halted state.
    DEBUGP (("[TAP] Miniport State: Halted\n"));

    tapAdapterAcquireLock(adapter,FALSE);
    adapter->Locked.AdapterState = MiniportHaltedState;
    tapAdapterReleaseLock(adapter,FALSE);

    // Remove this adapter from the global adapter list.
    tapAdapterContextRemoveFromGlobalList(adapter);

    // BUGBUG!!! Call AdapterShutdownEx to do some of the work of stopping.

    // TODO!!! More...

    //
    // Destroy the TAP Win32 device.
    //
    DestroyTapDevice(adapter);

    //
    // Remove initial reference added in AdapterCreate.
    // ------------------------------------------------
    // This should result in freeing adapter context memory
    // and resources allocated in AdapterCreate.
    //
    tapAdapterContextDereference(adapter);
    adapter = NULL;

    DEBUGP (("[TAP] <-- AdapterHalt\n"));
}

VOID
tapWaitForReceiveNblInFlightCountZeroEvent(
    __in PTAP_ADAPTER_CONTEXT     Adapter
    )
{
    LONG    nblCount;

    //
    // Wait until higher-level protocol has returned all NBLs
    // to the driver.
    //

    // Add one NBL "bias" to insure allow event to be reset safely.
    nblCount = NdisInterlockedIncrement(&Adapter->ReceiveNblInFlightCount);
    ASSERT(nblCount > 0 );
    NdisResetEvent(&Adapter->ReceiveNblInFlightCountZeroEvent);

    //
    // Now remove the bias and wait for the ReceiveNblInFlightCountZeroEvent
    // if the count returned is not zero.
    //
    nblCount = NdisInterlockedDecrement(&Adapter->ReceiveNblInFlightCount);
    ASSERT(nblCount >= 0);

    if(nblCount)
    {
        LARGE_INTEGER   startTime, currentTime;

        NdisGetSystemUpTimeEx(&startTime);

        for (;;)
        {
            BOOLEAN waitResult = NdisWaitEvent(
                &Adapter->ReceiveNblInFlightCountZeroEvent, 
                TAP_WAIT_POLL_LOOP_TIMEOUT
                );

            NdisGetSystemUpTimeEx(&currentTime);

            if (waitResult)
            {
                break;
            }

            DEBUGP (("[%s] Waiting for %d in-flight receive NBLs to be returned.\n",
                MINIPORT_INSTANCE_ID (Adapter),
                Adapter->ReceiveNblInFlightCount
                ));
        }

        DEBUGP (("[%s] Waited %d ms for all in-flight NBLs to be returned.\n",
            MINIPORT_INSTANCE_ID (Adapter),
            (currentTime.LowPart - startTime.LowPart)
            ));
    }
}

NDIS_STATUS
AdapterPause(
    __in  NDIS_HANDLE                       MiniportAdapterContext,
    __in  PNDIS_MINIPORT_PAUSE_PARAMETERS   PauseParameters
    )
/*++

Routine Description:

    When a miniport receives a pause request, it enters into a Pausing state.
    The miniport should not indicate up any more network data.  Any pending
    send requests must be completed, and new requests must be rejected with
    NDIS_STATUS_PAUSED.

    Once all sends have been completed and all recieve NBLs have returned to
    the miniport, the miniport enters the Paused state.

    While paused, the miniport can still service interrupts from the hardware
    (to, for example, continue to indicate NDIS_STATUS_MEDIA_CONNECT
    notifications).

    The miniport must continue to be able to handle status indications and OID
    requests.  MiniportPause is different from MiniportHalt because, in
    general, the MiniportPause operation won't release any resources.
    MiniportPause must not attempt to acquire any resources where allocation
    can fail, since MiniportPause itself must not fail.


    MiniportPause runs at IRQL = PASSIVE_LEVEL.

Arguments:

    MiniportAdapterContext  Pointer to the Adapter
    MiniportPauseParameters  Additional information about the pause operation

Return Value:

    If the miniport is able to immediately enter the Paused state, it should
    return NDIS_STATUS_SUCCESS.

    If the miniport must wait for send completions or pending receive NBLs, it
    should return NDIS_STATUS_PENDING now, and call NDISMPauseComplete when the
    miniport has entered the Paused state.

    No other return value is permitted.  The pause operation must not fail.

--*/
{
    PTAP_ADAPTER_CONTEXT   adapter = (PTAP_ADAPTER_CONTEXT )MiniportAdapterContext;
    NDIS_STATUS    status;

    UNREFERENCED_PARAMETER(PauseParameters);

    DEBUGP (("[TAP] --> AdapterPause\n"));

    // Enter the Pausing state.
    DEBUGP (("[TAP] Miniport State: Pausing\n"));

    tapAdapterAcquireLock(adapter,FALSE);
    adapter->Locked.AdapterState = MiniportPausingState;
    tapAdapterReleaseLock(adapter,FALSE);

    //
    // Stop the flow of network data through the receive path
    // ------------------------------------------------------
    // In the Pausing and Paused state tapAdapterSendAndReceiveReady
    // will prevent new calls to NdisMIndicateReceiveNetBufferLists
    // to indicate additional receive NBLs to the host.
    //
    // However, there may be some in-flight NBLs owned by the driver
    // that have been indicated to the host but have not yet been
    // returned.
    //
    // Wait here for all in-flight receive indications to be returned.
    //
    tapWaitForReceiveNblInFlightCountZeroEvent(adapter);

    //
    // Stop the flow of network data through the send path
    // ---------------------------------------------------
    // The initial implementation of the NDIS 6 send path follows the
    // NDIS 5 pattern. Under this approach every send packet is copied
    // into a driver-owned TAP_PACKET structure and the NBL owned by
    // higher-level protocol is immediatly completed.
    //
    // With this deep-copy approach the driver never claims ownership
    // of any send NBL.
    //
    // A future implementation may queue send NBLs and thereby eliminate
    // the need for the unnecessary allocation and deep copy of each packet.
    //
    // So, nothing to do here for the send path for now...

    status = NDIS_STATUS_SUCCESS;

    // Enter the Paused state.
    DEBUGP (("[TAP] Miniport State: Paused\n"));

    tapAdapterAcquireLock(adapter,FALSE);
    adapter->Locked.AdapterState = MiniportPausedState;
    tapAdapterReleaseLock(adapter,FALSE);

    DEBUGP (("[TAP] <-- AdapterPause; status = %8.8X\n",status));

    return status;
}

NDIS_STATUS
AdapterRestart(
    __in  NDIS_HANDLE                             MiniportAdapterContext,
    __in  PNDIS_MINIPORT_RESTART_PARAMETERS       RestartParameters
    )
/*++

Routine Description:

    When a miniport receives a restart request, it enters into a Restarting
    state.  The miniport may begin indicating received data (e.g., using
    NdisMIndicateReceiveNetBufferLists), handling status indications, and
    processing OID requests in the Restarting state.  However, no sends will be
    requested while the miniport is in the Restarting state.

    Once the miniport is ready to send data, it has entered the Running state.
    The miniport informs NDIS that it is in the Running state by returning
    NDIS_STATUS_SUCCESS from this MiniportRestart function; or if this function
    has already returned NDIS_STATUS_PENDING, by calling NdisMRestartComplete.


    MiniportRestart runs at IRQL = PASSIVE_LEVEL.

Arguments:

    MiniportAdapterContext  Pointer to the Adapter
    RestartParameters  Additional information about the restart operation

Return Value:

    If the miniport is able to immediately enter the Running state, it should
    return NDIS_STATUS_SUCCESS.

    If the miniport is still in the Restarting state, it should return
    NDIS_STATUS_PENDING now, and call NdisMRestartComplete when the miniport
    has entered the Running state.

    Other NDIS_STATUS codes indicate errors.  If an error is encountered, the
    miniport must return to the Paused state (i.e., stop indicating receives).

--*/
{
    PTAP_ADAPTER_CONTEXT   adapter = (PTAP_ADAPTER_CONTEXT )MiniportAdapterContext;
    NDIS_STATUS    status;

    UNREFERENCED_PARAMETER(RestartParameters);

    DEBUGP (("[TAP] --> AdapterRestart\n"));

    // Enter the Restarting state.
    DEBUGP (("[TAP] Miniport State: Restarting\n"));

    tapAdapterAcquireLock(adapter,FALSE);
    adapter->Locked.AdapterState = MiniportRestartingState;
    tapAdapterReleaseLock(adapter,FALSE);

    status = NDIS_STATUS_SUCCESS;

    if(status == NDIS_STATUS_SUCCESS)
    {
        // Enter the Running state.
        DEBUGP (("[TAP] Miniport State: Running\n"));

        tapAdapterAcquireLock(adapter,FALSE);
        adapter->Locked.AdapterState = MiniportRunning;
        tapAdapterReleaseLock(adapter,FALSE);
    }
    else
    {
        // Enter the Paused state if restart failed.
        DEBUGP (("[TAP] Miniport State: Paused\n"));

        tapAdapterAcquireLock(adapter,FALSE);
        adapter->Locked.AdapterState = MiniportPausedState;
        tapAdapterReleaseLock(adapter,FALSE);
    }

    DEBUGP (("[TAP] <-- AdapterRestart; status = %8.8X\n",status));

    return status;
}

BOOLEAN
tapAdapterReadAndWriteReady(
    __in PTAP_ADAPTER_CONTEXT     Adapter
    )
/*++

Routine Description:

    This routine determines whether the adapter device interface can
    accept read and write operations.

Arguments:

    Adapter              Pointer to our adapter context

Return Value:

    Returns TRUE if the adapter state allows it to queue IRPs passed to
    the device read and write callbacks.
--*/
{
    if(!Adapter->TapDeviceCreated)
    {
        // TAP device not created or is being destroyed.
        return FALSE;
    }

    if(Adapter->TapFileObject == NULL)
    {
        // TAP application file object not open.
        return FALSE;
    }

    if(!Adapter->TapFileIsOpen)
    {
        // TAP application file object may be closing.
        return FALSE;
    }

    if(!Adapter->LogicalMediaState)
    {
        // Don't handle read/write if media not connected.
        return FALSE;
    }

    if(Adapter->CurrentPowerState != NdisDeviceStateD0)
    {
        // Don't handle read/write if device is not fully powered.
        return FALSE;
    }

    return TRUE;
}

NDIS_STATUS
tapAdapterSendAndReceiveReady(
    __in PTAP_ADAPTER_CONTEXT     Adapter
    )
/*++

Routine Description:

    This routine determines whether the adapter NDIS send and receive
    paths are ready.

    This routine examines various adapter state variables and returns
    a value that indicates whether the adapter NDIS interfaces can
    accept send packets or indicate receive packets.

    In normal operation the adapter may temporarily enter and then exit
    a not-ready condition. In particular, the adapter becomes not-ready
    when in the Pausing/Paused states, but may become ready again when
    Restarted.

    Runs at IRQL <= DISPATCH_LEVEL

Arguments:

    Adapter              Pointer to our adapter context

Return Value:

    Returns NDIS_STATUS_SUCCESS if the adapter state allows it to
    accept send packets and indicate receive packets.

    Otherwise it returns a NDIS_STATUS value other than NDIS_STATUS_SUCCESS.
    These status values can be used directly as the completion status for
    packets that must be completed immediatly in the send path.
--*/
{
    NDIS_STATUS status = NDIS_STATUS_SUCCESS;

    //
    // Check various state variables to insure adapter is ready.
    //
    tapAdapterAcquireLock(Adapter,FALSE);

    if(!Adapter->LogicalMediaState)
    {
        status = NDIS_STATUS_MEDIA_DISCONNECTED;
    }
    else if(Adapter->CurrentPowerState != NdisDeviceStateD0)
    {
        status = NDIS_STATUS_LOW_POWER_STATE;
    }
    else if(Adapter->ResetInProgress)
    {
        status = NDIS_STATUS_RESET_IN_PROGRESS;
    }
    else
    {
        switch(Adapter->Locked.AdapterState)
        {
        case MiniportPausingState:
        case MiniportPausedState:
            status = NDIS_STATUS_PAUSED;
            break;

        case MiniportHaltedState:
            status = NDIS_STATUS_INVALID_STATE;
            break;

        default:
            status = NDIS_STATUS_SUCCESS;
            break;
        }
    }

    tapAdapterReleaseLock(Adapter,FALSE);

    return status;
}

BOOLEAN
AdapterCheckForHangEx(
    __in  NDIS_HANDLE MiniportAdapterContext
    )
/*++

Routine Description:

    The MiniportCheckForHangEx handler is called to report the state of the
    NIC, or to monitor the responsiveness of an underlying device driver.
    This is an optional function. If this handler is not specified, NDIS
    judges the driver unresponsive when the driver holds
    MiniportQueryInformation or MiniportSetInformation requests for a
    time-out interval (deafult 4 sec), and then calls the driver's
    MiniportReset function. A NIC driver's MiniportInitialize function can
    extend NDIS's time-out interval by calling NdisMSetAttributesEx to
    avoid unnecessary resets.

    MiniportCheckForHangEx runs at IRQL <= DISPATCH_LEVEL.

Arguments:

    MiniportAdapterContext  Pointer to our adapter

Return Value:

    TRUE    NDIS calls the driver's MiniportReset function.
    FALSE   Everything is fine

--*/
{
    PTAP_ADAPTER_CONTEXT   adapter = (PTAP_ADAPTER_CONTEXT )MiniportAdapterContext;

    //DEBUGP (("[TAP] --> AdapterCheckForHangEx\n"));

    //DEBUGP (("[TAP] <-- AdapterCheckForHangEx; status = FALSE\n"));

    return FALSE;   // Everything is fine
}

NDIS_STATUS
AdapterReset(
    __in   NDIS_HANDLE            MiniportAdapterContext,
    __out PBOOLEAN                AddressingReset
    )
/*++

Routine Description:

    MiniportResetEx is a required to issue a hardware reset to the NIC
    and/or to reset the driver's software state.

    1) The miniport driver can optionally complete any pending
        OID requests. NDIS will submit no further OID requests
        to the miniport driver for the NIC being reset until
        the reset operation has finished. After the reset,
        NDIS will resubmit to the miniport driver any OID requests
        that were pending but not completed by the miniport driver
        before the reset.

    2) A deserialized miniport driver must complete any pending send
        operations. NDIS will not requeue pending send packets for
        a deserialized driver since NDIS does not maintain the send
        queue for such a driver.

    3) If MiniportReset returns NDIS_STATUS_PENDING, the driver must
        complete the original request subsequently with a call to
        NdisMResetComplete.

    MiniportReset runs at IRQL <= DISPATCH_LEVEL.

Arguments:

AddressingReset - If multicast or functional addressing information
                  or the lookahead size, is changed by a reset,
                  MiniportReset must set the variable at AddressingReset
                  to TRUE before it returns control. This causes NDIS to
                  call the MiniportSetInformation function to restore
                  the information.

MiniportAdapterContext - Pointer to our adapter

Return Value:

    NDIS_STATUS

--*/
{
    PTAP_ADAPTER_CONTEXT   adapter = (PTAP_ADAPTER_CONTEXT )MiniportAdapterContext;
    NDIS_STATUS    status;

    UNREFERENCED_PARAMETER(MiniportAdapterContext);
    UNREFERENCED_PARAMETER(AddressingReset);

    DEBUGP (("[TAP] --> AdapterReset\n"));

    // Indicate that adapter reset is in progress.
    adapter->ResetInProgress = TRUE;

    // See note above...
    *AddressingReset = FALSE;

    // BUGBUG!!! TODO!!! Lots of work here...

    // Indicate that adapter reset has completed.
    adapter->ResetInProgress = FALSE;

    status = NDIS_STATUS_SUCCESS;

    DEBUGP (("[TAP] <-- AdapterReset; status = %8.8X\n",status));

    return status;
}

VOID
AdapterDevicePnpEventNotify(
    __in  NDIS_HANDLE             MiniportAdapterContext,
    __in  PNET_DEVICE_PNP_EVENT   NetDevicePnPEvent
    )
{
    PTAP_ADAPTER_CONTEXT   adapter = (PTAP_ADAPTER_CONTEXT )MiniportAdapterContext;

    DEBUGP (("[TAP] --> AdapterDevicePnpEventNotify\n"));

/*
    switch (NetDevicePnPEvent->DevicePnPEvent)
    {
        case NdisDevicePnPEventSurpriseRemoved:
            //
            // Called when NDIS receives IRP_MN_SUPRISE_REMOVAL.
            // NDIS calls MiniportHalt function after this call returns.
            //
            MP_SET_FLAG(Adapter, fMP_ADAPTER_SURPRISE_REMOVED);
            DEBUGP(MP_INFO, "[%p] MPDevicePnpEventNotify: NdisDevicePnPEventSurpriseRemoved\n", Adapter);
            break;

        case NdisDevicePnPEventPowerProfileChanged:
            //
            // After initializing a miniport driver and after miniport driver
            // receives an OID_PNP_SET_POWER notification that specifies
            // a device power state of NdisDeviceStateD0 (the powered-on state),
            // NDIS calls the miniport's MiniportPnPEventNotify function with
            // PnPEvent set to NdisDevicePnPEventPowerProfileChanged.
            //
            DEBUGP(MP_INFO, "[%p] MPDevicePnpEventNotify: NdisDevicePnPEventPowerProfileChanged\n", Adapter);

            if (NetDevicePnPEvent->InformationBufferLength == sizeof(ULONG))
            {
                ULONG NdisPowerProfile = *((PULONG)NetDevicePnPEvent->InformationBuffer);

                if (NdisPowerProfile == NdisPowerProfileBattery)
                {
                    DEBUGP(MP_INFO, "[%p] The host system is running on battery power\n", Adapter);
                }
                if (NdisPowerProfile == NdisPowerProfileAcOnLine)
                {
                    DEBUGP(MP_INFO, "[%p] The host system is running on AC power\n", Adapter);
                }
            }
            break;

        default:
            DEBUGP(MP_ERROR, "[%p] MPDevicePnpEventNotify: unknown PnP event 0x%x\n", Adapter, NetDevicePnPEvent->DevicePnPEvent);
    }
*/
    DEBUGP (("[TAP] <-- AdapterDevicePnpEventNotify\n"));
}

VOID
AdapterShutdownEx(
    __in  NDIS_HANDLE             MiniportAdapterContext,
    __in  NDIS_SHUTDOWN_ACTION    ShutdownAction
    )
/*++

Routine Description:

    The MiniportShutdownEx handler restores hardware to its initial state when
    the system is shut down, whether by the user or because an unrecoverable
    system error occurred. This is to ensure that the NIC is in a known
    state and ready to be reinitialized when the machine is rebooted after
    a system shutdown occurs for any reason, including a crash dump.

    Here just disable the interrupt and stop the DMA engine.  Do not free
    memory resources or wait for any packet transfers to complete.  Do not call
    into NDIS at this time.

    This can be called at aribitrary IRQL, including in the context of a
    bugcheck.

Arguments:

    MiniportAdapterContext  Pointer to our adapter
    ShutdownAction  The reason why NDIS called the shutdown function

Return Value:

    None.

--*/
{
    PTAP_ADAPTER_CONTEXT   adapter = (PTAP_ADAPTER_CONTEXT )MiniportAdapterContext;

    UNREFERENCED_PARAMETER(ShutdownAction);
    UNREFERENCED_PARAMETER(MiniportAdapterContext);

    DEBUGP (("[TAP] --> AdapterShutdownEx\n"));

    // Enter the Shutdown state.
    DEBUGP (("[TAP] Miniport State: Shutdown\n"));

    tapAdapterAcquireLock(adapter,FALSE);
    adapter->Locked.AdapterState = MiniportShutdownState;
    tapAdapterReleaseLock(adapter,FALSE);

    //
    // BUGBUG!!! FlushIrpQueues???
    //

    DEBUGP (("[TAP] <-- AdapterShutdownEx\n"));
}


// Free adapter context memory and associated resources.
VOID
tapAdapterContextFree(
    __in PTAP_ADAPTER_CONTEXT     Adapter
    )
{
    PLIST_ENTRY listEntry = &Adapter->AdapterListLink;

    DEBUGP (("[TAP] --> tapAdapterContextFree\n"));

    // Adapter context should already be removed.
    ASSERT( (listEntry->Flink == listEntry) && (listEntry->Blink == listEntry ) );

    // Insure that adapter context has been removed from global adapter list.
    RemoveEntryList(&Adapter->AdapterListLink);

    // Free the adapter lock.
    NdisFreeSpinLock(&Adapter->AdapterLock);

    // Free the ANSI NetCfgInstanceId buffer.
    if(Adapter->NetCfgInstanceIdAnsi.Buffer != NULL)
    {
        RtlFreeAnsiString(&Adapter->NetCfgInstanceIdAnsi);
    }

    Adapter->NetCfgInstanceIdAnsi.Buffer = NULL;

    // Free the receive NBL pool.
    if(Adapter->ReceiveNblPool != NULL )
    {
        NdisFreeNetBufferListPool(Adapter->ReceiveNblPool);
    }

    Adapter->ReceiveNblPool = NULL;

    NdisFreeMemory(Adapter,0,0);

    DEBUGP (("[TAP] <-- tapAdapterContextFree\n"));
}
ULONG
tapGetNetBufferFrameType(
    __in PNET_BUFFER       NetBuffer
    )
/*++

Routine Description:

    Reads the network frame's destination address to determine the type
    (broadcast, multicast, etc)

    Runs at IRQL <= DISPATCH_LEVEL.

Arguments:

    NetBuffer                 The NB to examine

Return Value:

    NDIS_PACKET_TYPE_BROADCAST
    NDIS_PACKET_TYPE_MULTICAST
    NDIS_PACKET_TYPE_DIRECTED

--*/
{
    PETH_HEADER ethernetHeader;

    ethernetHeader = (PETH_HEADER )NdisGetDataBuffer(
                        NetBuffer,
                        sizeof(ETH_HEADER),
                        NULL,
                        1,
                        0
                        );

    ASSERT(ethernetHeader);

    if (ETH_IS_BROADCAST(ethernetHeader->dest))
    {
        return NDIS_PACKET_TYPE_BROADCAST;
    }
    else if(ETH_IS_MULTICAST(ethernetHeader->dest))
    {
        return NDIS_PACKET_TYPE_MULTICAST;
    }
    else
    {
        return NDIS_PACKET_TYPE_DIRECTED;
    }

}

ULONG
tapGetNetBufferCountsFromNetBufferList(
    __in PNET_BUFFER_LIST   NetBufferList,
    __inout_opt PULONG      TotalByteCount      // Of all linked NBs
    )
/*++

Routine Description:

    Returns the number of net buffers linked to the net buffer list.

    Optionally retuens the total byte count of all net buffers linked
    to the net buffer list

    Runs at IRQL <= DISPATCH_LEVEL.

Arguments:

    NetBufferList                 The NBL to examine

Return Value:

    The number of net buffers linked to the net buffer list.

--*/
{
    ULONG       netBufferCount = 0;
    PNET_BUFFER currentNb;

    if(TotalByteCount)
    {
        *TotalByteCount = 0;
    }

    currentNb = NET_BUFFER_LIST_FIRST_NB(NetBufferList);

    while(currentNb)
    {
        ++netBufferCount;

        if(TotalByteCount)
        {
            *TotalByteCount += NET_BUFFER_DATA_LENGTH(currentNb);
        }

        // Move to next NB
        currentNb = NET_BUFFER_NEXT_NB(currentNb);
    }

    return netBufferCount;
}

VOID
tapAdapterAcquireLock(
    __in    PTAP_ADAPTER_CONTEXT    Adapter,
    __in    BOOLEAN                 DispatchLevel
    )
{
    ASSERT(!DispatchLevel || (DISPATCH_LEVEL == KeGetCurrentIrql()));
   
    if (DispatchLevel)
    {
        NdisDprAcquireSpinLock(&Adapter->AdapterLock);
    }
    else
    {
        NdisAcquireSpinLock(&Adapter->AdapterLock);
    }
}

VOID
tapAdapterReleaseLock(
    __in    PTAP_ADAPTER_CONTEXT    Adapter,
    __in    BOOLEAN                 DispatchLevel
    )
{
    ASSERT(!DispatchLevel || (DISPATCH_LEVEL == KeGetCurrentIrql()));
   
    if (DispatchLevel)
    {
        NdisDprReleaseSpinLock(&Adapter->AdapterLock);
    }
    else
    {
        NdisReleaseSpinLock(&Adapter->AdapterLock);
    }
}