/*++

Copyright (c) 1999, Microsoft Corporation

Module Name:

    elport.c

Abstract:
   
    This module deals with the port management for EAPOL, r/w to ports


Revision History:

    sachins, Apr 28 2000, Created

--*/

#include "pcheapol.h"
#pragma hdrstop
#include "intfhdl.h"


BYTE g_bDefaultGroupMacAddr[]={0x01, 0x80, 0xc2, 0x00, 0x00, 0x03};
BYTE g_bEtherType8021X[SIZE_ETHERNET_TYPE]={0x88, 0x8E};
BYTE DEFAULT_8021X_VERSION=0x01;


//
// ElReadPerPortRegistryParams
//
// Description:
//
// Function called to read per port interface parameters from the registry
//
// Arguments:
//      pwszDeviceGUID - GUID-string for the port
//      pPCB - Pointer to PCB for the port
//
// Return values:
//      NO_ERROR - success 
//      NON-zero - error
//

DWORD
ElReadPerPortRegistryParams (
        IN  WCHAR       *pwszDeviceGUID,
        IN  EAPOL_PCB   *pPCB
        )
{
    EAPOL_INTF_PARAMS   EapolIntfParams;
    EAPOL_POLICY_PARAMS EAPOLPolicyParams = {0};
    DWORD               dwSizeOfAuthData = 0;
    BYTE                *pbAuthData = NULL;
    DWORD               dwRetCode = NO_ERROR;

    do
    {

    // Set the Auth Mode and the Supplicant mode for the context

    pPCB->dwEAPOLAuthMode = g_dwEAPOLAuthMode;
    pPCB->dwSupplicantMode = g_dwSupplicantMode;

    // Read EAP type and default EAPOL state

    ZeroMemory ((BYTE *)&EapolIntfParams, sizeof(EAPOL_INTF_PARAMS));
    EapolIntfParams.dwVersion = EAPOL_CURRENT_VERSION;
    EapolIntfParams.dwEapFlags = DEFAULT_EAP_STATE;
    EapolIntfParams.dwEapType = DEFAULT_EAP_TYPE;
    if (pPCB->pSSID != NULL)
    {
        memcpy (EapolIntfParams.bSSID, pPCB->pSSID->Ssid, pPCB->pSSID->SsidLength);
        EapolIntfParams.dwSizeOfSSID = pPCB->pSSID->SsidLength;
    }
    if ((dwRetCode = ElGetInterfaceParams (
                            pwszDeviceGUID,
                            &EapolIntfParams
                            )) != NO_ERROR)
    {
        TRACE1 (PORT, "ElReadPerPortRegistryParams: ElGetInterfaceParams failed with error %ld",
                dwRetCode);

        if (dwRetCode == ERROR_FILE_NOT_FOUND)
        {
            dwRetCode = NO_ERROR;
        }
        else
        {
            break;
        }
    }

    // Do version check here
    // If registry blob has a version not equal to latest version,
    // modify parameters to reflect default settings for current version

    if ((EapolIntfParams.dwVersion != EAPOL_CURRENT_VERSION) &&
            (EapolIntfParams.dwEapType == EAP_TYPE_TLS))
    {
        EapolIntfParams.dwVersion = EAPOL_CURRENT_VERSION;
        EapolIntfParams.dwEapFlags |= DEFAULT_MACHINE_AUTH_STATE;
        EapolIntfParams.dwEapFlags &= ~EAPOL_GUEST_AUTH_ENABLED;
        if ((dwRetCode = ElSetInterfaceParams (
                                pwszDeviceGUID,
                                &EapolIntfParams
                                )) != NO_ERROR)
        {
            TRACE1 (PORT, "ElReadPerPortRegistryParams: ElSetInterfaceParams failed with error %ld, continuing",
                dwRetCode);
            dwRetCode = NO_ERROR;
        }
    }

    if ((pPCB->PhysicalMediumType == NdisPhysicalMediumWirelessLan) &&
            (EapolIntfParams.dwEapType == EAP_TYPE_MD5))
    {
        EapolIntfParams.dwEapType = EAP_TYPE_TLS;
        if ((dwRetCode = ElSetInterfaceParams (
                                pwszDeviceGUID,
                                &EapolIntfParams
                                )) != NO_ERROR)
        {
            TRACE1 (PORT, "ElReadPerPortRegistryParams: ElSetInterfaceParams for TLS failed with error %ld, continuing",
                dwRetCode);
            dwRetCode = NO_ERROR;
        }
    }

    pPCB->dwEapFlags = EapolIntfParams.dwEapFlags;
    pPCB->dwEapTypeToBeUsed = EapolIntfParams.dwEapType;

    // 
    // Query with zero-config and see if it is enabled on the interface
    // or not. If zero-config is disabled on the interface, 802.1x should
    // also be disabled 
    //

    {
        DWORD           dwErr = 0;
        INTF_ENTRY      ZCIntfEntry = {0};
        ZCIntfEntry.wszGuid = pwszDeviceGUID;
        if ((dwErr = LstQueryInterface (
                                INTF_ENABLED,
                                &ZCIntfEntry,
                                NULL
                                )) == NO_ERROR)
        {
                if (!(ZCIntfEntry.dwCtlFlags & INTFCTL_ENABLED))
                {
                        // TRACE0 (ANY, "LstQueryInterface returned Zero-configuration is disabled on network");
                        pPCB->dwEapFlags &= ~EAPOL_ENABLED;
                }
                else
                {
                        // TRACE0 (ANY, "LstQueryInterface returned Zero-configuration is enabled on network");
                }
        }
        else
        {
                if (dwErr != ERROR_FILE_NOT_FOUND)
                {
                    TRACE1 (ANY, "LstQueryInterface failed with error (%ld)",
                                    dwErr);
                }
        }
    }

    // Get the size of the EAP blob
    if ((dwRetCode = ElGetCustomAuthData (
                    pwszDeviceGUID,
                    pPCB->dwEapTypeToBeUsed,
                    (pPCB->pSSID)?pPCB->pSSID->SsidLength:0,
                    (pPCB->pSSID)?pPCB->pSSID->Ssid:NULL,
                    NULL,
                    &dwSizeOfAuthData
                    )) != NO_ERROR)
    {
        if (dwRetCode == ERROR_BUFFER_TOO_SMALL)
        {
            if (dwSizeOfAuthData <= 0)
            {
                // No EAP blob stored in the registry
                // Port can have NULL EAP blob
                pbAuthData = NULL;
                dwRetCode = NO_ERROR;
            }
            else
            {
                // Allocate memory to hold the blob
                pbAuthData = MALLOC (dwSizeOfAuthData);
                if (pbAuthData == NULL)
                {
                    dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
                    TRACE0 (USER, "ElReadPerPortRegistryParams: Error in memory allocation for EAP blob");
                    break;
                }
                if ((dwRetCode = ElGetCustomAuthData (
                                    pwszDeviceGUID,
                                    pPCB->dwEapTypeToBeUsed,
                                    (pPCB->pSSID)?pPCB->pSSID->SsidLength:0,
                                    (pPCB->pSSID)?pPCB->pSSID->Ssid:NULL,
                                    pbAuthData,
                                    &dwSizeOfAuthData
                                    )) != NO_ERROR)
                {
                    TRACE1 (USER, "ElReadPerPortRegistryParams: ElGetCustomAuthData failed with %ld",
                            dwRetCode);
                    break;
                }
            }
        
            if (pPCB->pCustomAuthConnData != NULL)
            {
                FREE (pPCB->pCustomAuthConnData);
                pPCB->pCustomAuthConnData = NULL;
            }
        
            pPCB->pCustomAuthConnData = MALLOC (dwSizeOfAuthData + sizeof (DWORD));
            if (pPCB->pCustomAuthConnData == NULL)
            {
                dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
                TRACE0 (EAPOL, "ElReadPerPortRegistryParams: MALLOC failed for pCustomAuthConnData");
                break;
            }
    
            pPCB->pCustomAuthConnData->dwSizeOfCustomAuthData = dwSizeOfAuthData;
            if ((dwSizeOfAuthData != 0) && (pbAuthData != NULL))
            {
                memcpy ((BYTE *)pPCB->pCustomAuthConnData->pbCustomAuthData, 
                    (BYTE *)pbAuthData, dwSizeOfAuthData);
            }
        }
        else
        {
            TRACE1 (USER, "ElReadPerPortRegistryParams: ElGetCustomAuthData size estimation failed with error %ld",
                    dwRetCode);
            break;
        }
    }

    // Initialize Policy parameters not in EAPOL_INTF_PARAMS
    if ((dwRetCode = ElGetPolicyInterfaceParams (
                    EapolIntfParams.dwSizeOfSSID,
                    EapolIntfParams.bSSID,
                    &EAPOLPolicyParams
                    )) == NO_ERROR)
    {
        pPCB->dwEAPOLAuthMode = EAPOLPolicyParams.dwEAPOLAuthMode;
        pPCB->dwSupplicantMode = EAPOLPolicyParams.dwSupplicantMode;
        pPCB->EapolConfig.dwheldPeriod = EAPOLPolicyParams.dwheldPeriod;
        pPCB->EapolConfig.dwauthPeriod = EAPOLPolicyParams.dwauthPeriod;
        pPCB->EapolConfig.dwstartPeriod = EAPOLPolicyParams.dwstartPeriod;
        pPCB->EapolConfig.dwmaxStart = EAPOLPolicyParams.dwmaxStart;
    }
    else
    {
        if (dwRetCode != ERROR_FILE_NOT_FOUND)
        {
            TRACE1 (USER, "ElReadPerPortRegistryParams: ElGetPolicyInterfaceParams failed with error (%ld)",
                dwRetCode);
        }
        dwRetCode = NO_ERROR;
    }

    // Determine maximum fail count possible before being parked into failure
    // state (DISCONNECTED)
    switch (pPCB->dwEAPOLAuthMode)
    {
        case EAPOL_AUTH_MODE_0:
        case EAPOL_AUTH_MODE_1:
            if (g_fUserLoggedOn)
            {
                // When user is logged in, only user and guest will be tried
                pPCB->dwTotalMaxAuthFailCount = EAPOL_MAX_AUTH_FAIL_COUNT;
                pPCB->dwTotalMaxAuthFailCount += ((IS_GUEST_AUTH_ENABLED(pPCB->dwEapFlags))?1:0)*EAPOL_MAX_AUTH_FAIL_COUNT;
            }
            else
            {
                // When user is logged out, only machine and guest will be tried
                pPCB->dwTotalMaxAuthFailCount = ((IS_GUEST_AUTH_ENABLED(pPCB->dwEapFlags))?1:0)*EAPOL_MAX_AUTH_FAIL_COUNT;
                pPCB->dwTotalMaxAuthFailCount += ((IS_MACHINE_AUTH_ENABLED(pPCB->dwEapFlags))?1:0)*EAPOL_MAX_AUTH_FAIL_COUNT;
            }
            break;
        case EAPOL_AUTH_MODE_2:
            // In Mode 2, only machine and guest will be tried
            pPCB->dwTotalMaxAuthFailCount = ((IS_GUEST_AUTH_ENABLED(pPCB->dwEapFlags))?1:0)*EAPOL_MAX_AUTH_FAIL_COUNT;
            pPCB->dwTotalMaxAuthFailCount += ((IS_MACHINE_AUTH_ENABLED(pPCB->dwEapFlags))?1:0)*EAPOL_MAX_AUTH_FAIL_COUNT;
            break;
    }

    TRACE1 (PORT, "ElReadPerPortRegistryParams: dwTotalMaxAuthFailCount = (%ld)",
            pPCB->dwTotalMaxAuthFailCount);

    memcpy(pPCB->bEtherType, &g_bEtherType8021X[0], SIZE_ETHERNET_TYPE);

    pPCB->bProtocolVersion = DEFAULT_8021X_VERSION;

    }
    while (FALSE);

    if (pbAuthData != NULL)
    {
        FREE (pbAuthData);
    }

    return dwRetCode;
}


//
// ElHashPortToBucket
//
// Description:
//
// Function called to convert Device GUID into PCB hash table index.
//
// Arguments:
//      pwszDeviceGUID - GUID-string for the port
//
// Return values:
//      PCB hash table index from 0 to PORT_TABLE_BUCKETS-1
//

DWORD
ElHashPortToBucket (
        IN WCHAR    *pwszDeviceGUID
        )
{
    return ((DWORD)((_wtol(pwszDeviceGUID)) % PORT_TABLE_BUCKETS)); 
}


//
// ElRemovePCBFromTable
//
// Description:
//
// Function called to remove a PCB from the Hash Bucket table
// Delink it from the hash table, but do not free up the memory
//
// Arguments:
//      pPCB - Pointer to PCB entry to be removed
//
//  Return values:
//

VOID
ElRemovePCBFromTable (
        IN EAPOL_PCB *pPCB
        )
{
    DWORD       dwIndex;
    EAPOL_PCB   *pPCBWalker = NULL;
    EAPOL_PCB   *pPCBTemp = NULL;

    if (pPCB == NULL)
    {
        TRACE0 (PORT, "ElRemovePCBFromTable: Deleting NULL PCB, returning");
        return;
    }

    dwIndex = ElHashPortToBucket (pPCB->pwszDeviceGUID);
    pPCBWalker = g_PCBTable.pPCBBuckets[dwIndex].pPorts;
    pPCBTemp = pPCBWalker;

    while (pPCBTemp != NULL)
    {
        if (wcsncmp (pPCBTemp->pwszDeviceGUID, 
                    pPCB->pwszDeviceGUID, wcslen (pPCB->pwszDeviceGUID)) == 0)
        {
            // Entry is at head of list in table
            if (pPCBTemp == g_PCBTable.pPCBBuckets[dwIndex].pPorts)
            {
                g_PCBTable.pPCBBuckets[dwIndex].pPorts = pPCBTemp->pNext;
            }
            else
            {
                // Entry in inside list in table
                pPCBWalker->pNext = pPCBTemp->pNext;
            }
        
            break;
        }

        pPCBWalker = pPCBTemp;
        pPCBTemp = pPCBWalker->pNext;
    }

    return;

}


//
// ElGetPCBPointerFromPortGUID
//
// Description:
//
// Function called to convert interface GUID to PCB pointer for the entry in 
// the PCB hash table
//
// Arguments:
//      pwszDeviceGUID - Identifier of the form GUID-String
//
// Return values:
//

PEAPOL_PCB
ElGetPCBPointerFromPortGUID (
        IN WCHAR    *pwszDeviceGUID 
        )
{
    EAPOL_PCB   *pPCBWalker = NULL;
    DWORD       dwIndex;

    // TRACE1 (PORT, "ElGetPCBPointerFromPortGUID: GUID %ws", pwszDeviceGUID);
        
    dwIndex = ElHashPortToBucket (pwszDeviceGUID);

    // TRACE1 (PORT, "ElGetPCBPointerFromPortGUID: Index %d", dwIndex);

    for (pPCBWalker = g_PCBTable.pPCBBuckets[dwIndex].pPorts;
            pPCBWalker != NULL;
            pPCBWalker = pPCBWalker->pNext
            )
    {
        if (wcslen(pPCBWalker->pwszDeviceGUID) == wcslen(pwszDeviceGUID))
        {
            if (wcsncmp (pPCBWalker->pwszDeviceGUID, pwszDeviceGUID, wcslen (pwszDeviceGUID)) == 0)
            {
                return pPCBWalker;
            }
        }
    }

    return (NULL);
}


//
// ElInitializeEAPOL
//
// Description:
//
// Function to initialize EAPOL protocol module.
// Global EAPOL parameters are read from the registry.
// PCB hash table is initialized.
// EAP protocol is intialized.
//
// Arguments:
//
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

DWORD
ElInitializeEAPOL (
        )
{
    DWORD       dwIndex;
    HANDLE      hLocalTimerQueue = NULL;
    DWORD       dwRetCode = NO_ERROR;

    do 
    {
        // Initialize global config locks
        if (dwRetCode = CREATE_READ_WRITE_LOCK(&(g_EAPOLConfig), "CFG") != NO_ERROR)
        {
            TRACE1(PORT, "ElInitializeEAPOL: Error %d creating g_EAPOLConfig read-write-lock", dwRetCode);
            // LOG
            break;
        }
    
        // Read parameters stored in registry
        if ((dwRetCode = ElReadGlobalRegistryParams ()) != NO_ERROR)
        {
            TRACE1 (PORT, "ElInitializeEAPOL: ElReadGlobalRegistryParams failed with error = %ld",
                    dwRetCode);
            dwRetCode = NO_ERROR;

            // Don't exit, since default values will be used
        }
     
        // Initialize Hash Bucket Table
        g_PCBTable.pPCBBuckets = (PCB_BUCKET *) MALLOC ( PORT_TABLE_BUCKETS * sizeof (PCB_BUCKET));
    
        if (g_PCBTable.pPCBBuckets == NULL)
        {
            TRACE0 (PORT, "ElInitializeEAPOL: Error in allocating memory for PCB buckets");
            dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
            break;
        }
        g_PCBTable.dwNumPCBBuckets = PORT_TABLE_BUCKETS;
    
        for (dwIndex=0; dwIndex < PORT_TABLE_BUCKETS; dwIndex++)
        {
            g_PCBTable.pPCBBuckets[dwIndex].pPorts=NULL;
        }
    
        // Initialize global locks
        if (dwRetCode = CREATE_READ_WRITE_LOCK(&(g_PCBLock), "PCB") != NO_ERROR)
        {
            TRACE1(PORT, "ElInitializeEAPOL: Error %d creating g_PCBLock read-write-lock", dwRetCode);
            // LOG
            break;
        }
    
        // Create global timer queue for the various EAPOL state machines
        if ((g_hTimerQueue = CreateTimerQueue()) == NULL)
        {
            dwRetCode = GetLastError();
            TRACE1(PORT, "ElInitializeEAPOL: Error %d creating timer queue", dwRetCode);
            break;
        }

        // Initialize EAP
        if ((dwRetCode = ElEapInit(TRUE)) != NO_ERROR)
        {
            TRACE1 (PORT, "ElInitializeEAPOL: Error in ElEapInit= %ld",
                    dwRetCode);
            break;
        }
    
    } while (FALSE);
    
    if (dwRetCode != NO_ERROR)
    {
        if (g_PCBTable.pPCBBuckets != NULL)
        {
            FREE (g_PCBTable.pPCBBuckets);
            g_PCBTable.pPCBBuckets = NULL;
        }

        if (READ_WRITE_LOCK_CREATED(&(g_PCBLock)))
        {
            DELETE_READ_WRITE_LOCK(&(g_PCBLock));
        }

        if (READ_WRITE_LOCK_CREATED(&(g_EAPOLConfig)))
        {
            DELETE_READ_WRITE_LOCK(&(g_EAPOLConfig));
        }

        if (g_hTimerQueue != NULL)
        {
            hLocalTimerQueue = g_hTimerQueue;
            g_hTimerQueue = NULL;

            if (!DeleteTimerQueueEx(
                    hLocalTimerQueue,
                    INVALID_HANDLE_VALUE 
                    ))
            {
                dwRetCode = GetLastError();
                
                TRACE1 (PORT, "ElInitializeEAPOL: Error in DeleteTimerQueueEx = %d",
                        dwRetCode);
            }

        }

        // DeInit EAP
        ElEapInit(FALSE);
    }

    TRACE1 (PORT, "ElInitializeEAPOL: Completed, RetCode = %ld", dwRetCode);
    return dwRetCode;
}


//
// ElCreatePort
//
// Description:
//
// Function to initialize Port Control Block for a port and start EAPOL
// on it. If the PCB already exists for the GUID, EAPOL state machine 
// is restarted for that port.
//
// Arguments:
//      hDevice - Handle to open NDISUIO driver on the interface
//      pwszGUID - Pointer to GUID-String for the interface
//      pwszFriendlyName - Friendly name of the interface
// 
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

DWORD
ElCreatePort (
        IN  HANDLE      hDevice,
        IN  WCHAR       *pwszGUID,
        IN  WCHAR       *pwszFriendlyName,
        IN  DWORD       dwZeroConfigId,
        IN  PRAW_DATA   prdUserData
        )
{
    EAPOL_PCB       *pNewPCB;
    BOOL            fPortToBeReStarted = FALSE;
    BOOL            fPCBCreated = FALSE;
    DWORD           dwIndex = 0;
    DWORD           dwSizeofMacAddr = 0;
    DWORD           dwSizeofSSID = 0;
    DWORD           ulOidDataLength = 0;
    NIC_STATISTICS  NicStatistics;
    EAPOL_ZC_INTF   *pZCData = NULL;
    NDIS_802_11_NETWORK_INFRASTRUCTURE  InfrastructureMode = Ndis802_11InfrastructureMax;
    DWORD           dwSizeOfInfrastructureMode = 0;
    DWORD           dwRetCode = NO_ERROR;

    do 
    {
        TRACE5 (PORT, "ElCreatePort: Entered for Handle=(%p), GUID=(%ws), Name=(%ws), ZCId=(%ld), UserData=(%p)",
                hDevice, pwszGUID, pwszFriendlyName, dwZeroConfigId, prdUserData);

        // See if the port already exists
        // If yes, initialize the state machine
        // Else, create a new port
    
        ACQUIRE_WRITE_LOCK (&g_PCBLock);
    
        pNewPCB = ElGetPCBPointerFromPortGUID (pwszGUID);

        if (pNewPCB != NULL)
        {
            // PCB found, restart EAPOL STATE machine

            fPortToBeReStarted = TRUE;

        }
        else
        {
            // PCB not found, create new PCB and initialize it
            TRACE1 (PORT, "ElCreatePort: No PCB found for %ws", pwszGUID);
    
            // Allocate and initialize a new PCB
            pNewPCB = (PEAPOL_PCB) MALLOC (sizeof(EAPOL_PCB));
            if (pNewPCB == NULL)
            {
                RELEASE_WRITE_LOCK (&g_PCBLock);
                TRACE0(PORT, "ElCreatePort: Error in memory allocation using MALLOC");
                dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
                return dwRetCode;
            }

        }

        // Get Media Statistics for the interface

        ZeroMemory ((PVOID)&NicStatistics, sizeof(NIC_STATISTICS));
        if ((dwRetCode = ElGetInterfaceNdisStatistics (
                        pwszGUID,
                        &NicStatistics
                        )) != NO_ERROR)
        {
            RELEASE_WRITE_LOCK (&g_PCBLock);
            TRACE1(PORT, "ElCreatePort: ElGetInterfaceNdisStatistics failed with error %ld",
                    dwRetCode);
            break;
        }

        if  (fPortToBeReStarted)
        {
            if (NicStatistics.MediaState != MEDIA_STATE_CONNECTED)
            {
                RELEASE_WRITE_LOCK (&g_PCBLock);
                dwRetCode = ERROR_INVALID_STATE;
                TRACE1(PORT, "ElCreatePort: Invalid media status for port to be restarted = (%ld)",
                        NicStatistics.MediaState);
                break;
            }
        }
        else
        {
            if ((NicStatistics.MediaState != MEDIA_STATE_CONNECTED) &&
                (NicStatistics.MediaState != MEDIA_STATE_DISCONNECTED))
            {
                RELEASE_WRITE_LOCK (&g_PCBLock);
                dwRetCode = ERROR_INVALID_STATE;
                TRACE1(PORT, "ElCreatePort: Invalid media status for port = (%ld)",
                        NicStatistics.MediaState);
                break;
            }
        }

        pNewPCB->MediaState = NicStatistics.MediaState;
        pNewPCB->PhysicalMediumType = NicStatistics.PhysicalMediaType;

        if (fPortToBeReStarted)
        {
            // Only port state will be changed to CONNECTING
            // No read requests will be cancelled
            // Hence no new read request will be posted
            TRACE1 (PORT, "ElCreatePort: PCB found for %ws", pwszGUID);
    
            if ((dwRetCode = ElReStartPort (
                            pNewPCB, 
                            dwZeroConfigId,
                            prdUserData)) 
                                    != NO_ERROR)
            {
                TRACE1 (PORT, "ElCreatePort: Error in ElReStartPort = %d",
                        dwRetCode);
            
            }
            RELEASE_WRITE_LOCK (&g_PCBLock);
            break;
        }
        else
        {
            // New Port Control Block created

            // PCB creation reference count
            pNewPCB->dwRefCount = 1;
            pNewPCB->hPort = hDevice; 

            // Mark the port as active 
            pNewPCB->dwFlags = EAPOL_PORT_FLAG_ACTIVE; 

            if (wcslen(pwszGUID) > (GUID_STRING_LEN_WITH_TERM-1))
            {
                RELEASE_WRITE_LOCK (&g_PCBLock);
                TRACE0(PORT, "ElCreatePort: Invalid GUID for port");
                break;
            }

            pNewPCB->pwszDeviceGUID = 
                (PWCHAR) MALLOC ((wcslen(pwszGUID) + 1)*sizeof(WCHAR));
            if (pNewPCB->pwszDeviceGUID == NULL)
            {
                RELEASE_WRITE_LOCK (&g_PCBLock);
                TRACE0(PORT, "ElCreatePort: Error in memory allocation for GUID");
                dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
                break;
            }

            wcscpy (pNewPCB->pwszDeviceGUID, pwszGUID);

            pNewPCB->pwszFriendlyName = 
                (PWCHAR) MALLOC ((wcslen(pwszFriendlyName) + 1)*sizeof(WCHAR));
            if (pNewPCB->pwszFriendlyName == NULL)
            {
                RELEASE_WRITE_LOCK (&g_PCBLock);
                TRACE0(PORT, "ElCreatePort: Error in memory allocation for Friendly Name");
                dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
                break;
            }

            wcscpy (pNewPCB->pwszFriendlyName, pwszFriendlyName);

            // Get the Local Current Mac address 
            dwSizeofMacAddr = SIZE_MAC_ADDR;
            if (dwRetCode = ElNdisuioQueryOIDValue (
                                    pNewPCB->hPort,
                                    OID_802_3_CURRENT_ADDRESS,
                                    pNewPCB->bSrcMacAddr,
                                    &dwSizeofMacAddr
                                            ) != NO_ERROR)
            {
                RELEASE_WRITE_LOCK (&g_PCBLock);
                TRACE1 (PORT, "ElCreatePort: ElNdisuioQueryOIDValue for OID_802_3_CURRENT_ADDRESS failed with error %ld",
                        dwRetCode);
                    break;
            }
            else
            {
                TRACE0 (PORT, "ElCreatePort: ElNdisuioQueryOIDValue for OID_802_3_CURRENT_ADDRESS successful");
                EAPOL_DUMPBA (pNewPCB->bSrcMacAddr, dwSizeofMacAddr);
            }

            if (pNewPCB->PhysicalMediumType == NdisPhysicalMediumWirelessLan)
            {
                // Query the BSSID and SSID if media_connect

                if (pNewPCB->MediaState == MEDIA_STATE_CONNECTED)
                {
                    dwSizeOfInfrastructureMode = sizeof (InfrastructureMode);
                    // Get the infrastructure mode
                    // 802.1x cannot work on Adhoc networks
                    if (dwRetCode = ElNdisuioQueryOIDValue (
                                        pNewPCB->hPort,
                                        OID_802_11_INFRASTRUCTURE_MODE,
                                        (BYTE *)&InfrastructureMode,
                                        &dwSizeOfInfrastructureMode
                                                ) != NO_ERROR)
                    {
                        RELEASE_WRITE_LOCK (&g_PCBLock);
                        TRACE1 (PORT, "ElCreatePort: ElNdisuioQueryOIDValue for OID_802_11_INFRASTRUCTURE_MODE failed with error %ld",
                            dwRetCode);
                        break;
                    }
                    else
                    {
                        TRACE1 (PORT, "ElCreatePort: ElNdisuioQueryOIDValue for OID_802_11_INFRASTRUCTURE_MODE successful, Mode = (%ld)",
                                InfrastructureMode);
                    }

                    if (InfrastructureMode != Ndis802_11Infrastructure)
                    {
                        dwRetCode = ERROR_NOT_SUPPORTED;
                        RELEASE_WRITE_LOCK (&g_PCBLock);
                        TRACE0 (PORT, "ElCreatePort: 802.1x cannot work on non-infrastructure networks");
                        break;
                    }

                    // Get the Remote MAC address if possible
                    dwSizeofMacAddr = SIZE_MAC_ADDR;
                    if (dwRetCode = ElNdisuioQueryOIDValue (
                                        pNewPCB->hPort,
                                        OID_802_11_BSSID,
                                        pNewPCB->bDestMacAddr,
                                        &dwSizeofMacAddr
                                                ) != NO_ERROR)
                    {
                        RELEASE_WRITE_LOCK (&g_PCBLock);
                        TRACE1 (PORT, "ElCreatePort: ElNdisuioQueryOIDValue for OID_802_11_BSSID failed with error %ld",
                            dwRetCode);
                        break;
                    }
                    else
                    {
                        TRACE0 (PORT, "ElCreatePort: ElNdisuioQueryOIDValue for OID_802_11_BSSID successful");
                        EAPOL_DUMPBA (pNewPCB->bDestMacAddr, dwSizeofMacAddr);
                    }

                    if ((pNewPCB->pSSID = MALLOC (NDIS_802_11_SSID_LEN)) == NULL)
                    {
                        RELEASE_WRITE_LOCK (&g_PCBLock);
                        dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
                        TRACE0 (PORT, "ElCreatePort: MALLOC failed for pSSID");
                        break;
                    }

                    dwSizeofSSID = NDIS_802_11_SSID_LEN;
                    if (dwRetCode = ElNdisuioQueryOIDValue (
                                        pNewPCB->hPort,
                                        OID_802_11_SSID,
                                        (BYTE *)pNewPCB->pSSID,
                                        &dwSizeofSSID
                                                ) != NO_ERROR)
                    {
                        RELEASE_WRITE_LOCK (&g_PCBLock);
                        TRACE1 (PORT, "ElCreatePort: ElNdisuioQueryOIDValue for OID_802_11_SSID failed with error %ld",
                            dwRetCode);
    
                        break;
                    }
                    else
                    {
                        if (pNewPCB->pSSID->SsidLength > MAX_SSID_LEN)
                        {
                            RELEASE_WRITE_LOCK (&g_PCBLock);
                            dwRetCode = ERROR_INVALID_PARAMETER;
                            TRACE0 (PORT, "ElCreatePort: ElNdisuioQueryOIDValue OID_802_11_SSID returned invalid SSID");
                            break;
                        }
                        TRACE0 (PORT, "ElCreatePort: ElNdisuioQueryOIDValue for OID_802_11_SSID successful");
                        EAPOL_DUMPBA (pNewPCB->pSSID->Ssid, pNewPCB->pSSID->SsidLength);
                    }
                }
            }
            else
            {
                // Wired Lan

                // Copy default destination Mac address value
                memcpy(pNewPCB->bDestMacAddr, &g_bDefaultGroupMacAddr[0], SIZE_MAC_ADDR);

                // If destination MacAddress is going to be multicast
                // inform the driver to accept the packets to this address

                if ((dwRetCode = ElNdisuioSetOIDValue (
                                                pNewPCB->hPort,
                                                OID_802_3_MULTICAST_LIST,
                                                (BYTE *)&g_bDefaultGroupMacAddr[0],
                                                SIZE_MAC_ADDR)) 
                                                        != NO_ERROR)
                {
                    RELEASE_WRITE_LOCK (&g_PCBLock);
                    TRACE1 (PORT, "ElCreatePort: ElNdisuioSetOIDValue for OID_802_3_MULTICAST_LIST failed with error %ld",
                            dwRetCode);
                    break;
                }
                else
                {
                    TRACE0 (PORT, "ElCreatePort: ElNdisuioSetOIDValue for OID_802_3_MULTICAST_LIST successful");
                }
            }

            // Identity related initialization

            pNewPCB->PreviousAuthenticationType = EAPOL_UNAUTHENTICATED_ACCESS;
            pNewPCB->fGotUserIdentity = FALSE;

            if (prdUserData != NULL)
            {
                if ((prdUserData->dwDataLen >= sizeof (EAPOL_ZC_INTF))
                        && (prdUserData->pData != NULL))
                {
                    // Extract information stored with Zero-Config
                    pZCData = (PEAPOL_ZC_INTF) prdUserData->pData;
                    pNewPCB->dwAuthFailCount = pZCData->dwAuthFailCount;
                    pNewPCB->PreviousAuthenticationType =
                        pZCData->PreviousAuthenticationType;

                    TRACE2 (PORT, "ElCreatePort: prdUserData: Authfailcount = %ld, PreviousAuthType = %ld",
                        pZCData->dwAuthFailCount, pZCData->PreviousAuthenticationType);
                }
                else
                {
                    // Reset for zeroed out prdUserData
                    pNewPCB->dwAuthFailCount = 0;
                    TRACE0 (PORT, "ElCreatePort: prdUserData not valid");
                }

            }

            pNewPCB->dwTotalMaxAuthFailCount = EAPOL_TOTAL_MAX_AUTH_FAIL_COUNT;

            pNewPCB->dwZeroConfigId = dwZeroConfigId;


            // Not yet received 802.1X packet from remote end
            pNewPCB->fIsRemoteEndEAPOLAware = FALSE;
    
            // EAPOL state machine variables
            pNewPCB->State = EAPOLSTATE_LOGOFF;
    
            // Create timer with very high due time and infinite period
            // Timer will be deleted when the port is deleted
            CREATE_TIMER (&(pNewPCB->hTimer), 
                    ElTimeoutCallbackRoutine, 
                    (PVOID)pNewPCB, 
                    INFINITE_SECONDS,
                    "PCB", 
                    &dwRetCode);
            if (dwRetCode != NO_ERROR)
            {
                RELEASE_WRITE_LOCK (&g_PCBLock);
                TRACE1 (PORT, "ElCreatePort: Error in CREATE_TIMER %ld", dwRetCode);
                break;
            }
    
            // EAPOL_Start s that have been sent out
            pNewPCB->ulStartCount = 0;

            // Last received Id from the remote end
            pNewPCB->dwPreviousId = 256;

    
            ACQUIRE_WRITE_LOCK (&g_EAPOLConfig);
            
            pNewPCB->EapolConfig.dwheldPeriod = g_dwheldPeriod;
            pNewPCB->EapolConfig.dwauthPeriod = g_dwauthPeriod;
            pNewPCB->EapolConfig.dwstartPeriod = g_dwstartPeriod;
            pNewPCB->EapolConfig.dwmaxStart = g_dwmaxStart;
    
            RELEASE_WRITE_LOCK (&g_EAPOLConfig);

            // Initialize read-write lock
            if (dwRetCode = CREATE_READ_WRITE_LOCK(&(pNewPCB->rwLock), "EPL") 
                    != NO_ERROR)
            {
                RELEASE_WRITE_LOCK (&g_PCBLock);
                TRACE1(PORT, "ElCreatePort: Error %d creating read-write-lock", 
                        dwRetCode);
                // LOG
                break;
            }
    
            // Initialize registry connection auth data for this port
            // If connection data is not present for EAP-TLS and SSID="Default"
            // create the blob
            if ((dwRetCode = ElInitRegPortData (
                            pNewPCB->pwszDeviceGUID
                            )) != NO_ERROR)
            {
                RELEASE_WRITE_LOCK (&g_PCBLock);
                TRACE1 (PORT, "ElCreatePort: Error in ElInitRegPortData = %d",
                        dwRetCode);
                break;
            }

            // Initialize per port information from registry
            if ((dwRetCode = ElReadPerPortRegistryParams(pwszGUID, pNewPCB)) != NO_ERROR)
            {
                RELEASE_WRITE_LOCK (&g_PCBLock);
                TRACE1(PORT, "ElCreatePort: ElReadPerPortRegistryParams failed with error %ld",
                    dwRetCode);
                break;
            }

            switch (pNewPCB->dwSupplicantMode)
            {
                case SUPPLICANT_MODE_0:
                case SUPPLICANT_MODE_1:
                case SUPPLICANT_MODE_2:
                    pNewPCB->fEAPOLTransmissionFlag = FALSE;
                    break;
                case SUPPLICANT_MODE_3:
                    pNewPCB->fEAPOLTransmissionFlag = TRUE;
                    break;
            }

            // Unicast mode, can talk with peer without broadcast messages
            if (pNewPCB->PhysicalMediumType == NdisPhysicalMediumWirelessLan)
            {
                pNewPCB->fEAPOLTransmissionFlag = TRUE;
            }
                
            if ((!IS_EAPOL_ENABLED(pNewPCB->dwEapFlags)) ||
                    (pNewPCB->dwSupplicantMode == SUPPLICANT_MODE_0))
            {
                TRACE0 (PORT, "ElCreatePort: Marking port as disabled");
                pNewPCB->dwFlags &= ~EAPOL_PORT_FLAG_ACTIVE;
                pNewPCB->dwFlags |= EAPOL_PORT_FLAG_DISABLED;
            }

            // Add one more for local access
            pNewPCB->dwRefCount += 1;

            // Insert NewPCB into PCB hash table
            dwIndex = ElHashPortToBucket (pwszGUID);
            pNewPCB->pNext = g_PCBTable.pPCBBuckets[dwIndex].pPorts;
            g_PCBTable.pPCBBuckets[dwIndex].pPorts = pNewPCB;
            pNewPCB->dwPortIndex = dwIndex;

            fPCBCreated = TRUE;

            RELEASE_WRITE_LOCK (&g_PCBLock);

            ACQUIRE_WRITE_LOCK (&(pNewPCB->rwLock));

            //
            // Post a read request on the port
            //

            // Initiate read operation on the port, since it is now active
            if (dwRetCode = ElReadFromPort (
                        pNewPCB,
                        NULL,
                        0
                        )
                    != NO_ERROR)
            {
                RELEASE_WRITE_LOCK (&(pNewPCB->rwLock));
                TRACE1 (PORT, "ElCreatePort: Error in ElReadFromPort = %d",
                        dwRetCode);
                break;
            }
            
            //
            // Kick off EAPOL state machine
            //

            if ((pNewPCB->MediaState == MEDIA_STATE_CONNECTED) &&
                    EAPOL_PORT_ACTIVE(pNewPCB))
            {
                // Set port to EAPOLSTATE_CONNECTING State
                // Send out EAPOL_Start Packets to detect if it is a secure
                // or non-secure LAN based on response received from remote end

                if ((dwRetCode = FSMConnecting (pNewPCB, NULL)) != NO_ERROR)
                {
                    RELEASE_WRITE_LOCK (&(pNewPCB->rwLock));
                    TRACE1 (PORT, "ElCreatePort: FSMConnecting failed with error %ld",
                            dwRetCode);
                    break;
                }
            }
            else
            {
                // Set port to EAPOLSTATE_DISCONNECTED State
                if ((dwRetCode = FSMDisconnected (pNewPCB, NULL)) != NO_ERROR)
                {
                    RELEASE_WRITE_LOCK (&(pNewPCB->rwLock));
                    TRACE1 (PORT, "ElCreatePort: FSMDisconnected failed with error %ld",
                            dwRetCode);
                    break;
                }
            }

            RELEASE_WRITE_LOCK (&(pNewPCB->rwLock));
        
            TRACE2 (PORT, "ElCreatePort: Completed for GUID= %ws, Name = %ws", 
                    pNewPCB->pwszDeviceGUID, pNewPCB->pwszFriendlyName);
        }

    } 
    while (FALSE);

    // Remove the local access reference
    if (fPCBCreated)
    {
        EAPOL_DEREFERENCE_PORT(pNewPCB);
    }

    if (dwRetCode != NO_ERROR)
    {
        // If PCB was not being restarted
        if (!fPortToBeReStarted)
        {
            // If PCB was created
            if (fPCBCreated)
            {
                HANDLE  hTempDevice;

                // Mark the Port as deleted. Cleanup if possible
                // Don't worry about return code
                ElDeletePort (
                        pNewPCB->pwszDeviceGUID,
                        &hDevice
                        );
            }
            else
            {
                // Remove all partial traces of port creation

                if (pNewPCB->hTimer != NULL)
                {
                    if (InterlockedCompareExchangePointer (
                        &g_hTimerQueue,
                        NULL,
                        NULL
                        ))
                    {
                        DWORD       dwTmpRetCode = NO_ERROR;
                        TRACE2 (PORT, "ElCreatePort: DeleteTimer (%p), queue (%p)",
                                pNewPCB->hTimer, g_hTimerQueue);
                        DELETE_TIMER (pNewPCB->hTimer, INVALID_HANDLE_VALUE, 
                                &dwTmpRetCode);
                        if (dwTmpRetCode != NO_ERROR)
                        {
                            TRACE1 (PORT, "ElCreatePort: DeleteTimer failed with error %ld",
                                    dwTmpRetCode);
                        }
                    }
                }

                if (READ_WRITE_LOCK_CREATED(&(pNewPCB->rwLock)))
                {
                    DELETE_READ_WRITE_LOCK(&(pNewPCB->rwLock));
                }
                
                if (pNewPCB->pwszDeviceGUID != NULL)
                {
                    FREE(pNewPCB->pwszDeviceGUID);
                    pNewPCB->pwszDeviceGUID = NULL;
                }
                if (pNewPCB->pwszFriendlyName != NULL)
                {
                    FREE(pNewPCB->pwszFriendlyName);
                    pNewPCB->pwszFriendlyName = NULL;
                }
                if (pNewPCB != NULL)
                {
                    ZeroMemory ((PVOID)pNewPCB, sizeof (EAPOL_PCB));
                    FREE (pNewPCB);
                    pNewPCB = NULL;
                }
            }
        }
    }

    return dwRetCode;
}
        

//
// ElDeletePort
//
// Description:
//
// Function to stop EAPOL and delete PCB for a port.
// Returns back pointer to handle opened on the interface so that
// the handle can be closed by the interface management module.
//
// Input arguments:
//      pwszDeviceGUID - GUID-String of the interface whose PCB needs to be 
//                      deleted
//      pHandle - Output: Handle to NDISUIO driver for this port
//
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

DWORD
ElDeletePort (
        IN  WCHAR   *pwszDeviceGUID,
        OUT HANDLE  *pHandle
        )
{
    EAPOL_PCB   *pPCB = NULL;
    HANDLE      hTimer = NULL;
    DWORD       dwRetCode = NO_ERROR;

    ACQUIRE_WRITE_LOCK (&(g_PCBLock));

    // Verify if PCB exists for this GUID

    TRACE1 (PORT, "ElDeletePort entered for GUID %ws", pwszDeviceGUID);
    pPCB = ElGetPCBPointerFromPortGUID (pwszDeviceGUID);

    if (pPCB == NULL)
    {
        RELEASE_WRITE_LOCK (&(g_PCBLock));
        TRACE1 (PORT, "ElDeletePort: PCB not found entered for Port %s", 
                pwszDeviceGUID);
        return ERROR_NO_SUCH_INTERFACE;
    }

    ACQUIRE_WRITE_LOCK (&(pPCB->rwLock));

    // Make sure it isn't already deleted

    if (EAPOL_PORT_DELETED(pPCB)) 
    {
        RELEASE_WRITE_LOCK (&(pPCB->rwLock));
        RELEASE_WRITE_LOCK (&(g_PCBLock));
        TRACE1 (PORT, "ElDeletePort: PCB already marked deleted for Port %ws", 
                pwszDeviceGUID);
        return ERROR_NO_SUCH_INTERFACE;
    }
   
    InterlockedIncrement (&g_lPCBContextsAlive);

    // Retain handle to NDISUIO device
    *pHandle = pPCB->hPort;

    // Mark the PCB as deleted and remove it from the hash bucket
    pPCB->dwFlags = EAPOL_PORT_FLAG_DELETED;
    ElRemovePCBFromTable(pPCB);
    
    // Shutdown EAP 
    // Will always return NO_ERROR, so no check on return value
    ElEapEnd (pPCB);

    // Delete timer since PCB is not longer to be used

    hTimer = pPCB->hTimer;

    TRACE1 (PORT, "ElDeletePort: RefCount for port = %ld", pPCB->dwRefCount);
    
    RELEASE_WRITE_LOCK (&(pPCB->rwLock));

    if (InterlockedCompareExchangePointer (
                &g_hTimerQueue,
                NULL,
                NULL
                ))
    {
        TRACE2 (PORT, "ElDeletePort: DeleteTimer (%p), queue (%p)",
                hTimer, g_hTimerQueue);
        DELETE_TIMER (hTimer, INVALID_HANDLE_VALUE, &dwRetCode);
        if (dwRetCode != NO_ERROR)
        {
            TRACE1 (PORT, "ElDeletePort: DeleteTimer failed with error %ld",
                    dwRetCode);
        }
    }

    // If reference count is zero, perform final cleanup
    
    EAPOL_DEREFERENCE_PORT (pPCB);

    RELEASE_WRITE_LOCK (&(g_PCBLock));

    return NO_ERROR;
}


//
// ElCleanupPort
//
// Description:
//
// Function called when the very last reference to a PCB
// is released. The PCB memory is released and zeroed out
//
// Arguments:
//  pPCB - Pointer to port control block to be destroyed
// 
//

VOID
ElCleanupPort (
        IN  PEAPOL_PCB  pPCB
        )
{
    DWORD       dwRetCode = NO_ERROR;

    TRACE1 (PORT, "ElCleanupPort entered for %ws", pPCB->pwszDeviceGUID);

    if (pPCB->pwszDeviceGUID != NULL)
    {
        FREE (pPCB->pwszDeviceGUID);
    }
    if (pPCB->pwszFriendlyName)
    {
        FREE (pPCB->pwszFriendlyName);
    }

    if (pPCB->pwszEapReplyMessage != NULL)
    {
        FREE (pPCB->pwszEapReplyMessage);
    }

    if (pPCB->pwszSSID != NULL)
    {
        FREE (pPCB->pwszSSID);
    }

    if (pPCB->pSSID != NULL)
    {
        FREE (pPCB->pSSID);
    }

    if (pPCB->EapUIData.pEapUIData != NULL)
    {
        FREE (pPCB->EapUIData.pEapUIData);
    }

    if (pPCB->MasterSecretSend.cbData != 0)
    {
        FREE (pPCB->MasterSecretSend.pbData);
        pPCB->MasterSecretSend.cbData = 0;
        pPCB->MasterSecretSend.pbData = NULL;
    }

    if (pPCB->MasterSecretRecv.cbData != 0)
    {
        FREE (pPCB->MasterSecretRecv.pbData);
        pPCB->MasterSecretRecv.cbData = 0;
        pPCB->MasterSecretRecv.pbData = NULL;
    }

    if (pPCB->MPPESendKey.cbData != 0)
    {
        FREE (pPCB->MPPESendKey.pbData);
        pPCB->MPPESendKey.cbData = 0;
        pPCB->MPPESendKey.pbData = NULL;
    }

    if (pPCB->MPPERecvKey.cbData != 0)
    {
        FREE (pPCB->MPPERecvKey.pbData);
        pPCB->MPPERecvKey.cbData = 0;
        pPCB->MPPERecvKey.pbData = NULL;
    }

    if (pPCB->hUserToken != NULL)
    {
        if (!CloseHandle (pPCB->hUserToken))
        {
            dwRetCode = GetLastError ();
            TRACE1 (PORT, "ElCleanupPort: CloseHandle failed with error %ld",
                    dwRetCode);
            dwRetCode = NO_ERROR;
        }
    }
    pPCB->hUserToken = NULL;

    if (pPCB->pszIdentity != NULL)
    {
        FREE (pPCB->pszIdentity);
    }

    if (pPCB->PasswordBlob.pbData != NULL)
    {
        FREE (pPCB->PasswordBlob.pbData);
    }

    if (pPCB->pCustomAuthUserData != NULL)
    {
        FREE (pPCB->pCustomAuthUserData);
    }

    if (pPCB->pCustomAuthConnData != NULL)
    {
        FREE (pPCB->pCustomAuthConnData);
    }

    if (pPCB->pbPreviousEAPOLPkt != NULL)
    {
        FREE (pPCB->pbPreviousEAPOLPkt);
    }

    if (READ_WRITE_LOCK_CREATED(&(pPCB->rwLock)))
    {
        DELETE_READ_WRITE_LOCK(&(pPCB->rwLock));
    }
    
    ZeroMemory ((PVOID)pPCB, sizeof(EAPOL_PCB));

    FREE (pPCB);

    pPCB = NULL;

    InterlockedDecrement (&g_lPCBContextsAlive);

    TRACE0 (PORT, "ElCleanupPort completed");

    return;

} 


//
// ElReStartPort
//
// Description:
//
// Function called to reset the EAPOL state machine to Connecting state
// This may be called due to:
//      1. From ElCreatePort, for an existing PCB
//      2. Configuration parameters may have changed. Initialization
//          is required to allow new values to take effect.
//      Initialization will take the EAPOL state to CONNECTING
//
// Arguments:
//  pPCB - Pointer to port control block to be initialized
//
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

DWORD
ElReStartPort (
        IN  EAPOL_PCB   *pPCB,
        IN  DWORD       dwZeroConfigId,
        IN  PRAW_DATA   prdUserData
        )
{
    DWORD           dwSizeofSSID = 0;
    DWORD           dwSizeofMacAddr = 0;
    DWORD           dwCurrenTickCount = 0;
    EAPOL_ZC_INTF   *pZCData = NULL;
    NIC_STATISTICS  NicStatistics;
    NDIS_802_11_SSID PreviousSSID;
    BOOLEAN         fResetCredentials = TRUE;
    BYTE            bTmpDestMacAddr[SIZE_MAC_ADDR];
    NDIS_802_11_NETWORK_INFRASTRUCTURE  InfrastructureMode = Ndis802_11InfrastructureMax;
    DWORD           dwSizeOfInfrastructureMode = 0;
    DWORD           dwRetCode = NO_ERROR;

    TRACE1 (PORT, "ElReStartPort: Entered: Refcnt = %ld",
            pPCB->dwRefCount);

    do
    {
        ACQUIRE_WRITE_LOCK (&pPCB->rwLock);

        if (EAPOL_PORT_DELETED(pPCB)) 
        {
            RELEASE_WRITE_LOCK (&(pPCB->rwLock));
            TRACE1 (PORT, "ElReStartPort: PCB already marked deleted for Port %ws", 
                    pPCB->pwszDeviceGUID);
            break;
        }

        pPCB->dwFlags = EAPOL_PORT_FLAG_ACTIVE;
    
        // Set current authentication mode, based on administrative setting
        pPCB->PreviousAuthenticationType = EAPOL_UNAUTHENTICATED_ACCESS;

        if (prdUserData != NULL)
        {
            if ((prdUserData->dwDataLen >= sizeof (EAPOL_ZC_INTF))
                    && (prdUserData->pData != NULL))
            {
                // Extract information stored with Zero-Config
                pZCData = (PEAPOL_ZC_INTF) prdUserData->pData;
                pPCB->dwAuthFailCount = pZCData->dwAuthFailCount;
                pPCB->PreviousAuthenticationType =
                    pZCData->PreviousAuthenticationType;
                TRACE2 (PORT, "ElReStartPort: prdUserData: Authfailcount = %ld, PreviousAuthType = %ld",
                        pZCData->dwAuthFailCount, pZCData->PreviousAuthenticationType);
            }
            else
            {
                // Reset for zeroed out prdUserData
                pPCB->dwAuthFailCount = 0;
                TRACE0 (PORT, "ElReStartPort: prdUserData not valid");
            }
        }

        pPCB->EapUIState = 0;

        pPCB->dwTotalMaxAuthFailCount = EAPOL_TOTAL_MAX_AUTH_FAIL_COUNT;
        pPCB->dwZeroConfigId = dwZeroConfigId;

        pPCB->ulStartCount = 0;
        pPCB->dwPreviousId = 256;
        pPCB->dwLogoffSent = 0;
        pPCB->ullLastReplayCounter = 0;
        pPCB->fAuthenticationOnNewNetwork = FALSE;

        // Clean out CustomAuthData since EAP type may have changed
        // During authentication, CustomAuthData for the connection will be
        // picked up again

        if (pPCB->pCustomAuthConnData != NULL)
        {
            FREE (pPCB->pCustomAuthConnData);
            pPCB->pCustomAuthConnData = NULL;
        }
    
        // Parameters initialization
        memcpy(pPCB->bEtherType, &g_bEtherType8021X[0], SIZE_ETHERNET_TYPE);
        pPCB->bProtocolVersion = DEFAULT_8021X_VERSION;
     
        // Not yet received 802.1X packet from remote end
        pPCB->fIsRemoteEndEAPOLAware = FALSE;

        // Set EAPOL timeout values
     
        ACQUIRE_WRITE_LOCK (&g_EAPOLConfig);
            
        pPCB->EapolConfig.dwheldPeriod = g_dwheldPeriod;
        pPCB->EapolConfig.dwauthPeriod = g_dwauthPeriod;
        pPCB->EapolConfig.dwstartPeriod = g_dwstartPeriod;
        pPCB->EapolConfig.dwmaxStart = g_dwmaxStart;

        RELEASE_WRITE_LOCK (&g_EAPOLConfig);

        ZeroMemory ((PVOID)&NicStatistics, sizeof(NIC_STATISTICS));
        if ((dwRetCode = ElGetInterfaceNdisStatistics (
                        pPCB->pwszDeviceGUID,
                        &NicStatistics
                        )) != NO_ERROR)
        {
            RELEASE_WRITE_LOCK (&pPCB->rwLock);
            TRACE1(PORT, "ElReStartPort: ElGetInterfaceNdisStatistics failed with error %ld",
                    dwRetCode);
            break;
        }

        pPCB->MediaState = NicStatistics.MediaState;

        ZeroMemory ((BYTE *)&PreviousSSID, sizeof(NDIS_802_11_SSID));

        if (pPCB->pSSID != NULL)
        {
            memcpy ((BYTE *)&PreviousSSID, (BYTE *)pPCB->pSSID, 
                    sizeof(NDIS_802_11_SSID));
        }
                
        // Get the Remote Mac address if possible, since we may have roamed
        if (pPCB->PhysicalMediumType == NdisPhysicalMediumWirelessLan)
        {
            // Since authentication is to be restarted, flag that transmit
            // key was not received
            pPCB->fTransmitKeyReceived = FALSE;

            dwSizeOfInfrastructureMode = sizeof (InfrastructureMode);
            // Get the infrastructure mode
            // 802.1x cannot work on Adhoc networks
            if (dwRetCode = ElNdisuioQueryOIDValue (
                                pPCB->hPort,
                                OID_802_11_INFRASTRUCTURE_MODE,
                                (BYTE *)&InfrastructureMode,
                                &dwSizeOfInfrastructureMode
                                        ) != NO_ERROR)
            {
                RELEASE_WRITE_LOCK (&pPCB->rwLock);
                TRACE1 (PORT, "ElReStartPort: ElNdisuioQueryOIDValue for OID_802_11_INFRASTRUCTURE_MODE failed with error %ld",
                    dwRetCode);
                break;
            }
            else
            {
                TRACE1 (PORT, "ElReStartPort: ElNdisuioQueryOIDValue for OID_802_11_INFRASTRUCTURE_MODE successful, Mode = (%ld)",
                        InfrastructureMode);
            }

            if (InfrastructureMode != Ndis802_11Infrastructure)
            {
                dwRetCode = ERROR_NOT_SUPPORTED;
                RELEASE_WRITE_LOCK (&pPCB->rwLock);
                TRACE0 (PORT, "ElReStartPort: 802.1x cannot work on non-infrastructure networks");
                break;
            }

            ZeroMemory (bTmpDestMacAddr, SIZE_MAC_ADDR);
            dwSizeofMacAddr = SIZE_MAC_ADDR;
            if (dwRetCode = ElNdisuioQueryOIDValue (
                                pPCB->hPort,
                                OID_802_11_BSSID,
                                bTmpDestMacAddr,
                                &dwSizeofMacAddr
                                        ) != NO_ERROR)
            {
                RELEASE_WRITE_LOCK (&pPCB->rwLock);
                TRACE1 (PORT, "ElReStartPort: ElNdisuioQueryOIDValue for OID_802_11_BSSID failed with error %ld",
                    dwRetCode);

                break;
            }
            else
            {
                TRACE0 (PORT, "ElReStartPort: ElNdisuioQueryOIDValue for OID_802_11_BSSID successful");
                EAPOL_DUMPBA (bTmpDestMacAddr, dwSizeofMacAddr);
            }

            memcpy (pPCB->bDestMacAddr, bTmpDestMacAddr, SIZE_MAC_ADDR);
                
            // Query the SSID if media_connect
            if (pPCB->pSSID != NULL)
            {
                FREE (pPCB->pSSID);
                pPCB->pSSID = NULL;
            }

            if ((pPCB->pSSID = MALLOC (NDIS_802_11_SSID_LEN)) == NULL)
            {
                RELEASE_WRITE_LOCK (&pPCB->rwLock);
                dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
                TRACE0 (PORT, "ElReStartPort: MALLOC failed for pSSID");
                break;
            }

            dwSizeofSSID = NDIS_802_11_SSID_LEN;
            if (dwRetCode = ElNdisuioQueryOIDValue (
                                pPCB->hPort,
                                OID_802_11_SSID,
                                (BYTE *)pPCB->pSSID,
                                &dwSizeofSSID
                                        ) != NO_ERROR)
            {
                RELEASE_WRITE_LOCK (&pPCB->rwLock);
                TRACE1 (PORT, "ElReStartPort: ElNdisuioQueryOIDValue for OID_802_11_SSID failed with error %ld",
                    dwRetCode);
                break;
            }
            else
            {
                if (pPCB->pSSID->SsidLength > MAX_SSID_LEN)
                {
                    RELEASE_WRITE_LOCK (&pPCB->rwLock);
                    dwRetCode = ERROR_INVALID_PARAMETER;
                    TRACE0 (PORT, "ElReStartPort: ElNdisuioQueryOIDValue OID_802_11_SSID returned invalid SSID");
                    break;
                }
                TRACE0 (PORT, "ElReStartPort: ElNdisuioQueryOIDValue for OID_802_11_SSID successful");
                EAPOL_DUMPBA (pPCB->pSSID->Ssid, pPCB->pSSID->SsidLength);
            }
        }

        // Retain credentials if on same network

        if (pPCB->pSSID != NULL)
        {
            if (!memcmp ((BYTE *)pPCB->pSSID, (BYTE *)&PreviousSSID,
                        sizeof(NDIS_802_11_SSID)))
            {
                fResetCredentials = FALSE;
            }
        }

        if (fResetCredentials)
        {
            pPCB->fGotUserIdentity = FALSE;

            if (pPCB->PasswordBlob.pbData != NULL)
            {
                FREE (pPCB->PasswordBlob.pbData);
                pPCB->PasswordBlob.pbData = NULL;
                pPCB->PasswordBlob.cbData = 0;
            }

            if (pPCB->hUserToken != NULL)
            {
                if (!CloseHandle (pPCB->hUserToken))
                {
                    dwRetCode = GetLastError ();
                    TRACE1 (PORT, "ElReStartPort: CloseHandle failed with error %ld",
                            dwRetCode);
                    dwRetCode = NO_ERROR;
                }
            }
            pPCB->hUserToken = NULL;
        }
        else
        {
            // If this is the same SSID refresh the Master Secret with the
            // last copy of MPPE Keys. If re-keying has stomped on keys, this
            // will ensure that with the new AP with IAPP, the keys will
            // be same on the supplicant too

            if ((dwRetCode = ElReloadMasterSecrets (pPCB)) != NO_ERROR)
            {
                RELEASE_WRITE_LOCK (&pPCB->rwLock);
                TRACE1 (PORT, "ElReStartPort: ElReloadMasterSecret failed with error %ld",
                        dwRetCode);
                break;
            }
        }

        // Initialize per port information from registry
        if ((dwRetCode = ElReadPerPortRegistryParams(pPCB->pwszDeviceGUID, 
                                                        pPCB)) != NO_ERROR)
        {
            RELEASE_WRITE_LOCK (&pPCB->rwLock);
            TRACE1(PORT, "ElReStartPort: ElReadPerPortRegistryParams failed with error %ld",
                    dwRetCode);
            break;
        }
            
        // Set correct supplicant mode
        switch (pPCB->dwSupplicantMode)
        {
            case SUPPLICANT_MODE_0:
            case SUPPLICANT_MODE_1:
            case SUPPLICANT_MODE_2:
                pPCB->fEAPOLTransmissionFlag = FALSE;
                break;
            case SUPPLICANT_MODE_3:
                pPCB->fEAPOLTransmissionFlag = TRUE;
                break;
        }

        // Unicast mode, can talk with peer without broadcast messages
        if (pPCB->PhysicalMediumType == NdisPhysicalMediumWirelessLan)
        {
            pPCB->fEAPOLTransmissionFlag = TRUE;
        }

        if ((!IS_EAPOL_ENABLED(pPCB->dwEapFlags)) ||
                (pPCB->dwSupplicantMode == SUPPLICANT_MODE_0))
        {
            TRACE0 (PORT, "ElReStartPort: Marking port as disabled");
            pPCB->dwFlags &= ~EAPOL_PORT_FLAG_ACTIVE;
            pPCB->dwFlags |= EAPOL_PORT_FLAG_DISABLED;
        }

        if ((pPCB->MediaState == MEDIA_STATE_CONNECTED) &&
                EAPOL_PORT_ACTIVE(pPCB))
        {
            // Set port to EAPOLSTATE_CONNECTING State
            // Send out EAPOL_Start Packets to detect if it is a secure
            // or non-secure LAN based on response received from remote end

            if ((dwRetCode = FSMConnecting (pPCB, NULL)) != NO_ERROR)
            {
                RELEASE_WRITE_LOCK (&(pPCB->rwLock));
                TRACE1 (PORT, "ElReStartPort: FSMConnecting failed with error %ld",
                        dwRetCode);
                break;
            }
        }
        else
        {
            // Set port to EAPOLSTATE_DISCONNECTED State
            if ((dwRetCode = FSMDisconnected (pPCB, NULL)) != NO_ERROR)
            {
                RELEASE_WRITE_LOCK (&(pPCB->rwLock));
                TRACE1 (PORT, "ElReStartPort: FSMDisconnected failed with error %ld",
                        dwRetCode);
                break;
            }
        }

        RELEASE_WRITE_LOCK (&pPCB->rwLock);

    } while (FALSE);

    return dwRetCode;
}


//
// ElEAPOLDeInit
//
// Description:
//
// Function called to shutdown EAPOL module 
// Shutdown EAP.
// Cleanup all used memory
//
// Arguments:
//
// Return values:
//      NO_ERROR - success
//      non-zero - error
//
//

DWORD
ElEAPOLDeInit (
        )
{
    EAPOL_PCB       *pPCBWalker = NULL;
    EAPOL_PCB       *pPCB = NULL;
    DWORD           dwIndex = 0;              
    HANDLE          hLocalTimerQueue = NULL;
    HANDLE          hNULL = NULL;
    HANDLE          hTimer = NULL;
    DWORD           dwRetCode = NO_ERROR;

    TRACE0 (PORT, "ElEAPOLDeInit entered");

    do 
    {
        // Walk the hash table
        // Mark PCBs as deleted. Free PCBs which we can

        ACQUIRE_WRITE_LOCK (&(g_PCBLock));

        for (dwIndex = 0; dwIndex < PORT_TABLE_BUCKETS; dwIndex++)
        {
            pPCBWalker = g_PCBTable.pPCBBuckets[dwIndex].pPorts;

            while (pPCBWalker != NULL)
            {
                pPCB = pPCBWalker;
                pPCBWalker = pPCB->pNext;

                ACQUIRE_WRITE_LOCK (&(pPCB->rwLock));

                // Send out Logoff Packet so that no one else can
                // ride on the connection
                // If the mode does not allow EAPOL_Logoff packet to sent
                // out, there is not much that can be done to break the 
                // connection
                FSMLogoff (pPCB, NULL);

                // Mark the PCB as deleted and remove it from the hash bucket
                pPCB->dwFlags = EAPOL_PORT_FLAG_DELETED;
                ElRemovePCBFromTable(pPCB);

                // Shutdown EAP 
                ElEapEnd (pPCB);

                hTimer = pPCB->hTimer;

                RELEASE_WRITE_LOCK (&(pPCB->rwLock));

                if (InterlockedCompareExchangePointer (
                                &g_hTimerQueue,
                                NULL,
                                NULL
                                ))
                {
                    TRACE2 (PORT, "ElEAPOLDeInit: DeleteTimer (%p), queue (%p)",
                            hTimer, g_hTimerQueue);

                    DELETE_TIMER (hTimer, INVALID_HANDLE_VALUE, &dwRetCode);
                    if (dwRetCode != NO_ERROR)
                    {
                        TRACE1 (PORT, "ElEAPOLDeInit: DeleteTimer 1 failed with error %ld",
                                dwRetCode);
                    }
                }

                ACQUIRE_WRITE_LOCK (&(pPCB->rwLock));

                // Close the handle to the NDISUIO driver
                if ((dwRetCode = ElCloseInterfaceHandle (
                                        pPCB->hPort, 
                                        pPCB->pwszDeviceGUID)) 
                                != NO_ERROR)
                {
                    TRACE1 (DEVICE, 
                        "ElEAPOLDeInit: Error in ElCloseInterfaceHandle %d", 
                        dwRetCode);
                }

                RELEASE_WRITE_LOCK (&(pPCB->rwLock));

                InterlockedIncrement (&g_lPCBContextsAlive);

                EAPOL_DEREFERENCE_PORT (pPCB);
            }
        }

        RELEASE_WRITE_LOCK (&(g_PCBLock));
    
        do
        {
            TRACE1 (PORT, "ElEAPOLDeInit: Waiting for %ld PCB contexts to terminate ...", 
                    g_lPCBContextsAlive);
            Sleep (1000);
        }
        while (g_lPCBContextsAlive != 0);

        // Delete EAPOL config lock
        if (READ_WRITE_LOCK_CREATED(&(g_EAPOLConfig)))
        {
            DELETE_READ_WRITE_LOCK(&(g_EAPOLConfig));
        }
    
        // Delete global PCB table lock
        if (READ_WRITE_LOCK_CREATED(&(g_PCBLock)))
        {
            DELETE_READ_WRITE_LOCK(&(g_PCBLock));
        }
    
        if (g_PCBTable.pPCBBuckets != NULL)
        {
            FREE (g_PCBTable.pPCBBuckets);
            g_PCBTable.pPCBBuckets = NULL;
        }

        // Delete global timer queue
        if (g_hTimerQueue != NULL)
        {
            hLocalTimerQueue = InterlockedExchangePointer (
                    &g_hTimerQueue,
                    hNULL
                    );

            if (!DeleteTimerQueueEx(
                hLocalTimerQueue,
                INVALID_HANDLE_VALUE // Wait for ALL timer callbacks to complete
                ))
            {
                dwRetCode = GetLastError();

                TRACE1 (PORT, "ElEAPOLDeInit: Error in DeleteTimerQueueEx = %d",
                        dwRetCode);

            }
        }
    
        // Un-initialize EAP
        if ((dwRetCode = ElEapInit(FALSE)) != NO_ERROR)
        {
            TRACE1 (PORT, "ElEAPOLDeInit: Error in ElEapInit(FALSE) = %ld",
                    dwRetCode);
            break;
        }
    
    } while (FALSE);

    TRACE1 (PORT, "ElEAPOLDeInit completed, RetCode = %d", dwRetCode);

    return dwRetCode;
}


//
// Currently Unsupported 
// Read EAPOL statistics for the port
//

VOID
ElReadPortStatistics (
        IN  WCHAR           *pwszDeviceGUID,
        OUT PEAPOL_STATS    pEapolStats
        )
{
}


//
// Currently Unsupported 
// Read EAPOL Port Configuration for the mentioned port
//

VOID
ElReadPortConfiguration (
        IN  WCHAR           *pwszDeviceGUID,
        OUT PEAPOL_CONFIG   pEapolConfig
        )
{
}


//
// Currently Unsupported 
// Set EAPOL Port Configuration for the mentioned port
//

DWORD
ElSetPortConfiguration (
        IN  WCHAR           *pwszDeviceGUID,
        IN  PEAPOL_CONFIG   pEapolConfig
        )
{
    DWORD   dwRetCode = NO_ERROR;

    return dwRetCode;
}


//
// ElReadCompletionRoutine
//
// Description:
//
// This routine is invoked upon completion of an OVERLAPPED read operation
// on an interface on which EAPOL is running
//
// The message read is validated and processed, and if necessary,
// a reply is generated and sent out
//
// Arguments:
//      dwError - Win32 status code for the I/O operation
//
//      dwBytesTransferred - number of bytes in 'pEapolBuffer'
// 
//      pEapolBuffer - holds data read from the datagram socket
//
// Notes:
//  A reference to the component will have been made on our behalf
//  by ElReadPort(). Hence g_PCBLock, will not be required
//  to be taken since current PCB existence is guaranteed
//

VOID 
CALLBACK
ElReadCompletionRoutine (
        DWORD           dwError,
        DWORD           dwBytesReceived,
        EAPOL_BUFFER    *pEapolBuffer 
        )
{
    EAPOL_PCB       *pPCB;
    DWORD           dwRetCode;

    pPCB = (EAPOL_PCB *)pEapolBuffer->pvContext;
    TRACE1 (PORT, "ElReadCompletionRoutine entered, %ld bytes recvd",
            dwBytesReceived);

    do 
    {
        if (dwError)
        {
            // Error in read request
           
            TRACE2 (PORT, "ElReadCompletionRoutine: Error %d on port %ws",
                    dwError, pPCB->pwszDeviceGUID);
            
            // Having a ref count from the read posted, guarantees existence
            // of PCB. Hence no need to acquire g_PCBLock

            ACQUIRE_WRITE_LOCK (&(pPCB->rwLock));
            if (EAPOL_PORT_DELETED(pPCB))
            {
                TRACE1 (PORT, "ElReadCompletionRoutine: Port %ws not active",
                        pPCB->pwszDeviceGUID);
                // Port is not active, release Context buffer
                RELEASE_WRITE_LOCK (&(pPCB->rwLock));
                FREE (pEapolBuffer);
            }
            else
            {
                TRACE1 (PORT, "ElReadCompletionRoutine: Reposting buffer on port %ws",
                        pPCB->pwszDeviceGUID);


                // Repost buffer for another read operation
                // Free the current buffer, ElReadFromPort creates a new 
                // buffer
                FREE(pEapolBuffer);

                if ((dwRetCode = ElReadFromPort (
                                pPCB,
                                NULL,
                                0
                                )) != NO_ERROR)
                {
                    RELEASE_WRITE_LOCK (&(pPCB->rwLock));
                    TRACE1 (PORT, "ElReadCompletionRoutine: ElReadFromPort 1 error %d",
                            dwRetCode);
                    break;
                }
                RELEASE_WRITE_LOCK (&(pPCB->rwLock));
            }
            break;
        }
            
        // Successful read completion

        ACQUIRE_WRITE_LOCK (&(pPCB->rwLock));

        if (EAPOL_PORT_DELETED(pPCB))
        {
            // Port is not active
            RELEASE_WRITE_LOCK (&(pPCB->rwLock));
            FREE (pEapolBuffer);
            TRACE1 (PORT, "ElReadCompletionRoutine: Port %ws is inactive",
                    pPCB->pwszDeviceGUID);
            break;
        }
            
        RELEASE_WRITE_LOCK (&(pPCB->rwLock));

        // Queue a work item to the Thread Pool to execute in the
        // I/O component. Callbacks from BindIoCompletionCallback do not
        // guarantee to be running in I/O component. So, on a non I/O
        // component thread may die while requests are pending.
        // (Refer to Jeffrey Richter, pg 416, Programming Applications for
        // Microsoft Windows, Fourth Edition

        // pEapolBuffer will be the context for the function
        // since it stores all relevant information for processing
        // i.e. pBuffer, dwBytesTransferred, pContext => pPCB

        InterlockedIncrement (&g_lWorkerThreads);

        if (!QueueUserWorkItem (
                    (LPTHREAD_START_ROUTINE)ElProcessReceivedPacket,
                    (PVOID)pEapolBuffer,
                    WT_EXECUTELONGFUNCTION
                    ))
        {
            InterlockedDecrement (&g_lWorkerThreads);
            FREE (pEapolBuffer);
            dwRetCode = GetLastError();
            TRACE1 (PORT, "ElReadCompletionRoutine: Critical error: QueueUserWorkItem failed with error %ld",
                    dwRetCode);
            break;
        }
        else
        {
            //TRACE1 (PORT, "ElReadCompletionRoutine: QueueUserWorkItem work item queued for port %p",
                    //pPCB);

            // The received packet has still not been processed. 
            // The ref count cannot be decrement, yet
            
            return;
        }

    } while (FALSE);

    TRACE2 (PORT, "ElReadCompletionRoutine: pPCB= %p, RefCnt = %ld", 
            pPCB, pPCB->dwRefCount);

    // Decrement refcount for error cases

    EAPOL_DEREFERENCE_PORT(pPCB); 
}


//
// ElWriteCompletionRoutine
//
// Description:
//
// This routine is invoked upon completion of an OVERLAPPED write operation
// on an interface on which EAPOL is running.
//
//
// Arguments:
//      dwError - Win32 status code for the I/O operation
//
//      dwBytesTransferred - number of bytes sent out
// 
//      pEapolBuffer - buffer sent to the WriteFile command 
//
// Notes:
//  The reference count for the write operation is removed.
//

VOID 
CALLBACK
ElWriteCompletionRoutine (
        DWORD           dwError,
        DWORD           dwBytesSent,
        EAPOL_BUFFER    *pEapolBuffer 
        )
{
    PEAPOL_PCB  pPCB = (PEAPOL_PCB)pEapolBuffer->pvContext;

    TRACE2 (DEVICE, "ElWriteCompletionRoutine sent out %d bytes with error %d",
            dwBytesSent, dwError);

    // No need to acquire locks, since PCB existence is guaranteed
    // by reference made when write was posted
    EAPOL_DEREFERENCE_PORT(pPCB);
    TRACE2 (PORT, "ElWriteCompletionRoutine: pPCB= %p, RefCnt = %ld", 
            pPCB, pPCB->dwRefCount);
    FREE(pEapolBuffer);
    return;

    // Free Read/Write buffer area, if it is dynamically allocated
    // We have static Read-write buffer for now
}


//
// ElIoCompletionRoutine
//
// Description:
//
// Callback function defined to BindIoCompletionCallback
// This routine is invoked by the I/O system upon completion of a read/write
// operation
// This routine in turn calls ElReadCompletionRoutine or 
// ElWriteCompletionRoutine depending on what command invoked the 
// I/O operation i.e. ReadFile or WriteFile
// 
// Input arguments:
//      dwError - system-supplied error code
//      dwBytesTransferred - system-supplied byte-count
//      lpOverlapped - called-supplied context area
//
// Return values:
//

VOID 
CALLBACK
ElIoCompletionRoutine (
        DWORD           dwError,
        DWORD           dwBytesTransferred,
        LPOVERLAPPED    lpOverlapped
        )
{
    PEAPOL_BUFFER pBuffer = CONTAINING_RECORD (lpOverlapped, EAPOL_BUFFER, Overlapped);

    TRACE1 (DEVICE, "ElIoCompletionRoutine called, %ld bytes xferred",
            dwBytesTransferred);

    pBuffer->dwErrorCode = dwError;
    pBuffer->dwBytesTransferred = dwBytesTransferred;
    pBuffer->CompletionRoutine (
            pBuffer->dwErrorCode,
            pBuffer->dwBytesTransferred,
            pBuffer
            );

    return;

} 


//
// ElReadFromPort
//
// Description:
//
// Function to read EAPOL packets from a port
// 
// Arguments: 
//      pPCB - Pointer to PCB for port on which read is to be performed
//      pBuffer - unused
//      dwBufferLength - unused
//
// Return values:
//
// Locks:
//  pPCB->rw_Lock should be acquired before calling this function
//  

DWORD
ElReadFromPort (
        IN PEAPOL_PCB       pPCB,
        IN PCHAR            pBuffer,
        IN DWORD            dwBufferLength
        )
{
    PEAPOL_BUFFER   pEapolBuffer;
    DWORD           dwRetCode = NO_ERROR;

    TRACE0 (PORT, "ElReadFromPort entered");

    // Allocate Context buffer

    if ((pEapolBuffer = (PEAPOL_BUFFER) MALLOC (sizeof(EAPOL_BUFFER))) == NULL)
    {
        TRACE0 (PORT, "ElReadFromPort: Error in memory allocation");
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    // Initialize Context data used in Overlapped operations
    pEapolBuffer->pvContext = (PVOID)pPCB;
    pEapolBuffer->CompletionRoutine = ElReadCompletionRoutine;

    // Make a reference to the port
    // this reference is released in the completion routine
    if (!EAPOL_REFERENCE_PORT(pPCB))
    {
        //RELEASE_WRITE_LOCK (&(g_PCBLock));
        TRACE0 (PORT, "ElReadFromPort: Unable to obtain reference to port");
        FREE (pEapolBuffer);
        return ERROR_CAN_NOT_COMPLETE;
    }

    TRACE2 (DEVICE, "ElReadFromPort: pPCB = %p, RefCnt = %ld",
            pPCB, pPCB->dwRefCount);

    // Read from the NDISUIO interface corresponding to this port
    if ((dwRetCode = ElReadFromInterface(
                    pPCB->hPort,
                    pEapolBuffer,
                    MAX_PACKET_SIZE - SIZE_ETHERNET_CRC 
                            // read the maximum data possible
                    )) != NO_ERROR)
    {
        TRACE1 (DEVICE, "ElReadFromPort: Error in ElReadFromInterface = %d",
                dwRetCode);

        FREE(pEapolBuffer);
    
        // Decrement refcount just incremented, since it will not be
        // decremented in ReadCompletionRoutine which is not called 

        EAPOL_DEREFERENCE_PORT(pPCB); 
        TRACE2 (PORT, "ElReadFromPort: pPCB= %p, RefCnt = %ld", 
                pPCB, pPCB->dwRefCount);

    }

    return dwRetCode;

} 


//
// ElWriteToPort
//
// Description:
//
// Function to write EAPOL packets to a port
// 
// Input arguments: 
//      pPCB - Pointer to PCB for port on which write is to be performed
//      pBuffer - Pointer to data to be sent out
//      dwBufferLength - Number of bytes to be sent out
//
// Return values:
//  
// Locks:
//  pPCB->rw_Lock should be acquired before calling this function
//  

DWORD
ElWriteToPort (
        IN PEAPOL_PCB       pPCB,
        IN PCHAR            pBuffer,
        IN DWORD            dwBufferLength
        )
{
    PEAPOL_BUFFER   pEapolBuffer;
    PETH_HEADER     pEthHeader;
    DWORD           dwTotalBytes = 0;
    DWORD           dwRetCode = NO_ERROR;


    TRACE1 (PORT, "ElWriteToPort entered: Pkt Length = %ld", dwBufferLength);

    if ((pEapolBuffer = (PEAPOL_BUFFER) MALLOC (sizeof(EAPOL_BUFFER))) == NULL)
    {
        TRACE0 (PORT, "ElWriteToPort: Error in memory allocation");
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    // Initialize Context data used in Overlapped operations
    pEapolBuffer->pvContext = (PVOID)pPCB;
    pEapolBuffer->CompletionRoutine = ElWriteCompletionRoutine;

    pEthHeader = (PETH_HEADER)pEapolBuffer->pBuffer;

    // Copy the source MAC address and the destination MAC address
    memcpy ((PBYTE)pEthHeader->bDstAddr, 
            (PBYTE)pPCB->bDestMacAddr, 
            SIZE_MAC_ADDR);
    memcpy ((PBYTE)pEthHeader->bSrcAddr, 
            (PBYTE)pPCB->bSrcMacAddr, 
            SIZE_MAC_ADDR);
    
    // Validate packet length
    if ((dwBufferLength + sizeof(ETH_HEADER)) > 
            (MAX_PACKET_SIZE - SIZE_ETHERNET_CRC))
    {
        TRACE2 (PORT, "ElWriteToPort: Packetsize %d greater than maximum allowed",
                dwBufferLength, 
                (MAX_PACKET_SIZE - SIZE_ETHERNET_CRC - sizeof(ETH_HEADER)));
        FREE (pEapolBuffer);
        return ERROR_BAD_LENGTH;
    }

    // Copy the EAPOL packet and body
    if (pBuffer != NULL)
    {
        memcpy ((PBYTE)((PBYTE)pEapolBuffer->pBuffer+sizeof(ETH_HEADER)),
                (PBYTE)pBuffer, 
                dwBufferLength);
    }

    dwTotalBytes = dwBufferLength + sizeof(ETH_HEADER);

    ElParsePacket (pEapolBuffer->pBuffer, dwTotalBytes,
            FALSE);

    // Buffer will be released by calling function
        
    // Write to the NDISUIO interface corresponding to this port
    
    // Make a reference to the port
    // this reference is released in the completion routine
    if (!EAPOL_REFERENCE_PORT(pPCB))
    {
        TRACE0 (PORT, "ElWriteToPort: Unable to obtain reference to port");
        FREE (pEapolBuffer);
        return ERROR_CAN_NOT_COMPLETE;
    }
    else
    {
        TRACE2 (DEVICE, "ElWriteToPort: pPCB = %p, RefCnt = %ld",
            pPCB, pPCB->dwRefCount);

        if ((dwRetCode = ElWriteToInterface (
                    pPCB->hPort,
                    pEapolBuffer,
                    dwTotalBytes
                )) != NO_ERROR)
        {
            FREE (pEapolBuffer);
            TRACE1 (PORT, "ElWriteToPort: Error %d", dwRetCode);

            // Decrement refcount incremented in this function, 
            // since it will not be decremented in WriteCompletionRoutine 
            // as it will never be called.

            EAPOL_DEREFERENCE_PORT(pPCB); 
            TRACE2 (PORT, "ElWriteToPort: pPCB= %p, RefCnt = %ld", 
                    pPCB, pPCB->dwRefCount);

            return dwRetCode;
        }
    }

    //TRACE1 (PORT, "ElWriteToPort completed, dwRetCode =%d", dwRetCode);
    return dwRetCode;

}