/*
 * Copyright (c) 2014 VMware, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "precomp.h"

#include "Datapath.h"
#include "Event.h"
#include "Gre.h"
#include "IpHelper.h"
#include "Jhash.h"
#include "Oid.h"
#include "Stt.h"
#include "Switch.h"
#include "User.h"
#include "Vport.h"
#include "Vxlan.h"
#include "Geneve.h"

#ifdef OVS_DBG_MOD
#undef OVS_DBG_MOD
#endif
#define OVS_DBG_MOD OVS_DBG_VPORT
#include "Debug.h"

#define VPORT_NIC_ENTER(_nic) \
    OVS_LOG_TRACE("Enter: PortId: %x, NicIndex: %d", _nic->PortId, \
                                                     _nic->NicIndex)

#define VPORT_NIC_EXIT(_nic) \
    OVS_LOG_TRACE("Exit: PortId: %x, NicIndex: %d", _nic->PortId, \
                                                    _nic->NicIndex)

#define VPORT_PORT_ENTER(_port) \
    OVS_LOG_TRACE("Enter: PortId: %x", _port->PortId)

#define VPORT_PORT_EXIT(_port) \
    OVS_LOG_TRACE("Exit: PortId: %x", _port->PortId)

#define OVS_VPORT_DEFAULT_WAIT_TIME_MICROSEC    100

/* Context structure used to pass back and forth information to the tunnel
 * filter threads. */
typedef struct _OVS_TUNFLT_INIT_CONTEXT {
    POVS_SWITCH_CONTEXT switchContext;
    UINT32 outputLength;
    PVOID outputBuffer;
    PVOID inputBuffer;
    POVS_VPORT_ENTRY vport;
    BOOLEAN hvSwitchPort;
    BOOLEAN hvDelete;
    BOOLEAN ovsDelete;
} OVS_TUNFLT_INIT_CONTEXT, *POVS_TUNFLT_INIT_CONTEXT;


extern POVS_SWITCH_CONTEXT gOvsSwitchContext;

static VOID OvsInitVportWithPortParam(POVS_VPORT_ENTRY vport,
                PNDIS_SWITCH_PORT_PARAMETERS portParam);
static VOID OvsInitVportWithNicParam(POVS_SWITCH_CONTEXT switchContext,
                POVS_VPORT_ENTRY vport, PNDIS_SWITCH_NIC_PARAMETERS nicParam);
static VOID OvsCopyPortParamsFromVport(POVS_VPORT_ENTRY vport,
                                       PNDIS_SWITCH_PORT_PARAMETERS portParam);
static __inline VOID OvsWaitActivate(POVS_SWITCH_CONTEXT switchContext,
                                     ULONG sleepMicroSec);
static NTSTATUS OvsGetExtInfoIoctl(POVS_VPORT_GET vportGet,
                                   POVS_VPORT_EXT_INFO extInfo);
static NTSTATUS CreateNetlinkMesgForNetdev(POVS_VPORT_EXT_INFO info,
                                           POVS_MESSAGE msgIn,
                                           PVOID outBuffer,
                                           UINT32 outBufLen,
                                           int dpIfIndex);
static VOID UpdateSwitchCtxWithVport(POVS_SWITCH_CONTEXT switchContext,
                                     POVS_VPORT_ENTRY vport, BOOLEAN newPort);
static NTSTATUS OvsRemoveTunnelVport(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
                                     POVS_SWITCH_CONTEXT switchContext,
                                     POVS_VPORT_ENTRY vport,
                                     BOOLEAN hvDelete,
                                     BOOLEAN ovsDelete);
static VOID OvsTunnelVportPendingInit(PVOID context,
                                      NTSTATUS status,
                                      UINT32 *replyLen);
static VOID OvsTunnelVportPendingRemove(PVOID context,
                                        NTSTATUS status,
                                        UINT32 *replyLen);
static NTSTATUS GetNICAlias(PNDIS_SWITCH_NIC_PARAMETERS nicParam,
                            IF_COUNTED_STRING *portFriendlyName);
static NTSTATUS OvsConvertIfCountedStrToAnsiStr(PIF_COUNTED_STRING wStr,
                                                CHAR *str,
                                                UINT16 maxStrLen);
_Requires_lock_held_(switchContext->dispatchLock)
static VOID OvsBindVportWithIpHelper(POVS_VPORT_ENTRY vport,
                                     POVS_SWITCH_CONTEXT switchContext);
_Requires_lock_held_(switchContext->dispatchLock)
static VOID OvsUnBindVportWithIpHelper(POVS_VPORT_ENTRY vport,
                                       POVS_SWITCH_CONTEXT switchContext);

/*
 * --------------------------------------------------------------------------
 *  Creates a Vport entry for a Hyper-V switch port. 'nicIndex' is typically
 *  associated with a NIC than a port. We use it here for the special case
 *  where we need to create a Vport for an external NIC with NicIndex > 0.
 * --------------------------------------------------------------------------
 */
NDIS_STATUS
HvCreatePort(POVS_SWITCH_CONTEXT switchContext,
             PNDIS_SWITCH_PORT_PARAMETERS portParam,
             NDIS_SWITCH_NIC_INDEX nicIndex)
{
    POVS_VPORT_ENTRY vport;
    LOCK_STATE_EX lockState;
    NDIS_STATUS status = NDIS_STATUS_SUCCESS;
    BOOLEAN newPort = FALSE;

    VPORT_PORT_ENTER(portParam);

    NdisAcquireRWLockWrite(switchContext->dispatchLock, &lockState, 0);
    /* Lookup by port ID. */
    vport = OvsFindVportByPortIdAndNicIndex(switchContext,
                                            portParam->PortId, nicIndex);
    if (vport != NULL) {
        OVS_LOG_ERROR("Port add failed due to duplicate port name, "
                      "port Id: %u", portParam->PortId);
        status = STATUS_DATA_NOT_ACCEPTED;
        goto create_port_done;
    }

    /*
     * Lookup by port name to see if this port with this name had been added
     * (and deleted) previously.
     */
    vport = OvsFindVportByHvNameW(switchContext,
                                  portParam->PortFriendlyName.String,
                                  portParam->PortFriendlyName.Length);
    if (vport && vport->isAbsentOnHv == FALSE) {
        OVS_LOG_ERROR("Port add failed since a port already exists on "
                      "the specified port Id: %u, ovsName: %s",
                      portParam->PortId, vport->ovsName);
        status = STATUS_DATA_NOT_ACCEPTED;
        goto create_port_done;
    }

    if (vport != NULL) {
        ASSERT(vport->isAbsentOnHv);
        ASSERT(vport->portNo != OVS_DPPORT_NUMBER_INVALID);

        /*
         * It should be possible to simply just mark this port as "not deleted"
         * given that the port Id and the name are the same and also provided
         * that the other properties that we cache have not changed.
         */
        if (vport->portType != portParam->PortType) {
            OVS_LOG_INFO("Port add failed due to PortType change, port Id: %u"
                         " old: %u, new: %u", portParam->PortId,
                         vport->portType, portParam->PortType);
            status = STATUS_DATA_NOT_ACCEPTED;
            goto create_port_done;
        }
        vport->isAbsentOnHv = FALSE;
    } else {
        vport = (POVS_VPORT_ENTRY)OvsAllocateVport();
        if (vport == NULL) {
            status = NDIS_STATUS_RESOURCES;
            goto create_port_done;
        }
        newPort = TRUE;
    }
    OvsInitVportWithPortParam(vport, portParam);
    vport->nicIndex = nicIndex;
    UpdateSwitchCtxWithVport(switchContext, vport, newPort);

create_port_done:
    NdisReleaseRWLock(switchContext->dispatchLock, &lockState);
    VPORT_PORT_EXIT(portParam);
    return status;
}


/*
 * --------------------------------------------------------------------------
 * Function to process updates to a port on the Hyper-Vs witch.
 * --------------------------------------------------------------------------
 */
NDIS_STATUS
HvUpdatePort(POVS_SWITCH_CONTEXT switchContext,
             PNDIS_SWITCH_PORT_PARAMETERS portParam)
{
    POVS_VPORT_ENTRY vport;
    LOCK_STATE_EX lockState;
    OVS_VPORT_STATE ovsState;
    NDIS_SWITCH_NIC_STATE nicState;

    VPORT_PORT_ENTER(portParam);

    NdisAcquireRWLockWrite(switchContext->dispatchLock, &lockState, 0);
    vport = OvsFindVportByPortIdAndNicIndex(switchContext,
                                            portParam->PortId, 0);
    /*
     * Update properties only for NETDEV ports for supprting PS script
     */
    if (vport == NULL) {
        goto update_port_done;
    }

    /* Store the nic and the OVS states as Nic Create won't be called */
    ovsState = vport->ovsState;
    nicState = vport->nicState;

    /*
     * Currently only the port friendly name is being updated
     * Make sure that no other properties are changed
     */
    ASSERT(portParam->PortId == vport->portId);
    ASSERT(portParam->PortState == vport->portState);
    ASSERT(portParam->PortType == vport->portType);

    /*
     * Call the set parameters function the handle all properties
     * change in a single place in case future version supports change of
     * other properties
     */
    OvsInitVportWithPortParam(vport, portParam);
    /* Retore the nic and OVS states */
    vport->nicState = nicState;
    vport->ovsState = ovsState;

update_port_done:
    NdisReleaseRWLock(switchContext->dispatchLock, &lockState);
    VPORT_PORT_EXIT(portParam);

    /* Must always return success */
    return NDIS_STATUS_SUCCESS;
}


/*
 * --------------------------------------------------------------------------
 * Function to process teardown of a port on the Hyper-V switch.
 * --------------------------------------------------------------------------
 */
VOID
HvTeardownPort(POVS_SWITCH_CONTEXT switchContext,
               PNDIS_SWITCH_PORT_PARAMETERS portParam)
{
    POVS_VPORT_ENTRY vport;
    LOCK_STATE_EX lockState;

    VPORT_PORT_ENTER(portParam);

    NdisAcquireRWLockWrite(switchContext->dispatchLock, &lockState, 0);
    vport = OvsFindVportByPortIdAndNicIndex(switchContext,
                                            portParam->PortId, 0);
    if (vport) {
        /* add assertion here */
        vport->portState = NdisSwitchPortStateTeardown;
        vport->ovsState = OVS_STATE_PORT_TEAR_DOWN;
    } else {
        OVS_LOG_WARN("Vport not present.");
    }
    NdisReleaseRWLock(switchContext->dispatchLock, &lockState);

    VPORT_PORT_EXIT(portParam);
}

/*
 * --------------------------------------------------------------------------
 * Function to process deletion of a port on the Hyper-V switch.
 * --------------------------------------------------------------------------
 */
VOID
HvDeletePort(POVS_SWITCH_CONTEXT switchContext,
             PNDIS_SWITCH_PORT_PARAMETERS portParams)
{
    POVS_VPORT_ENTRY vport;
    LOCK_STATE_EX lockState;

    VPORT_PORT_ENTER(portParams);

    NdisAcquireRWLockWrite(switchContext->dispatchLock, &lockState, 0);
    vport = OvsFindVportByPortIdAndNicIndex(switchContext,
                                            portParams->PortId, 0);

    /*
     * XXX: we can only destroy and remove the port if its datapath port
     * counterpart was deleted. If the datapath port counterpart is present,
     * we only mark the vport for deletion, so that a netlink command vport
     * delete will delete the vport.
    */
    if (vport) {
        OVS_VPORT_EVENT_ENTRY event;

        event.portNo = vport->portNo;
        event.ovsType = vport->ovsType;
        event.upcallPid = vport->upcallPid;
        RtlCopyMemory(&event.ovsName, &vport->ovsName, sizeof event.ovsName);
        event.type = OVS_EVENT_LINK_DOWN;
        OvsRemoveAndDeleteVport(NULL, switchContext, vport, TRUE, FALSE);
        OvsPostVportEvent(&event);
    } else {
        OVS_LOG_WARN("Vport not present.");
    }
    NdisReleaseRWLock(switchContext->dispatchLock, &lockState);

    VPORT_PORT_EXIT(portParams);
}


/*
 * --------------------------------------------------------------------------
 * Function to process addition of a NIC connection on the Hyper-V switch.
 * XXX: Posting an event to DPIF is incorrect here. However, it might be useful
 * to post an event to netdev-windows.c.
 * --------------------------------------------------------------------------
 */
NDIS_STATUS
HvCreateNic(POVS_SWITCH_CONTEXT switchContext,
            PNDIS_SWITCH_NIC_PARAMETERS nicParam)
{
    POVS_VPORT_ENTRY vport;
    NDIS_STATUS status = NDIS_STATUS_SUCCESS;
    IF_COUNTED_STRING portFriendlyName = {0};
    LOCK_STATE_EX lockState;

    VPORT_NIC_ENTER(nicParam);

    /* Wait for lists to be initialized. */
    OvsWaitActivate(switchContext, OVS_VPORT_DEFAULT_WAIT_TIME_MICROSEC);

    if (!switchContext->isActivated) {
        OVS_LOG_WARN("Switch is not activated yet.");
        /* Veto the creation of nic */
        status = NDIS_STATUS_NOT_SUPPORTED;
        goto done;
    }

    if (OvsIsInternalNIC(nicParam->NicType) ||
        OvsIsRealExternalNIC(nicParam->NicType, nicParam->NicIndex)) {
        GetNICAlias(nicParam, &portFriendlyName);
    }

    NdisAcquireRWLockWrite(switchContext->dispatchLock, &lockState, 0);
    /*
     * There can be one or more NICs for the external port. We create a vport
     * structure for each such NIC, and each NIC inherits a lot of properties
     * from the parent external port.
     */
    if (OvsIsRealExternalNIC(nicParam->NicType, nicParam->NicIndex)) {
        /* The VPORT can be bound to OVS datapath already. Search for it
         * using its friendly name and if not found allocate a new port
         */
        ASSERT(OvsFindVportByPortIdAndNicIndex(switchContext,
                                               nicParam->PortId,
                                               nicParam->NicIndex) == NULL);
        char convertString[256];
        RtlZeroMemory(convertString, 256);
        NdisReleaseRWLock(switchContext->dispatchLock, &lockState);
        status = OvsConvertIfCountedStrToAnsiStr(&portFriendlyName,
                                                 convertString,
                                                 OVS_MAX_PORT_NAME_LENGTH);
        NdisAcquireRWLockWrite(switchContext->dispatchLock, &lockState, 0);
        if (status != NDIS_STATUS_SUCCESS) {
            goto add_nic_done;
        }
        POVS_VPORT_ENTRY ovsVport = OvsFindVportByOvsName(switchContext,
                                                          convertString);
        if (ovsVport != NULL) {
            UpdateSwitchCtxWithVport(switchContext, ovsVport, FALSE);
        } else {
            NDIS_SWITCH_PORT_PARAMETERS portParam;
            POVS_VPORT_ENTRY virtExtVport =
                (POVS_VPORT_ENTRY)switchContext->virtualExternalVport;

            ASSERT(virtExtVport);
            OvsCopyPortParamsFromVport(virtExtVport, &portParam);
            NdisReleaseRWLock(switchContext->dispatchLock, &lockState);
            status = HvCreatePort(switchContext, &portParam,
                                  nicParam->NicIndex);
            NdisAcquireRWLockWrite(switchContext->dispatchLock, &lockState, 0);
            if (status != NDIS_STATUS_SUCCESS) {
                goto add_nic_done;
            }
        }
    }

    vport = OvsFindVportByPortIdAndNicIndex(switchContext, nicParam->PortId,
                                            nicParam->NicIndex);

    if (vport == NULL) {
        OVS_LOG_ERROR("Create NIC without Switch Port,"
                      " PortId: %x, NicIndex: %d",
                      nicParam->PortId, nicParam->NicIndex);
        status = NDIS_STATUS_INVALID_PARAMETER;
        goto add_nic_done;
    }
    OvsInitVportWithNicParam(switchContext, vport, nicParam);
    if (OvsIsInternalNIC(nicParam->NicType) ||
        OvsIsRealExternalNIC(nicParam->NicType, nicParam->NicIndex)) {
        RtlCopyMemory(&vport->portFriendlyName, &portFriendlyName,
                      sizeof portFriendlyName);
    }

add_nic_done:
    NdisReleaseRWLock(switchContext->dispatchLock, &lockState);

done:
    VPORT_NIC_EXIT(nicParam);
    OVS_LOG_TRACE("Exit: status %8x.\n", status);

    return status;
}

/*
 * --------------------------------------------------------------------------
 * Function to process connection event of a NIC on the Hyper-V switch.
 * --------------------------------------------------------------------------
 */
VOID
HvConnectNic(POVS_SWITCH_CONTEXT switchContext,
             PNDIS_SWITCH_NIC_PARAMETERS nicParam)
{
    LOCK_STATE_EX lockState;
    POVS_VPORT_ENTRY vport;

    VPORT_NIC_ENTER(nicParam);

    /* Wait for lists to be initialized. */
    OvsWaitActivate(switchContext, OVS_VPORT_DEFAULT_WAIT_TIME_MICROSEC);

    if (!switchContext->isActivated) {
        OVS_LOG_WARN("Switch is not activated yet.");
        goto done;
    }

    NdisAcquireRWLockWrite(switchContext->dispatchLock, &lockState, 0);
    vport = OvsFindVportByPortIdAndNicIndex(switchContext,
                                            nicParam->PortId,
                                            nicParam->NicIndex);

    if (!vport) {
        OVS_LOG_WARN("Vport not present.");
        NdisReleaseRWLock(switchContext->dispatchLock, &lockState);
        ASSERT(0);
        goto done;
    }

    vport->ovsState = OVS_STATE_CONNECTED;
    vport->nicState = NdisSwitchNicStateConnected;

    if (nicParam->NicType == NdisSwitchNicTypeInternal) {
        OvsBindVportWithIpHelper(vport, switchContext);
    }

    NdisReleaseRWLock(switchContext->dispatchLock, &lockState);
done:
    VPORT_NIC_EXIT(nicParam);
}


/*
 * --------------------------------------------------------------------------
 * Function to process updates to a NIC on the Hyper-V switch.
 * --------------------------------------------------------------------------
 */
VOID
HvUpdateNic(POVS_SWITCH_CONTEXT switchContext,
            PNDIS_SWITCH_NIC_PARAMETERS nicParam)
{
    POVS_VPORT_ENTRY vport;
    LOCK_STATE_EX lockState;
    UINT32 event = 0;
    IF_COUNTED_STRING portFriendlyName = {0};
    BOOLEAN nameChanged = FALSE;
    BOOLEAN aliasLookup = FALSE;

    VPORT_NIC_ENTER(nicParam);

    /* Wait for lists to be initialized. */
    OvsWaitActivate(switchContext, OVS_VPORT_DEFAULT_WAIT_TIME_MICROSEC);

    if (!switchContext->isActivated) {
        OVS_LOG_WARN("Switch is not activated yet.");
        goto update_nic_done;
    }

    /* GetNICAlias() must be called outside of a lock. */
    if (nicParam->NicType == NdisSwitchNicTypeInternal ||
        OvsIsRealExternalNIC(nicParam->NicType, nicParam->NicIndex)) {
        GetNICAlias(nicParam, &portFriendlyName);
        aliasLookup = TRUE;
    }

    NdisAcquireRWLockWrite(switchContext->dispatchLock, &lockState, 0);
    vport = OvsFindVportByPortIdAndNicIndex(switchContext,
                                            nicParam->PortId,
                                            nicParam->NicIndex);
    if (vport == NULL) {
        NdisReleaseRWLock(switchContext->dispatchLock, &lockState);
        OVS_LOG_WARN("Vport search failed.");
        goto update_nic_done;
    }
    switch (nicParam->NicType) {
    case NdisSwitchNicTypeExternal:
    case NdisSwitchNicTypeInternal:
        RtlCopyMemory(&vport->netCfgInstanceId, &nicParam->NetCfgInstanceId,
                      sizeof (GUID));
        if (aliasLookup) {
            if (RtlCompareMemory(&vport->portFriendlyName,
                    &portFriendlyName, vport->portFriendlyName.Length) !=
                    vport->portFriendlyName.Length) {
                RtlCopyMemory(&vport->portFriendlyName, &portFriendlyName,
                    sizeof portFriendlyName);
                nameChanged = TRUE;
            }
        }
        break;
    case NdisSwitchNicTypeSynthetic:
    case NdisSwitchNicTypeEmulated:
        if (!RtlEqualMemory(vport->vmMacAddress, nicParam->VMMacAddress,
                           sizeof (vport->vmMacAddress))) {
            event |= OVS_EVENT_MAC_CHANGE;
            RtlCopyMemory(vport->vmMacAddress, nicParam->VMMacAddress,
                          sizeof (vport->vmMacAddress));
        }
        break;
    default:
        ASSERT(0);
    }
    if (!RtlEqualMemory(vport->permMacAddress, nicParam->PermanentMacAddress,
                        sizeof (vport->permMacAddress))) {
        RtlCopyMemory(vport->permMacAddress, nicParam->PermanentMacAddress,
                      sizeof (vport->permMacAddress));
        event |= OVS_EVENT_MAC_CHANGE;
    }
    if (!RtlEqualMemory(vport->currMacAddress, nicParam->CurrentMacAddress,
                        sizeof (vport->currMacAddress))) {
        RtlCopyMemory(vport->currMacAddress, nicParam->CurrentMacAddress,
                      sizeof (vport->currMacAddress));
        event |= OVS_EVENT_MAC_CHANGE;
    }

    if (vport->mtu != nicParam->MTU) {
        vport->mtu = nicParam->MTU;
        event |= OVS_EVENT_MTU_CHANGE;
    }
    vport->numaNodeId = nicParam->NumaNodeId;

    if (nameChanged) {
        OVS_VPORT_EVENT_ENTRY evt;
        evt.portNo = vport->portNo;
        evt.ovsType = vport->ovsType;
        evt.upcallPid = vport->upcallPid;
        RtlCopyMemory(&evt.ovsName, &vport->ovsName, sizeof evt.ovsName);
        evt.type = OVS_EVENT_LINK_DOWN;
        OvsRemoveAndDeleteVport(NULL, switchContext, vport, FALSE, TRUE);
        OvsPostVportEvent(&evt);
    }

    NdisReleaseRWLock(switchContext->dispatchLock, &lockState);

    /*
     * XXX: Not sure what kind of event to post here. DPIF is not interested in
     * changes to MAC address. Netdev-windows might be intrested, though.
     * That said, if the name chagnes, not clear what kind of event to be
     * posted. We might have to delete the vport, and have userspace recreate
     * it.
     */

update_nic_done:
    VPORT_NIC_EXIT(nicParam);
}

/*
 * --------------------------------------------------------------------------
 * Function to process disconnect event of a NIC on the Hyper-V switch.
 * --------------------------------------------------------------------------
 */
VOID
HvDisconnectNic(POVS_SWITCH_CONTEXT switchContext,
                PNDIS_SWITCH_NIC_PARAMETERS nicParam)
{
    POVS_VPORT_ENTRY vport;
    LOCK_STATE_EX lockState;
    BOOLEAN isInternalPort = FALSE;
    OVS_VPORT_EVENT_ENTRY event;

    VPORT_NIC_ENTER(nicParam);

    /* Wait for lists to be initialized. */
    OvsWaitActivate(switchContext, OVS_VPORT_DEFAULT_WAIT_TIME_MICROSEC);

    if (!switchContext->isActivated) {
        OVS_LOG_WARN("Switch is not activated yet.");
        goto done;
    }

    NdisAcquireRWLockWrite(switchContext->dispatchLock, &lockState, 0);
    vport = OvsFindVportByPortIdAndNicIndex(switchContext,
                                            nicParam->PortId,
                                            nicParam->NicIndex);

    if (!vport) {
        OVS_LOG_WARN("Vport not present.");
        NdisReleaseRWLock(switchContext->dispatchLock, &lockState);
        goto done;
    }

    vport->nicState = NdisSwitchNicStateDisconnected;
    vport->ovsState = OVS_STATE_NIC_CREATED;

    if (vport->ovsType == OVS_VPORT_TYPE_INTERNAL) {
        isInternalPort = TRUE;
    }

    event.portNo = vport->portNo;
    event.ovsType = vport->ovsType;
    event.upcallPid = vport->upcallPid;
    RtlCopyMemory(&event.ovsName, &vport->ovsName, sizeof event.ovsName);
    event.type = OVS_EVENT_LINK_DOWN;
    OvsPostVportEvent(&event);

    /*
     * Delete the port from the hash tables accessible to userspace. After this
     * point, userspace should not be able to access this port.
     */
    if (OvsIsRealExternalVport(vport)) {
        OvsRemoveAndDeleteVport(NULL, switchContext, vport, FALSE, TRUE);
    }

    if (isInternalPort) {
        OvsUnBindVportWithIpHelper(vport, switchContext);
        /*
         * Don't delete the port from the hash tables here for internal port
         * because the internal port cannot be recreated in HvCreateNic(). It
         * only can be created in HvCreatePort() by issuing
         * OID_SWITCH_PORT_CREATE. We should wait extensible switch interface
         * to issue OID_SWITCH_PORT_TEARDOWN and OID_SWITCH_PORT_DELETE to
         * delete the internal port.
         */
    }
    NdisReleaseRWLock(switchContext->dispatchLock, &lockState);

done:
    VPORT_NIC_EXIT(nicParam);
}

/*
 * --------------------------------------------------------------------------
 * Function to process delete event of a NIC on the Hyper-V switch.
 * --------------------------------------------------------------------------
 */
VOID
HvDeleteNic(POVS_SWITCH_CONTEXT switchContext,
            PNDIS_SWITCH_NIC_PARAMETERS nicParam)
{
    LOCK_STATE_EX lockState;
    POVS_VPORT_ENTRY vport;

    VPORT_NIC_ENTER(nicParam);
    /* Wait for lists to be initialized. */
    OvsWaitActivate(switchContext, OVS_VPORT_DEFAULT_WAIT_TIME_MICROSEC);

    if (!switchContext->isActivated) {
        OVS_LOG_WARN("Switch is not activated yet.");
        goto done;
    }

    NdisAcquireRWLockWrite(switchContext->dispatchLock, &lockState, 0);
    vport = OvsFindVportByPortIdAndNicIndex(switchContext,
                                            nicParam->PortId,
                                            nicParam->NicIndex);

    if (!vport) {
        OVS_LOG_WARN("Vport not present.");
        NdisReleaseRWLock(switchContext->dispatchLock, &lockState);
        goto done;
    }

    vport->nicState = NdisSwitchNicStateUnknown;
    vport->ovsState = OVS_STATE_PORT_CREATED;

    if (OvsIsRealExternalVport(vport)) {
        /* This vport was created in HvCreateNic(). */
        OvsRemoveAndDeleteVport(NULL, switchContext, vport, TRUE, FALSE);
    }

    NdisReleaseRWLock(switchContext->dispatchLock, &lockState);

done:
    VPORT_NIC_EXIT(nicParam);
}

/*
 * OVS Vport related functionality.
 */
_Use_decl_annotations_
POVS_VPORT_ENTRY
OvsFindVportByPortNo(POVS_SWITCH_CONTEXT switchContext,
                     UINT32 portNo)
{
    POVS_VPORT_ENTRY vport;
    PLIST_ENTRY head, link;
    UINT32 hash = OvsJhashBytes((const VOID *)&portNo, sizeof(portNo),
                                OVS_HASH_BASIS);
    head = &(switchContext->portNoHashArray[hash & OVS_VPORT_MASK]);
    LIST_FORALL(head, link) {
        vport = CONTAINING_RECORD(link, OVS_VPORT_ENTRY, portNoLink);
        if (vport->portNo == portNo) {
            return vport;
        }
    }
    return NULL;
}


POVS_VPORT_ENTRY
OvsFindTunnelVportByDstPortAndType(POVS_SWITCH_CONTEXT switchContext,
                                   UINT16 dstPort,
                                   OVS_VPORT_TYPE ovsPortType)
{
    POVS_VPORT_ENTRY vport;
    PLIST_ENTRY head, link;
    UINT32 hash = OvsJhashBytes((const VOID *)&dstPort, sizeof(dstPort),
                                OVS_HASH_BASIS);
    head = &(switchContext->tunnelVportsArray[hash & OVS_VPORT_MASK]);
    LIST_FORALL(head, link) {
        vport = CONTAINING_RECORD(link, OVS_VPORT_ENTRY, tunnelVportLink);
        if (GetPortFromPriv(vport) == dstPort &&
            vport->ovsType == ovsPortType) {
            return vport;
        }
    }
    return NULL;
}

POVS_VPORT_ENTRY
OvsFindTunnelVportByDstPortAndNWProto(POVS_SWITCH_CONTEXT switchContext,
                                      UINT16 dstPort,
                                      UINT8 nwProto)
{
    POVS_VPORT_ENTRY vport;
    PLIST_ENTRY head, link;
    UINT32 hash = OvsJhashBytes((const VOID *)&dstPort, sizeof(dstPort),
                                OVS_HASH_BASIS);
    head = &(switchContext->tunnelVportsArray[hash & OVS_VPORT_MASK]);
    LIST_FORALL(head, link) {
        vport = CONTAINING_RECORD(link, OVS_VPORT_ENTRY, tunnelVportLink);
        if (GetPortFromPriv(vport) == dstPort) {
            switch (nwProto) {
            case IPPROTO_UDP:
                if (vport->ovsType != OVS_VPORT_TYPE_GENEVE &&
                    vport->ovsType != OVS_VPORT_TYPE_VXLAN) {
                    continue;
                }
                break;
            case IPPROTO_TCP:
                if (vport->ovsType != OVS_VPORT_TYPE_STT) {
                    continue;
                }
                break;
            case IPPROTO_GRE:
                break;
            default:
                continue;
            }
            return vport;
        }
    }
    return NULL;
}

POVS_VPORT_ENTRY
OvsFindTunnelVportByPortType(POVS_SWITCH_CONTEXT switchContext,
                             OVS_VPORT_TYPE ovsPortType)
{
    POVS_VPORT_ENTRY vport;
    PLIST_ENTRY head, link;
    UINT16 dstPort = 0;
    UINT32 hash = OvsJhashBytes((const VOID *)&dstPort, sizeof(dstPort),
                                OVS_HASH_BASIS);
    head = &(switchContext->tunnelVportsArray[hash & OVS_VPORT_MASK]);
    LIST_FORALL(head, link) {
        vport = CONTAINING_RECORD(link, OVS_VPORT_ENTRY, tunnelVportLink);
        if (vport->ovsType == ovsPortType) {
            return vport;
        }
    }
    return NULL;
}

_Use_decl_annotations_
POVS_VPORT_ENTRY
OvsFindVportByOvsName(POVS_SWITCH_CONTEXT switchContext,
                      PSTR name)
{
    POVS_VPORT_ENTRY vport;
    PLIST_ENTRY head, link;
    UINT32 hash;
    SIZE_T length = strlen(name) + 1;

    hash = OvsJhashBytes((const VOID *)name, length, OVS_HASH_BASIS);
    head = &(switchContext->ovsPortNameHashArray[hash & OVS_VPORT_MASK]);

    LIST_FORALL(head, link) {
        vport = CONTAINING_RECORD(link, OVS_VPORT_ENTRY, ovsNameLink);
        if (!strcmp(name, vport->ovsName)) {
            return vport;
        }
    }

    return NULL;
}

/* OvsFindVportByHvName: "name" is assumed to be null-terminated */
_Use_decl_annotations_
POVS_VPORT_ENTRY
OvsFindVportByHvNameW(POVS_SWITCH_CONTEXT switchContext,
                      PWSTR wsName, SIZE_T wstrSize)
{
    POVS_VPORT_ENTRY vport = NULL;
    PLIST_ENTRY head, link;
    UINT i;

    for (i = 0; i < OVS_MAX_VPORT_ARRAY_SIZE; i++) {
        head = &(switchContext->portIdHashArray[i]);
        LIST_FORALL(head, link) {
            vport = CONTAINING_RECORD(link, OVS_VPORT_ENTRY, portIdLink);

            /*
             * NOTE about portFriendlyName:
             * If the string is NULL-terminated, the Length member does not
             * include the terminating NULL character.
             */
            if (vport->portFriendlyName.Length == wstrSize &&
                RtlEqualMemory(wsName, vport->portFriendlyName.String,
                               vport->portFriendlyName.Length)) {
                goto Cleanup;
            }

            vport = NULL;
        }
    }

    /*
     * Look in the list of ports that were added from the Hyper-V switch and
     * deleted.
     */
    if (vport == NULL) {
        for (i = 0; i < OVS_MAX_VPORT_ARRAY_SIZE; i++) {
            head = &(switchContext->portNoHashArray[i]);
            LIST_FORALL(head, link) {
                vport = CONTAINING_RECORD(link, OVS_VPORT_ENTRY, portNoLink);
                if (vport->portFriendlyName.Length == wstrSize &&
                    RtlEqualMemory(wsName, vport->portFriendlyName.String,
                                   vport->portFriendlyName.Length)) {
                    goto Cleanup;
                }

                vport = NULL;
            }
        }
    }

Cleanup:
    return vport;
}

_Use_decl_annotations_
POVS_VPORT_ENTRY
OvsFindVportByHvNameA(POVS_SWITCH_CONTEXT switchContext,
                      PSTR name)
{
    POVS_VPORT_ENTRY vport = NULL;
    /* 'portFriendlyName' is not NUL-terminated. */
    SIZE_T length = strlen(name);
    SIZE_T wstrSize = length * sizeof(WCHAR);
    UINT i;

    PWSTR wsName = OvsAllocateMemoryWithTag(wstrSize, OVS_VPORT_POOL_TAG);
    if (!wsName) {
        return NULL;
    }
    for (i = 0; i < length; i++) {
        wsName[i] = name[i];
    }
    vport = OvsFindVportByHvNameW(switchContext, wsName, wstrSize);
    OvsFreeMemoryWithTag(wsName, OVS_VPORT_POOL_TAG);
    return vport;
}

_Use_decl_annotations_
POVS_VPORT_ENTRY
OvsFindVportByPortIdAndNicIndex(POVS_SWITCH_CONTEXT switchContext,
                                NDIS_SWITCH_PORT_ID portId,
                                NDIS_SWITCH_NIC_INDEX index)
{
    if (switchContext->virtualExternalVport &&
            portId == switchContext->virtualExternalPortId &&
            index == switchContext->virtualExternalVport->nicIndex) {
        return (POVS_VPORT_ENTRY)switchContext->virtualExternalVport;
    } else {
        PLIST_ENTRY head, link;
        POVS_VPORT_ENTRY vport;
        UINT32 hash;
        hash = OvsJhashWords((UINT32 *)&portId, 1, OVS_HASH_BASIS);
        head = &(switchContext->portIdHashArray[hash & OVS_VPORT_MASK]);
        LIST_FORALL(head, link) {
            vport = CONTAINING_RECORD(link, OVS_VPORT_ENTRY, portIdLink);
            if (portId == vport->portId && index == vport->nicIndex) {
                return vport;
            }
        }
        return NULL;
    }
}

BOOLEAN OvsIsExternalVportByPortId(POVS_SWITCH_CONTEXT switchContext,
                                   NDIS_SWITCH_PORT_ID portId)
{
    return (portId == switchContext->virtualExternalPortId);
}

POVS_VPORT_ENTRY
OvsAllocateVport(VOID)
{
    POVS_VPORT_ENTRY vport;
    vport = (POVS_VPORT_ENTRY)OvsAllocateMemoryWithTag(
        sizeof(OVS_VPORT_ENTRY), OVS_VPORT_POOL_TAG);
    if (vport == NULL) {
        return NULL;
    }
    RtlZeroMemory(vport, sizeof (OVS_VPORT_ENTRY));
    vport->ovsState = OVS_STATE_UNKNOWN;
    vport->isAbsentOnHv = FALSE;
    vport->portNo = OVS_DPPORT_NUMBER_INVALID;

    InitializeListHead(&vport->ovsNameLink);
    InitializeListHead(&vport->portIdLink);
    InitializeListHead(&vport->portNoLink);

    return vport;
}

static VOID
OvsInitVportWithPortParam(POVS_VPORT_ENTRY vport,
                          PNDIS_SWITCH_PORT_PARAMETERS portParam)
{
    vport->portType = portParam->PortType;
    vport->portState = portParam->PortState;
    vport->portId = portParam->PortId;
    vport->nicState = NdisSwitchNicStateUnknown;
    vport->isExternal = FALSE;

    switch (vport->portType) {
    case NdisSwitchPortTypeExternal:
        vport->isExternal = TRUE;
        vport->ovsType = OVS_VPORT_TYPE_NETDEV;
        break;
    case NdisSwitchPortTypeInternal:
        vport->ovsType = OVS_VPORT_TYPE_INTERNAL;
        break;
    case NdisSwitchPortTypeSynthetic:
    case NdisSwitchPortTypeEmulated:
        vport->ovsType = OVS_VPORT_TYPE_NETDEV;
        break;
    }
    RtlCopyMemory(&vport->hvPortName, &portParam->PortName,
                  sizeof (NDIS_SWITCH_PORT_NAME));
    /* For external and internal ports, 'portFriendlyName' is overwritten
     * later. */
    RtlCopyMemory(&vport->portFriendlyName, &portParam->PortFriendlyName,
                  sizeof(NDIS_SWITCH_PORT_FRIENDLYNAME));

    switch (vport->portState) {
    case NdisSwitchPortStateCreated:
        vport->ovsState = OVS_STATE_PORT_CREATED;
        break;
    case NdisSwitchPortStateTeardown:
        vport->ovsState = OVS_STATE_PORT_TEAR_DOWN;
        break;
    case NdisSwitchPortStateDeleted:
        vport->ovsState = OVS_STATE_PORT_DELETED;
        break;
    }
}


static VOID
OvsInitVportWithNicParam(POVS_SWITCH_CONTEXT switchContext,
                         POVS_VPORT_ENTRY vport,
                         PNDIS_SWITCH_NIC_PARAMETERS nicParam)
{
    ASSERT(vport->portId == nicParam->PortId);

    UNREFERENCED_PARAMETER(switchContext);

    RtlCopyMemory(vport->permMacAddress, nicParam->PermanentMacAddress,
                  sizeof (vport->permMacAddress));
    RtlCopyMemory(vport->currMacAddress, nicParam->CurrentMacAddress,
                  sizeof (vport->currMacAddress));

    if (nicParam->NicType == NdisSwitchNicTypeSynthetic ||
        nicParam->NicType == NdisSwitchNicTypeEmulated) {
        RtlCopyMemory(vport->vmMacAddress, nicParam->VMMacAddress,
                      sizeof (vport->vmMacAddress));
        RtlCopyMemory(&vport->vmName, &nicParam->VmName,
                      sizeof (nicParam->VmName));
    } else {
        RtlCopyMemory(&vport->netCfgInstanceId, &nicParam->NetCfgInstanceId,
                      sizeof (nicParam->NetCfgInstanceId));
        RtlCopyMemory(&vport->nicFriendlyName, &nicParam->NicFriendlyName,
                      sizeof (nicParam->NicFriendlyName));
    }
    RtlCopyMemory(&vport->nicName, &nicParam->NicName,
                  sizeof (nicParam->NicName));
    vport->mtu = nicParam->MTU;
    vport->nicState = nicParam->NicState;
    vport->nicIndex = nicParam->NicIndex;
    vport->nicType = nicParam->NicType;
    vport->numaNodeId = nicParam->NumaNodeId;

    switch (vport->nicState) {
    case NdisSwitchNicStateCreated:
        vport->ovsState = OVS_STATE_NIC_CREATED;
        break;
    case NdisSwitchNicStateConnected:
        vport->ovsState = OVS_STATE_CONNECTED;
        break;
    case NdisSwitchNicStateDisconnected:
        vport->ovsState = OVS_STATE_NIC_CREATED;
        break;
    case NdisSwitchNicStateDeleted:
        vport->ovsState = OVS_STATE_PORT_CREATED;
        break;
    }
}

/*
 * --------------------------------------------------------------------------
 * Populates 'portParam' based on 'vport'.
 * --------------------------------------------------------------------------
 */
static VOID
OvsCopyPortParamsFromVport(POVS_VPORT_ENTRY vport,
                           PNDIS_SWITCH_PORT_PARAMETERS portParam)
{
    portParam->Flags = 0;
    portParam->PortId = vport->portId;
    RtlCopyMemory(&portParam->PortName, &vport->hvPortName,
                  sizeof (NDIS_SWITCH_PORT_NAME));
    RtlCopyMemory(&portParam->PortFriendlyName,
                  &vport->portFriendlyName,
                  sizeof(NDIS_SWITCH_PORT_FRIENDLYNAME));
    portParam->PortType = vport->portType;
    portParam->IsValidationPort = FALSE;
    portParam->PortState = vport->portState;
}

/*
 * --------------------------------------------------------------------------
 * Initializes a tunnel vport.
 * --------------------------------------------------------------------------
 */
NTSTATUS
OvsInitTunnelVport(PVOID userContext,
                   POVS_VPORT_ENTRY vport,
                   OVS_VPORT_TYPE ovsType,
                   UINT16 dstPort)
{
    NTSTATUS status = STATUS_SUCCESS;
    POVS_USER_PARAMS_CONTEXT usrParamsCtx =
        (POVS_USER_PARAMS_CONTEXT)userContext;

    vport->ovsType = ovsType;
    vport->ovsState = OVS_STATE_PORT_CREATED;
    switch (ovsType) {
    case OVS_VPORT_TYPE_GRE:
        status = OvsInitGreTunnel(vport);
        break;
    case OVS_VPORT_TYPE_VXLAN:
    {
        POVS_TUNFLT_INIT_CONTEXT tunnelContext = NULL;

        tunnelContext = OvsAllocateMemoryWithTag(sizeof(*tunnelContext),
                                                 OVS_VPORT_POOL_TAG);
        if (tunnelContext == NULL) {
            status = STATUS_INSUFFICIENT_RESOURCES;
            break;
        }
        tunnelContext->inputBuffer = usrParamsCtx->inputBuffer;
        tunnelContext->outputBuffer = usrParamsCtx->outputBuffer;
        tunnelContext->outputLength = usrParamsCtx->outputLength;
        tunnelContext->vport = vport;

        status = OvsInitVxlanTunnel(usrParamsCtx->irp,
                                    vport,
                                    dstPort,
                                    OvsTunnelVportPendingInit,
                                    (PVOID)tunnelContext);
        if (status != STATUS_PENDING) {
            OvsFreeMemoryWithTag(tunnelContext, OVS_VPORT_POOL_TAG);
            tunnelContext = NULL;
        }
        break;
    }
    case OVS_VPORT_TYPE_STT:
        status = OvsInitSttTunnel(vport, dstPort);
        break;
    case OVS_VPORT_TYPE_GENEVE:
        status = OvsInitGeneveTunnel(vport, dstPort);
        break;
    default:
        ASSERT(0);
    }
    return status;
}

/*
 * --------------------------------------------------------------------------
 * For external and internal vports 'portFriendlyName' parameter, provided by
  * Hyper-V, is overwritten with the interface alias name and NIC friendly name
  * equivalent.
 * --------------------------------------------------------------------------
 */
static NTSTATUS
GetNICAlias(PNDIS_SWITCH_NIC_PARAMETERS nicParam,
            IF_COUNTED_STRING *portFriendlyName)
{
    NTSTATUS status = STATUS_SUCCESS;
    WCHAR interfaceName[IF_MAX_STRING_SIZE + 1];
    NET_LUID interfaceLuid;
    size_t len;

    if (nicParam->NicType == NdisSwitchNicTypeInternal) {
        RtlCopyMemory(portFriendlyName, &nicParam->NicFriendlyName,
                      sizeof nicParam->NicFriendlyName);
    return status;
    }

    status = ConvertInterfaceGuidToLuid(&nicParam->NetCfgInstanceId,
                                        &interfaceLuid);
    if (status == STATUS_SUCCESS) {
        /*
         * Must be called from PASSIVE_LEVEL. Resulted in a
         * STATUS_INVALID_DEVICE_REQUEST if not.
         */
        status = ConvertInterfaceLuidToAlias(&interfaceLuid, interfaceName,
                                             IF_MAX_STRING_SIZE + 1);
        if (status == STATUS_SUCCESS) {
            RtlStringCbPrintfW(portFriendlyName->String,
                               IF_MAX_STRING_SIZE, L"%s", interfaceName);
            status = RtlStringCbLengthW(portFriendlyName->String,
                                        IF_MAX_STRING_SIZE, &len);
            if (!NT_SUCCESS(status)) {
                OVS_LOG_ERROR("Failed to get the length of the string,"
                              "status: %x", status);
                return status;
            }
            portFriendlyName->Length = (USHORT)len;
        } else {
            OVS_LOG_ERROR("Fail to convert interface LUID to alias, status: %x",
                status);
        }
    } else {
        OVS_LOG_ERROR("Fail to convert interface GUID to LUID, status: %x",
                      status);
    }

    return status;
}


/*
 * --------------------------------------------------------------------------
 * Functionality common to any port on the Hyper-V switch. This function is not
 * to be called for a port that is not on the Hyper-V switch.
 *
 * Inserts the port into 'portIdHashArray' and caches the pointer in the
 * 'switchContext' if needed.
 * --------------------------------------------------------------------------
 */
static VOID
UpdateSwitchCtxWithVport(POVS_SWITCH_CONTEXT switchContext,
                         POVS_VPORT_ENTRY vport,
                         BOOLEAN newPort)
{
    UINT32 hash;

    switch (vport->portType) {
    case NdisSwitchPortTypeExternal:
        if (vport->nicIndex == 0) {
            switchContext->virtualExternalPortId = vport->portId;
            switchContext->virtualExternalVport = vport;
        } else if (newPort == TRUE) {
            switchContext->numPhysicalNics++;
        }
        break;
    case NdisSwitchPortTypeInternal:
        switchContext->countInternalVports++;
        break;
    case NdisSwitchPortTypeSynthetic:
    case NdisSwitchPortTypeEmulated:
        break;
    }

    /*
     * It is important to not insert vport corresponding to virtual external
     * port into the 'portIdHashArray' since the port should not be exposed to
     * OVS userspace.
     */
    if (vport->portType == NdisSwitchPortTypeExternal &&
        vport->nicIndex == 0) {
        return;
    }

    /*
     * NOTE: OvsJhashWords has portId as "1" word. This should be ok, even
     * though sizeof(NDIS_SWITCH_PORT_ID) = 4, not 2, because the
     * hyper-v switch seems to use only 2 bytes out of 4.
     */
    hash = OvsJhashWords(&vport->portId, 1, OVS_HASH_BASIS);
    InsertHeadList(&switchContext->portIdHashArray[hash & OVS_VPORT_MASK],
                   &vport->portIdLink);
    if (newPort) {
        switchContext->numHvVports++;
    }
}

/*
 * --------------------------------------------------------------------------
 * Functionality common to any port added from OVS userspace.
 *
 * Inserts the port into 'portNoHashArray', 'ovsPortNameHashArray' and in
 * 'tunnelVportsArray' if appropriate.
 * --------------------------------------------------------------------------
 */
NDIS_STATUS
InitOvsVportCommon(POVS_SWITCH_CONTEXT switchContext,
                   POVS_VPORT_ENTRY vport)
{
    UINT32 hash;

    switch(vport->ovsType) {
    case OVS_VPORT_TYPE_GRE:
    case OVS_VPORT_TYPE_VXLAN:
    case OVS_VPORT_TYPE_STT:
    case OVS_VPORT_TYPE_GENEVE:
    {
        UINT16 dstPort = GetPortFromPriv(vport);
        hash = OvsJhashBytes(&dstPort,
                             sizeof(dstPort),
                             OVS_HASH_BASIS);
        InsertHeadList(
            &gOvsSwitchContext->tunnelVportsArray[hash & OVS_VPORT_MASK],
            &vport->tunnelVportLink);
        switchContext->numNonHvVports++;
        break;
    }
    default:
        break;
    }

    /*
     * Insert the port into the hash array of ports: by port number and ovs
     * and ovs (datapath) port name.
     * NOTE: OvsJhashWords has portNo as "1" word. This is ok, because the
     * portNo is stored in 2 bytes only (max port number = MAXUINT16).
     */
    hash = OvsJhashWords(&vport->portNo, 1, OVS_HASH_BASIS);
    InsertHeadList(&gOvsSwitchContext->portNoHashArray[hash & OVS_VPORT_MASK],
                   &vport->portNoLink);

    hash = OvsJhashBytes(vport->ovsName, strlen(vport->ovsName) + 1,
                         OVS_HASH_BASIS);
    InsertHeadList(
        &gOvsSwitchContext->ovsPortNameHashArray[hash & OVS_VPORT_MASK],
        &vport->ovsNameLink);

    return STATUS_SUCCESS;
}


/*
 * --------------------------------------------------------------------------
 * Provides functionality that is partly complementatry to
 * InitOvsVportCommon()/UpdateSwitchCtxWithVport().
 *
 * 'hvDelete' indicates if caller is removing the vport as a result of the
 * port being removed on the Hyper-V switch.
 * 'ovsDelete' indicates if caller is removing the vport as a result of the
 * port being removed from OVS userspace.
 * --------------------------------------------------------------------------
 */
NTSTATUS
OvsRemoveAndDeleteVport(PVOID usrParamsContext,
                        POVS_SWITCH_CONTEXT switchContext,
                        POVS_VPORT_ENTRY vport,
                        BOOLEAN hvDelete,
                        BOOLEAN ovsDelete)
{
    POVS_USER_PARAMS_CONTEXT usrParamsCtx =
        (POVS_USER_PARAMS_CONTEXT)usrParamsContext;
    BOOLEAN hvSwitchPort = FALSE;
    BOOLEAN deletedOnOvs = FALSE;
    BOOLEAN deletedOnHv = FALSE;

    switch (vport->ovsType) {
    case OVS_VPORT_TYPE_INTERNAL:
        if (hvDelete && vport->isAbsentOnHv == FALSE) {
            switchContext->countInternalVports--;
            ASSERT(switchContext->countInternalVports >= 0);
            OvsUnBindVportWithIpHelper(vport, switchContext);
        }
        hvSwitchPort = TRUE;
        break;
    case OVS_VPORT_TYPE_VXLAN:
    {
        NTSTATUS status;
        status = OvsRemoveTunnelVport(usrParamsCtx, switchContext, vport,
                                      hvDelete, ovsDelete);
        if (status != STATUS_SUCCESS) {
            return status;
        }
    }
    case OVS_VPORT_TYPE_GENEVE:
        OvsCleanupGeneveTunnel(vport);
        break;
    case OVS_VPORT_TYPE_STT:
        OvsCleanupSttTunnel(vport);
        break;
    case OVS_VPORT_TYPE_GRE:
        OvsCleanupGreTunnel(vport);
        break;
    case OVS_VPORT_TYPE_NETDEV:
        if (vport->isExternal) {
            if (vport->nicIndex == 0) {
                /* Such a vport is not part of any of the hash tables, since it
                 * is not exposed to userspace. See Vport.h for explanation. */
                ASSERT(hvDelete == TRUE);
                ASSERT(switchContext->numPhysicalNics == 0);
                switchContext->virtualExternalPortId = 0;
                switchContext->virtualExternalVport = NULL;
                OvsFreeMemoryWithTag(vport, OVS_VPORT_POOL_TAG);
                return STATUS_SUCCESS;
            }
        }
        hvSwitchPort = TRUE;
    default:
        break;
    }

    /*
     * 'hvDelete' == TRUE indicates that the port should be removed from the
     * 'portIdHashArray', while 'ovsDelete' == TRUE indicates that the port
     * should be removed from 'portNoHashArray' and the 'ovsPortNameHashArray'.
     *
     * Both 'hvDelete' and 'ovsDelete' can be set to TRUE by the caller.
     */
    if (vport->isAbsentOnHv == TRUE) {
        deletedOnHv = TRUE;
    }
    if (vport->portNo == OVS_DPPORT_NUMBER_INVALID) {
        deletedOnOvs = TRUE;
    }

    if (hvDelete && !deletedOnHv) {
        vport->isAbsentOnHv = TRUE;

        if (vport->isExternal) {
            ASSERT(vport->nicIndex != 0);
            ASSERT(switchContext->numPhysicalNics);
            switchContext->numPhysicalNics--;
        }

        /* Remove the port from the relevant lists. */
        RemoveEntryList(&vport->portIdLink);
        InitializeListHead(&vport->portIdLink);
        deletedOnHv = TRUE;
    }
    if (ovsDelete && !deletedOnOvs) {
        vport->portNo = OVS_DPPORT_NUMBER_INVALID;
        vport->ovsName[0] = '\0';

        /* Remove the port from the relevant lists. */
        RemoveEntryList(&vport->ovsNameLink);
        InitializeListHead(&vport->ovsNameLink);
        RemoveEntryList(&vport->portNoLink);
        InitializeListHead(&vport->portNoLink);
        if (OvsIsTunnelVportType(vport->ovsType)) {
            RemoveEntryList(&vport->tunnelVportLink);
            InitializeListHead(&vport->tunnelVportLink);
        }

        deletedOnOvs = TRUE;
    }

    /*
     * Deallocate the port if it has been deleted on the Hyper-V switch as well
     * as OVS userspace.
     */
    if (deletedOnHv && deletedOnOvs) {
        if (hvSwitchPort) {
            switchContext->numHvVports--;
        } else {
            switchContext->numNonHvVports--;
        }
        OvsFreeMemoryWithTag(vport, OVS_VPORT_POOL_TAG);
    }

    return STATUS_SUCCESS;
}

static NTSTATUS
OvsRemoveTunnelVport(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
                     POVS_SWITCH_CONTEXT switchContext,
                     POVS_VPORT_ENTRY vport,
                     BOOLEAN hvDelete,
                     BOOLEAN ovsDelete)
{
    POVS_TUNFLT_INIT_CONTEXT tunnelContext = NULL;
    PIRP irp = NULL;

    tunnelContext = OvsAllocateMemoryWithTag(sizeof(*tunnelContext),
                                             OVS_VPORT_POOL_TAG);
    if (tunnelContext == NULL) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }
    RtlZeroMemory(tunnelContext, sizeof(*tunnelContext));

    tunnelContext->switchContext = switchContext;
    tunnelContext->hvSwitchPort = FALSE;
    tunnelContext->hvDelete = hvDelete;
    tunnelContext->ovsDelete = ovsDelete;
    tunnelContext->vport = vport;

    if (usrParamsCtx) {
        tunnelContext->inputBuffer = usrParamsCtx->inputBuffer;
        tunnelContext->outputBuffer = usrParamsCtx->outputBuffer;
        tunnelContext->outputLength = usrParamsCtx->outputLength;
        irp = usrParamsCtx->irp;
    }

    return OvsCleanupVxlanTunnel(irp, vport, OvsTunnelVportPendingRemove,
                                 tunnelContext);
}

/*
 * --------------------------------------------------------------------------
 * Enumerates the ports on the Hyper-V switch.
 * --------------------------------------------------------------------------
 */
NDIS_STATUS
OvsAddConfiguredSwitchPorts(POVS_SWITCH_CONTEXT switchContext)
{
    NDIS_STATUS status = NDIS_STATUS_SUCCESS;
    ULONG arrIndex;
    PNDIS_SWITCH_PORT_PARAMETERS portParam;
    PNDIS_SWITCH_PORT_ARRAY portArray = NULL;

    OVS_LOG_TRACE("Enter: switchContext:%p", switchContext);

    status = OvsGetPortsOnSwitch(switchContext, &portArray);
    if (status != NDIS_STATUS_SUCCESS) {
        goto cleanup;
    }

    for (arrIndex = 0; arrIndex < portArray->NumElements; arrIndex++) {
         portParam = NDIS_SWITCH_PORT_AT_ARRAY_INDEX(portArray, arrIndex);

         if (portParam->IsValidationPort) {
             continue;
         }

         status = HvCreatePort(switchContext, portParam, 0);
         if (status != STATUS_SUCCESS && status != STATUS_DATA_NOT_ACCEPTED) {
             break;
         }
    }

cleanup:
    if (status != NDIS_STATUS_SUCCESS) {
        OvsClearAllSwitchVports(switchContext);
    }

    OvsFreeSwitchPortsArray(portArray);

    OVS_LOG_TRACE("Exit: status: %x", status);

    return status;
}

/*
 * --------------------------------------------------------------------------
 * Enumerates the NICs on the Hyper-V switch.
 * --------------------------------------------------------------------------
 */
NDIS_STATUS
OvsInitConfiguredSwitchNics(POVS_SWITCH_CONTEXT switchContext)
{
    NDIS_STATUS status = NDIS_STATUS_SUCCESS;
    PNDIS_SWITCH_NIC_ARRAY nicArray = NULL;
    ULONG arrIndex;
    PNDIS_SWITCH_NIC_PARAMETERS nicParam;

    OVS_LOG_TRACE("Enter: switchContext: %p", switchContext);
    /*
     * Now, get NIC list.
     */
    status = OvsGetNicsOnSwitch(switchContext, &nicArray);
    if (status != NDIS_STATUS_SUCCESS) {
        goto cleanup;
    }
    for (arrIndex = 0; arrIndex < nicArray->NumElements; ++arrIndex) {
        nicParam = NDIS_SWITCH_NIC_AT_ARRAY_INDEX(nicArray, arrIndex);

        /*
         * XXX: Check if the port is configured with a VLAN. Disallow such a
         * configuration, since we don't support tag-in-tag.
         * XXX: Check if the port is connected to a VF. Disconnect the VF in
         * such a case.
         */

        status = HvCreateNic(switchContext, nicParam);
        if (status == NDIS_STATUS_SUCCESS) {
            HvConnectNic(switchContext, nicParam);
        }
    }
cleanup:

    OvsFreeSwitchNicsArray(nicArray);

    OVS_LOG_TRACE("Exit: status: %x", status);
    return status;
}

/*
 * --------------------------------------------------------------------------
 * Deletes ports added from the Hyper-V switch as well as OVS usersapce. The
 * function deletes ports in 'portIdHashArray'. This will delete most of the
 * ports that are in the 'portNoHashArray' as well. Any remaining ports
 * are deleted by walking the 'portNoHashArray'.
 * --------------------------------------------------------------------------
 */
VOID
OvsClearAllSwitchVports(POVS_SWITCH_CONTEXT switchContext)
{
    for (UINT hash = 0; hash < OVS_MAX_VPORT_ARRAY_SIZE; hash++) {
        PLIST_ENTRY head, link, next;

        head = &(switchContext->portIdHashArray[hash & OVS_VPORT_MASK]);
        LIST_FORALL_SAFE(head, link, next) {
            POVS_VPORT_ENTRY vport;
            vport = CONTAINING_RECORD(link, OVS_VPORT_ENTRY, portIdLink);
            OvsRemoveAndDeleteVport(NULL, switchContext, vport, TRUE, TRUE);
        }
    }

    /*
     * Remove 'virtualExternalVport' as well. This port is not part of the
     * 'portIdHashArray'.
     */
    if (switchContext->virtualExternalVport) {
        OvsRemoveAndDeleteVport(NULL, switchContext,
            (POVS_VPORT_ENTRY)switchContext->virtualExternalVport, TRUE, TRUE);
    }


    for (UINT hash = 0; hash < OVS_MAX_VPORT_ARRAY_SIZE; hash++) {
        PLIST_ENTRY head, link, next;
        head = &(switchContext->portNoHashArray[hash & OVS_VPORT_MASK]);
        LIST_FORALL_SAFE(head, link, next) {
            POVS_VPORT_ENTRY vport;
            vport = CONTAINING_RECORD(link, OVS_VPORT_ENTRY, portNoLink);
            ASSERT(OvsIsTunnelVportType(vport->ovsType) ||
                   vport->isAbsentOnHv == TRUE);
            OvsRemoveAndDeleteVport(NULL, switchContext, vport, TRUE, TRUE);
        }
    }

    ASSERT(switchContext->virtualExternalVport == NULL);
    ASSERT(switchContext->countInternalVports == 0);
}


NTSTATUS
OvsConvertIfCountedStrToAnsiStr(PIF_COUNTED_STRING wStr,
                                CHAR *str,
                                UINT16 maxStrLen)
{
    ANSI_STRING astr;
    UNICODE_STRING ustr;
    NTSTATUS status;
    UINT32 size;

    ustr.Buffer = wStr->String;
    ustr.Length = wStr->Length;
    ustr.MaximumLength = IF_MAX_STRING_SIZE;

    astr.Buffer = str;
    astr.MaximumLength = maxStrLen;
    astr.Length = 0;

    size = RtlUnicodeStringToAnsiSize(&ustr);
    if (size > maxStrLen) {
        return STATUS_BUFFER_OVERFLOW;
    }

    status = RtlUnicodeStringToAnsiString(&astr, &ustr, FALSE);

    ASSERT(status == STATUS_SUCCESS);
    if (status != STATUS_SUCCESS) {
        return status;
    }
    ASSERT(astr.Length <= maxStrLen);
    str[astr.Length] = 0;
    return STATUS_SUCCESS;
}

/*
 * --------------------------------------------------------------------------
 * Utility function that populates a 'OVS_VPORT_EXT_INFO' structure for the
 * specified vport.
 * --------------------------------------------------------------------------
 */
NTSTATUS
OvsGetExtInfoIoctl(POVS_VPORT_GET vportGet,
                   POVS_VPORT_EXT_INFO extInfo)
{
    POVS_VPORT_ENTRY vport;
    LOCK_STATE_EX lockState;
    NTSTATUS status = STATUS_SUCCESS;
    BOOLEAN doConvert = FALSE;

    RtlZeroMemory(extInfo, sizeof (POVS_VPORT_EXT_INFO));
    NdisAcquireRWLockRead(gOvsSwitchContext->dispatchLock, &lockState, 0);
    if (vportGet->portNo == 0) {
        vport = OvsFindVportByHvNameA(gOvsSwitchContext, vportGet->name);
        if (vport == NULL) {
            /* If the port is not a Hyper-V port and it has been added earlier,
             * we'll find it in 'ovsPortNameHashArray'. */
            vport = OvsFindVportByOvsName(gOvsSwitchContext, vportGet->name);
        }
    } else {
        vport = OvsFindVportByPortNo(gOvsSwitchContext, vportGet->portNo);
    }
    if (vport == NULL || (vport->ovsState != OVS_STATE_CONNECTED &&
                          vport->ovsState != OVS_STATE_NIC_CREATED)) {
        NdisReleaseRWLock(gOvsSwitchContext->dispatchLock, &lockState);
        if (vportGet->portNo) {
            OVS_LOG_WARN("vport %u does not exist any more", vportGet->portNo);
        } else {
            OVS_LOG_WARN("vport %s does not exist any more", vportGet->name);
        }
        status = STATUS_DEVICE_DOES_NOT_EXIST;
        goto ext_info_done;
    }
    extInfo->dpNo = vportGet->dpNo;
    extInfo->portNo = vport->portNo;
    RtlCopyMemory(extInfo->macAddress, vport->currMacAddress,
                  sizeof (vport->currMacAddress));
    RtlCopyMemory(extInfo->permMACAddress, vport->permMacAddress,
                  sizeof (vport->permMacAddress));
    if (vport->ovsType == OVS_VPORT_TYPE_NETDEV) {
        RtlCopyMemory(extInfo->vmMACAddress, vport->vmMacAddress,
                      sizeof (vport->vmMacAddress));
    }
    extInfo->nicIndex = vport->nicIndex;
    extInfo->portId = vport->portId;
    extInfo->type = vport->ovsType;
    extInfo->mtu = vport->mtu;
    /*
     * TO be revisit XXX
     */
    if (vport->ovsState == OVS_STATE_NIC_CREATED) {
       extInfo->status = OVS_EVENT_CONNECT | OVS_EVENT_LINK_DOWN;
    } else if (vport->ovsState == OVS_STATE_CONNECTED) {
       extInfo->status = OVS_EVENT_CONNECT | OVS_EVENT_LINK_UP;
    } else {
       extInfo->status = OVS_EVENT_DISCONNECT;
    }
    if (extInfo->type == OVS_VPORT_TYPE_NETDEV &&
        (vport->ovsState == OVS_STATE_NIC_CREATED  ||
         vport->ovsState == OVS_STATE_CONNECTED)) {
        doConvert = TRUE;
    } else {
        extInfo->vmUUID[0] = 0;
        extInfo->vifUUID[0] = 0;
    }
    NdisReleaseRWLock(gOvsSwitchContext->dispatchLock, &lockState);
    if (doConvert) {
        status = OvsConvertIfCountedStrToAnsiStr(&vport->portFriendlyName,
                                                 extInfo->name,
                                                 OVS_MAX_PORT_NAME_LENGTH);
        if (status != STATUS_SUCCESS) {
            OVS_LOG_INFO("Fail to convert NIC name.");
            extInfo->name[0] = 0;
        }

        status = OvsConvertIfCountedStrToAnsiStr(&vport->vmName,
                                                 extInfo->vmUUID,
                                                 OVS_MAX_VM_UUID_LEN);
        if (status != STATUS_SUCCESS) {
            OVS_LOG_INFO("Fail to convert VM name.");
            extInfo->vmUUID[0] = 0;
        }

        status = OvsConvertIfCountedStrToAnsiStr(&vport->nicName,
                                                 extInfo->vifUUID,
                                                 OVS_MAX_VIF_UUID_LEN);
        if (status != STATUS_SUCCESS) {
            OVS_LOG_INFO("Fail to convert nic UUID");
            extInfo->vifUUID[0] = 0;
        }
        /*
         * for now ignore status
         */
        status = STATUS_SUCCESS;
    }

ext_info_done:
    return status;
}

/*
 * --------------------------------------------------------------------------
 *  Command Handler for 'OVS_WIN_NETDEV_CMD_GET'.
 * --------------------------------------------------------------------------
 */
NTSTATUS
OvsGetNetdevCmdHandler(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
                       UINT32 *replyLen)
{
    NTSTATUS status = STATUS_SUCCESS;
    POVS_MESSAGE msgIn = (POVS_MESSAGE)usrParamsCtx->inputBuffer;
    POVS_MESSAGE msgOut = (POVS_MESSAGE)usrParamsCtx->outputBuffer;
    NL_ERROR nlError = NL_ERROR_SUCCESS;
    OVS_VPORT_GET vportGet;
    OVS_VPORT_EXT_INFO info;

    static const NL_POLICY ovsNetdevPolicy[] = {
        [OVS_WIN_NETDEV_ATTR_NAME] = { .type = NL_A_STRING,
                                       .minLen = 2,
                                       .maxLen = IFNAMSIZ },
    };
    PNL_ATTR netdevAttrs[ARRAY_SIZE(ovsNetdevPolicy)];

    /* input buffer has been validated while validating transaction dev op. */
    ASSERT(usrParamsCtx->inputBuffer != NULL &&
           usrParamsCtx->inputLength > sizeof *msgIn);

    if (msgOut == NULL || usrParamsCtx->outputLength < sizeof *msgOut) {
        return STATUS_INVALID_BUFFER_SIZE;
    }

    if (!NlAttrParse((PNL_MSG_HDR)msgIn,
        NLMSG_HDRLEN + GENL_HDRLEN + OVS_HDRLEN,
        NlMsgAttrsLen((PNL_MSG_HDR)msgIn),
        ovsNetdevPolicy, ARRAY_SIZE(ovsNetdevPolicy),
        netdevAttrs, ARRAY_SIZE(netdevAttrs))) {
        return STATUS_INVALID_PARAMETER;
    }

    vportGet.portNo = 0;
    RtlCopyMemory(&vportGet.name, NlAttrGet(netdevAttrs[OVS_VPORT_ATTR_NAME]),
                  NlAttrGetSize(netdevAttrs[OVS_VPORT_ATTR_NAME]));

    status = OvsGetExtInfoIoctl(&vportGet, &info);
    if (status == STATUS_DEVICE_DOES_NOT_EXIST) {
        nlError = NL_ERROR_NODEV;
        goto cleanup;
    }

    status = CreateNetlinkMesgForNetdev(&info, msgIn,
                 usrParamsCtx->outputBuffer, usrParamsCtx->outputLength,
                 gOvsSwitchContext->dpNo);
    if (status == STATUS_SUCCESS) {
        *replyLen = msgOut->nlMsg.nlmsgLen;
    }

cleanup:
    if (nlError != NL_ERROR_SUCCESS) {
        POVS_MESSAGE_ERROR msgError = (POVS_MESSAGE_ERROR)
            usrParamsCtx->outputBuffer;

        ASSERT(msgError);
        NlBuildErrorMsg(msgIn, msgError, nlError, replyLen);
        ASSERT(*replyLen != 0);
    }

    return STATUS_SUCCESS;
}


/*
 * --------------------------------------------------------------------------
 *  Utility function to construct an OVS_MESSAGE for the specified vport. The
 *  OVS_MESSAGE contains the output of a netdev command.
 * --------------------------------------------------------------------------
 */
static NTSTATUS
CreateNetlinkMesgForNetdev(POVS_VPORT_EXT_INFO info,
                           POVS_MESSAGE msgIn,
                           PVOID outBuffer,
                           UINT32 outBufLen,
                           int dpIfIndex)
{
    NL_BUFFER nlBuffer;
    BOOLEAN ok;
    PNL_MSG_HDR nlMsg;
    UINT32 netdevFlags = 0;

    NlBufInit(&nlBuffer, outBuffer, outBufLen);

    ok = NlFillOvsMsg(&nlBuffer, msgIn->nlMsg.nlmsgType, NLM_F_MULTI,
                      msgIn->nlMsg.nlmsgSeq, msgIn->nlMsg.nlmsgPid,
                      msgIn->genlMsg.cmd, msgIn->genlMsg.version,
                      dpIfIndex);
    if (!ok) {
        return STATUS_INVALID_BUFFER_SIZE;
    }

    ok = NlMsgPutTailU32(&nlBuffer, OVS_WIN_NETDEV_ATTR_PORT_NO,
                         info->portNo);
    if (!ok) {
        return STATUS_INVALID_BUFFER_SIZE;
    }

    ok = NlMsgPutTailU32(&nlBuffer, OVS_WIN_NETDEV_ATTR_TYPE, info->type);
    if (!ok) {
        return STATUS_INVALID_BUFFER_SIZE;
    }

    ok = NlMsgPutTailString(&nlBuffer, OVS_WIN_NETDEV_ATTR_NAME,
                            info->name);
    if (!ok) {
        return STATUS_INVALID_BUFFER_SIZE;
    }

    ok = NlMsgPutTailUnspec(&nlBuffer, OVS_WIN_NETDEV_ATTR_MAC_ADDR,
             (PCHAR)info->macAddress, sizeof (info->macAddress));
    if (!ok) {
        return STATUS_INVALID_BUFFER_SIZE;
    }

    ok = NlMsgPutTailU32(&nlBuffer, OVS_WIN_NETDEV_ATTR_MTU, info->mtu);
    if (!ok) {
        return STATUS_INVALID_BUFFER_SIZE;
    }

    if (info->status != OVS_EVENT_CONNECT) {
        netdevFlags = OVS_WIN_NETDEV_IFF_UP;
    }
    ok = NlMsgPutTailU32(&nlBuffer, OVS_WIN_NETDEV_ATTR_IF_FLAGS,
                         netdevFlags);
    if (!ok) {
        return STATUS_INVALID_BUFFER_SIZE;
    }

    /*
     * XXX: add netdev_stats when we have the definition available in the
     * kernel.
     */

    nlMsg = (PNL_MSG_HDR)NlBufAt(&nlBuffer, 0, 0);
    nlMsg->nlmsgLen = NlBufSize(&nlBuffer);

    return STATUS_SUCCESS;
}

static __inline VOID
OvsWaitActivate(POVS_SWITCH_CONTEXT switchContext, ULONG sleepMicroSec)
{
    while ((!switchContext->isActivated) &&
          (!switchContext->isActivateFailed)) {
        /* Wait for the switch to be active and
         * the list of ports in OVS to be initialized. */
        NdisMSleep(sleepMicroSec);
    }
}

static NTSTATUS
OvsCreateMsgFromVport(POVS_VPORT_ENTRY vport,
                      POVS_MESSAGE msgIn,
                      PVOID outBuffer,
                      UINT32 outBufLen,
                      int dpIfIndex)
{
    NL_BUFFER nlBuffer;
    OVS_VPORT_FULL_STATS vportStats;
    BOOLEAN ok;
    PNL_MSG_HDR nlMsg;

    NlBufInit(&nlBuffer, outBuffer, outBufLen);

    ok = NlFillOvsMsg(&nlBuffer, msgIn->nlMsg.nlmsgType, NLM_F_MULTI,
                      msgIn->nlMsg.nlmsgSeq, msgIn->nlMsg.nlmsgPid,
                      msgIn->genlMsg.cmd, msgIn->genlMsg.version,
                      dpIfIndex);
    if (!ok) {
        return STATUS_INVALID_BUFFER_SIZE;
    }

    ok = NlMsgPutTailU32(&nlBuffer, OVS_VPORT_ATTR_PORT_NO, vport->portNo);
    if (!ok) {
        return STATUS_INVALID_BUFFER_SIZE;
    }

    ok = NlMsgPutTailU32(&nlBuffer, OVS_VPORT_ATTR_TYPE, vport->ovsType);
    if (!ok) {
        return STATUS_INVALID_BUFFER_SIZE;
    }

    ok = NlMsgPutTailString(&nlBuffer, OVS_VPORT_ATTR_NAME, vport->ovsName);
    if (!ok) {
        return STATUS_INVALID_BUFFER_SIZE;
    }

    /*
     * XXX: when we implement OVS_DP_ATTR_USER_FEATURES in datapath,
     * we'll need to check the OVS_DP_F_VPORT_PIDS flag: if it is set,
     * it means we have an array of pids, instead of a single pid.
     * ATM we assume we have one pid only.
    */

    ok = NlMsgPutTailU32(&nlBuffer, OVS_VPORT_ATTR_UPCALL_PID,
                         vport->upcallPid);
    if (!ok) {
        return STATUS_INVALID_BUFFER_SIZE;
    }

    /*stats*/
    vportStats.rxPackets = vport->stats.rxPackets;
    vportStats.rxBytes = vport->stats.rxBytes;
    vportStats.txPackets = vport->stats.txPackets;
    vportStats.txBytes = vport->stats.txBytes;
    vportStats.rxErrors = vport->errStats.rxErrors;
    vportStats.txErrors = vport->errStats.txErrors;
    vportStats.rxDropped = vport->errStats.rxDropped;
    vportStats.txDropped = vport->errStats.txDropped;

    ok = NlMsgPutTailUnspec(&nlBuffer, OVS_VPORT_ATTR_STATS,
                            (PCHAR)&vportStats,
                            sizeof(OVS_VPORT_FULL_STATS));
    if (!ok) {
        return STATUS_INVALID_BUFFER_SIZE;
    }

    /*
     * XXX: when vxlan udp dest port becomes configurable, we will also need
     * to add vport options
    */

    nlMsg = (PNL_MSG_HDR)NlBufAt(&nlBuffer, 0, 0);
    nlMsg->nlmsgLen = NlBufSize(&nlBuffer);

    return STATUS_SUCCESS;
}

static NTSTATUS
OvsGetVportDumpNext(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
                    UINT32 *replyLen)
{
    POVS_MESSAGE msgIn;
    POVS_OPEN_INSTANCE instance =
        (POVS_OPEN_INSTANCE)usrParamsCtx->ovsInstance;
    LOCK_STATE_EX lockState;
    UINT32 i = OVS_MAX_VPORT_ARRAY_SIZE;

    /*
     * XXX: this function shares some code with other dump command(s).
     * In the future, we will need to refactor the dump functions
    */

    ASSERT(usrParamsCtx->devOp == OVS_READ_DEV_OP);

    if (instance->dumpState.ovsMsg == NULL) {
        ASSERT(FALSE);
        return STATUS_INVALID_DEVICE_STATE;
    }

    /* Output buffer has been validated while validating read dev op. */
    ASSERT(usrParamsCtx->outputBuffer != NULL);

    msgIn = instance->dumpState.ovsMsg;

    /*
     * XXX: when we implement OVS_DP_ATTR_USER_FEATURES in datapath,
     * we'll need to check the OVS_DP_F_VPORT_PIDS flag: if it is set,
     * it means we have an array of pids, instead of a single pid.
     * ATM we assume we have one pid only.
    */
    NdisAcquireRWLockRead(gOvsSwitchContext->dispatchLock, &lockState, 0);

    if (gOvsSwitchContext->numHvVports > 0 ||
            gOvsSwitchContext->numNonHvVports > 0) {
        /* inBucket: the bucket, used for lookup */
        UINT32 inBucket = instance->dumpState.index[0];
        /* inIndex: index within the given bucket, used for lookup */
        UINT32 inIndex = instance->dumpState.index[1];
        /* the bucket to be used for the next dump operation */
        UINT32 outBucket = 0;
        /* the index within the outBucket to be used for the next dump */
        UINT32 outIndex = 0;

        for (i = inBucket; i < OVS_MAX_VPORT_ARRAY_SIZE; i++) {
            PLIST_ENTRY head, link;
            head = &(gOvsSwitchContext->portNoHashArray[i]);
            POVS_VPORT_ENTRY vport = NULL;

            outIndex = 0;
            LIST_FORALL(head, link) {

                /*
                 * if one or more dumps were previously done on this same bucket,
                 * inIndex will be > 0, so we'll need to reply with the
                 * inIndex + 1 vport from the bucket.
                */
                if (outIndex >= inIndex) {
                    vport = CONTAINING_RECORD(link, OVS_VPORT_ENTRY, portNoLink);

                    ASSERT(vport->portNo != OVS_DPPORT_NUMBER_INVALID);
                    OvsCreateMsgFromVport(vport, msgIn,
                                          usrParamsCtx->outputBuffer,
                                          usrParamsCtx->outputLength,
                                          gOvsSwitchContext->dpNo);
                    ++outIndex;
                    break;
                }

                ++outIndex;
            }

            if (vport) {
                break;
            }

            /*
             * if no vport was found above, check the next bucket, beginning
             * with the first (i.e. index 0) elem from within that bucket
            */
            inIndex = 0;
        }

        outBucket = i;

        /* XXX: what about NLMSG_DONE (as msg type)? */
        instance->dumpState.index[0] = outBucket;
        instance->dumpState.index[1] = outIndex;
    }

    NdisReleaseRWLock(gOvsSwitchContext->dispatchLock, &lockState);

    /* if i < OVS_MAX_VPORT_ARRAY_SIZE => vport was found */
    if (i < OVS_MAX_VPORT_ARRAY_SIZE) {
        POVS_MESSAGE msgOut = (POVS_MESSAGE)usrParamsCtx->outputBuffer;
        *replyLen = msgOut->nlMsg.nlmsgLen;
    } else {
        /*
         * if i >= OVS_MAX_VPORT_ARRAY_SIZE => vport was not found =>
         * it's dump done
         */
        *replyLen = 0;
        /* Free up the dump state, since there's no more data to continue. */
        FreeUserDumpState(instance);
    }

    return STATUS_SUCCESS;
}

static NTSTATUS
OvsGetVport(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
            UINT32 *replyLen)
{
    NTSTATUS status = STATUS_SUCCESS;
    LOCK_STATE_EX lockState;

    POVS_MESSAGE msgIn = (POVS_MESSAGE)usrParamsCtx->inputBuffer;
    POVS_MESSAGE msgOut = (POVS_MESSAGE)usrParamsCtx->outputBuffer;
    POVS_VPORT_ENTRY vport = NULL;
    NL_ERROR nlError = NL_ERROR_SUCCESS;
    PCHAR portName = NULL;
    UINT32 portNameLen = 0;
    UINT32 portNumber = OVS_DPPORT_NUMBER_INVALID;

    static const NL_POLICY ovsVportPolicy[] = {
        [OVS_VPORT_ATTR_PORT_NO] = { .type = NL_A_U32, .optional = TRUE },
        [OVS_VPORT_ATTR_NAME] = { .type = NL_A_STRING,
                                  .minLen = 2,
                                  .maxLen = IFNAMSIZ,
                                  .optional = TRUE},
    };
    PNL_ATTR vportAttrs[ARRAY_SIZE(ovsVportPolicy)];

    /* input buffer has been validated while validating write dev op. */
    ASSERT(usrParamsCtx->inputBuffer != NULL);

    if (!NlAttrParse((PNL_MSG_HDR)msgIn,
        NLMSG_HDRLEN + GENL_HDRLEN + OVS_HDRLEN,
        NlMsgAttrsLen((PNL_MSG_HDR)msgIn),
        ovsVportPolicy, ARRAY_SIZE(ovsVportPolicy),
        vportAttrs, ARRAY_SIZE(vportAttrs))) {
        return STATUS_INVALID_PARAMETER;
    }

    /* Output buffer has been validated while validating transact dev op. */
    ASSERT(msgOut != NULL && usrParamsCtx->outputLength >= sizeof *msgOut);

    NdisAcquireRWLockRead(gOvsSwitchContext->dispatchLock, &lockState, 0);
    if (vportAttrs[OVS_VPORT_ATTR_NAME] != NULL) {
        portName = NlAttrGet(vportAttrs[OVS_VPORT_ATTR_NAME]);
        portNameLen = NlAttrGetSize(vportAttrs[OVS_VPORT_ATTR_NAME]);

        /* the port name is expected to be null-terminated */
        ASSERT(portName[portNameLen - 1] == '\0');

        vport = OvsFindVportByOvsName(gOvsSwitchContext, portName);
    } else if (vportAttrs[OVS_VPORT_ATTR_PORT_NO] != NULL) {
        portNumber = NlAttrGetU32(vportAttrs[OVS_VPORT_ATTR_PORT_NO]);

        vport = OvsFindVportByPortNo(gOvsSwitchContext, portNumber);
    } else {
        nlError = NL_ERROR_INVAL;
        NdisReleaseRWLock(gOvsSwitchContext->dispatchLock, &lockState);
        goto Cleanup;
    }

    if (!vport) {
        nlError = NL_ERROR_NODEV;
        NdisReleaseRWLock(gOvsSwitchContext->dispatchLock, &lockState);
        goto Cleanup;
    }

    status = OvsCreateMsgFromVport(vport, msgIn, usrParamsCtx->outputBuffer,
                                   usrParamsCtx->outputLength,
                                   gOvsSwitchContext->dpNo);
    NdisReleaseRWLock(gOvsSwitchContext->dispatchLock, &lockState);

    *replyLen = msgOut->nlMsg.nlmsgLen;

Cleanup:
    if (nlError != NL_ERROR_SUCCESS) {
        POVS_MESSAGE_ERROR msgError = (POVS_MESSAGE_ERROR)
            usrParamsCtx->outputBuffer;

        ASSERT(msgError);
        NlBuildErrorMsg(msgIn, msgError, nlError, replyLen);
        ASSERT(*replyLen != 0);
    }

    return STATUS_SUCCESS;
}

/*
 * --------------------------------------------------------------------------
 *  Command Handler for 'OVS_VPORT_CMD_GET'.
 *
 *  The function handles the initial call to setup the dump state, as well as
 *  subsequent calls to continue dumping data.
 * --------------------------------------------------------------------------
*/
NTSTATUS
OvsGetVportCmdHandler(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
                      UINT32 *replyLen)
{
    *replyLen = 0;

    switch (usrParamsCtx->devOp) {
    case OVS_WRITE_DEV_OP:
        return OvsSetupDumpStart(usrParamsCtx);

    case OVS_READ_DEV_OP:
        return OvsGetVportDumpNext(usrParamsCtx, replyLen);

    case OVS_TRANSACTION_DEV_OP:
        return OvsGetVport(usrParamsCtx, replyLen);

    default:
        return STATUS_INVALID_DEVICE_REQUEST;
    }

}

static UINT32
OvsComputeVportNo(POVS_SWITCH_CONTEXT switchContext)
{
    /* we are not allowed to create the port OVS_DPPORT_NUMBER_LOCAL */
    for (ULONG i = OVS_DPPORT_NUMBER_LOCAL + 1; i < MAXUINT16; ++i) {
        POVS_VPORT_ENTRY vport;

        vport = OvsFindVportByPortNo(switchContext, i);
        if (!vport) {
            return i;
        }
    }

    return OVS_DPPORT_NUMBER_INVALID;
}

/*
 * --------------------------------------------------------------------------
 *  Command Handler for 'OVS_VPORT_CMD_NEW'.
 * --------------------------------------------------------------------------
 */
NTSTATUS
OvsNewVportCmdHandler(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
                      UINT32 *replyLen)
{
    NDIS_STATUS status = STATUS_SUCCESS;
    LOCK_STATE_EX lockState;

    NL_ERROR nlError = NL_ERROR_SUCCESS;
    POVS_MESSAGE msgIn = (POVS_MESSAGE)usrParamsCtx->inputBuffer;
    POVS_MESSAGE msgOut = (POVS_MESSAGE)usrParamsCtx->outputBuffer;
    POVS_VPORT_ENTRY vport = NULL;
    PCHAR portName;
    ULONG portNameLen;
    UINT32 portType;
    BOOLEAN vportAllocated = FALSE, vportInitialized = FALSE;

    static const NL_POLICY ovsVportPolicy[] = {
        [OVS_VPORT_ATTR_PORT_NO] = { .type = NL_A_U32, .optional = TRUE },
        [OVS_VPORT_ATTR_TYPE] = { .type = NL_A_U32, .optional = FALSE },
        [OVS_VPORT_ATTR_NAME] = { .type = NL_A_STRING, .maxLen = IFNAMSIZ,
                                  .optional = FALSE},
        [OVS_VPORT_ATTR_UPCALL_PID] = { .type = NL_A_UNSPEC,
                                        .optional = FALSE },
        [OVS_VPORT_ATTR_OPTIONS] = { .type = NL_A_NESTED, .optional = TRUE },
    };

    PNL_ATTR vportAttrs[ARRAY_SIZE(ovsVportPolicy)];

    /* input buffer has been validated while validating write dev op. */
    ASSERT(usrParamsCtx->inputBuffer != NULL);

    /* Output buffer has been validated while validating transact dev op. */
    ASSERT(msgOut != NULL && usrParamsCtx->outputLength >= sizeof *msgOut);

    if (!NlAttrParse((PNL_MSG_HDR)msgIn,
        NLMSG_HDRLEN + GENL_HDRLEN + OVS_HDRLEN,
        NlMsgAttrsLen((PNL_MSG_HDR)msgIn),
        ovsVportPolicy, ARRAY_SIZE(ovsVportPolicy),
        vportAttrs, ARRAY_SIZE(vportAttrs))) {
        return STATUS_INVALID_PARAMETER;
    }

    portName = NlAttrGet(vportAttrs[OVS_VPORT_ATTR_NAME]);
    portNameLen = NlAttrGetSize(vportAttrs[OVS_VPORT_ATTR_NAME]);
    portType = NlAttrGetU32(vportAttrs[OVS_VPORT_ATTR_TYPE]);

    /* we are expecting null terminated strings to be passed */
    ASSERT(portName[portNameLen - 1] == '\0');

    NdisAcquireRWLockWrite(gOvsSwitchContext->dispatchLock, &lockState, 0);

    vport = OvsFindVportByOvsName(gOvsSwitchContext, portName);
    if (vport) {
        nlError = NL_ERROR_EXIST;
        goto Cleanup;
    }

    if (portType == OVS_VPORT_TYPE_NETDEV ||
        portType == OVS_VPORT_TYPE_INTERNAL) {
        /* External and internal ports can also be looked up like VIF ports. */
        vport = OvsFindVportByHvNameA(gOvsSwitchContext, portName);
    } else {
        ASSERT(OvsIsTunnelVportType(portType));

        vport = (POVS_VPORT_ENTRY)OvsAllocateVport();
        if (vport == NULL) {
            nlError = NL_ERROR_NOMEM;
            goto Cleanup;
        }
        vportAllocated = TRUE;

        if (OvsIsTunnelVportType(portType)) {
            UINT16 transportPortDest = 0;
            UINT8 nwProto = IPPROTO_NONE;
            POVS_VPORT_ENTRY dupVport;

            switch (portType) {
            case OVS_VPORT_TYPE_GRE:
                nwProto = IPPROTO_GRE;
                break;
            case OVS_VPORT_TYPE_VXLAN:
                transportPortDest = VXLAN_UDP_PORT;
                nwProto = IPPROTO_UDP;
                break;
            case OVS_VPORT_TYPE_GENEVE:
                transportPortDest = GENEVE_UDP_PORT;
                break;
            case OVS_VPORT_TYPE_STT:
                transportPortDest = STT_TCP_PORT;
                nwProto = IPPROTO_TCP;
                break;
            default:
                nlError = NL_ERROR_INVAL;
                goto Cleanup;
            }

            if (vportAttrs[OVS_VPORT_ATTR_OPTIONS]) {
                PNL_ATTR attr = NlAttrFindNested(vportAttrs[OVS_VPORT_ATTR_OPTIONS],
                                                 OVS_TUNNEL_ATTR_DST_PORT);
                if (attr) {
                    transportPortDest = NlAttrGetU16(attr);
                }
            }

            /*
             * We don't allow two tunnel ports on identical N/W protocol and
             * L4 port number. This is applicable even if the two ports are of
             * different tunneling types.
             */
            dupVport =
                OvsFindTunnelVportByDstPortAndNWProto(gOvsSwitchContext,
                                                      transportPortDest,
                                                      nwProto);
            if (dupVport) {
                OVS_LOG_ERROR("Vport for N/W proto and port already exists,"
                    " type: %u, dst port: %u, name: %s", dupVport->ovsType,
                    transportPortDest, dupVport->ovsName);
                goto Cleanup;
            }

            status = OvsInitTunnelVport(usrParamsCtx,
                                        vport,
                                        portType,
                                        transportPortDest);

            nlError = NlMapStatusToNlErr(status);
        }

        vportInitialized = TRUE;

        if (nlError == NL_ERROR_SUCCESS) {
            vport->ovsState = OVS_STATE_CONNECTED;
            vport->nicState = NdisSwitchNicStateConnected;

            /*
             * Allow the vport to be deleted, because there is no
             * corresponding hyper-v switch part.
             */
            vport->isAbsentOnHv = TRUE;
        } else {
            goto Cleanup;
        }
    }

    if (!vport) {
        nlError = NL_ERROR_INVAL;
        goto Cleanup;
    }
    if (vport->portNo != OVS_DPPORT_NUMBER_INVALID) {
        nlError = NL_ERROR_EXIST;
        goto Cleanup;
    }

    if (vportAttrs[OVS_VPORT_ATTR_PORT_NO] != NULL) {
        /*
         * XXX: when we implement the limit for ovs port number to be
         * MAXUINT16, we'll need to check the port number received from the
         * userspace.
         */
        vport->portNo = NlAttrGetU32(vportAttrs[OVS_VPORT_ATTR_PORT_NO]);
    } else {
        vport->portNo = OvsComputeVportNo(gOvsSwitchContext);
        if (vport->portNo == OVS_DPPORT_NUMBER_INVALID) {
            nlError = NL_ERROR_NOMEM;
            goto Cleanup;
        }
    }

    /* The ovs port name must be uninitialized. */
    ASSERT(vport->ovsName[0] == '\0');
    ASSERT(portNameLen <= OVS_MAX_PORT_NAME_LENGTH);

    RtlCopyMemory(vport->ovsName, portName, portNameLen);
    /* if we don't have options, then vport->portOptions will be NULL */
    vport->portOptions = vportAttrs[OVS_VPORT_ATTR_OPTIONS];

    /*
     * XXX: when we implement OVS_DP_ATTR_USER_FEATURES in datapath,
     * we'll need to check the OVS_DP_F_VPORT_PIDS flag: if it is set,
     * it means we have an array of pids, instead of a single pid.
     * ATM we assume we have one pid only.
     */
    vport->upcallPid = NlAttrGetU32(vportAttrs[OVS_VPORT_ATTR_UPCALL_PID]);

    status = InitOvsVportCommon(gOvsSwitchContext, vport);
    ASSERT(status == STATUS_SUCCESS);

    status = OvsCreateMsgFromVport(vport, msgIn, usrParamsCtx->outputBuffer,
                                   usrParamsCtx->outputLength,
                                   gOvsSwitchContext->dpNo);

    *replyLen = msgOut->nlMsg.nlmsgLen;
    OVS_LOG_INFO("Created new vport, name: %s, type: %u", vport->ovsName,
                 vport->ovsType);

Cleanup:
    NdisReleaseRWLock(gOvsSwitchContext->dispatchLock, &lockState);

    if ((nlError != NL_ERROR_SUCCESS) && (nlError != NL_ERROR_PENDING)) {
        POVS_MESSAGE_ERROR msgError = (POVS_MESSAGE_ERROR)
            usrParamsCtx->outputBuffer;

        if (vport && vportAllocated == TRUE) {
            if (vportInitialized == TRUE) {
                if (OvsIsTunnelVportType(portType)) {
                    switch (vport->ovsType) {
                    case OVS_VPORT_TYPE_VXLAN:
                        OvsCleanupVxlanTunnel(NULL, vport, NULL, NULL);
                        break;
                    case OVS_VPORT_TYPE_STT:
                        OvsCleanupSttTunnel(vport);
                        break;
                    case OVS_VPORT_TYPE_GENEVE:
                        OvsCleanupGeneveTunnel(vport);
                        break;
                    default:
                        ASSERT(!"Invalid tunnel port type");
                    }
                }
            }
            OvsFreeMemoryWithTag(vport, OVS_VPORT_POOL_TAG);
        }

        ASSERT(msgError);
        NlBuildErrorMsg(msgIn, msgError, nlError, replyLen);
        ASSERT(*replyLen != 0);
    }

    return (status == STATUS_PENDING) ? STATUS_PENDING : STATUS_SUCCESS;
}


/*
 * --------------------------------------------------------------------------
 *  Command Handler for 'OVS_VPORT_CMD_SET'.
 * --------------------------------------------------------------------------
 */
NTSTATUS
OvsSetVportCmdHandler(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
                      UINT32 *replyLen)
{
    NDIS_STATUS status = STATUS_SUCCESS;
    LOCK_STATE_EX lockState;

    POVS_MESSAGE msgIn = (POVS_MESSAGE)usrParamsCtx->inputBuffer;
    POVS_MESSAGE msgOut = (POVS_MESSAGE)usrParamsCtx->outputBuffer;
    POVS_VPORT_ENTRY vport = NULL;
    NL_ERROR nlError = NL_ERROR_SUCCESS;

    static const NL_POLICY ovsVportPolicy[] = {
        [OVS_VPORT_ATTR_PORT_NO] = { .type = NL_A_U32, .optional = TRUE },
        [OVS_VPORT_ATTR_TYPE] = { .type = NL_A_U32, .optional = TRUE },
        [OVS_VPORT_ATTR_NAME] = { .type = NL_A_STRING, .maxLen = IFNAMSIZ,
                                  .optional = TRUE },
        [OVS_VPORT_ATTR_UPCALL_PID] = { .type = NL_A_UNSPEC,
                                        .optional = TRUE },
        [OVS_VPORT_ATTR_STATS] = { .type = NL_A_UNSPEC,
                                   .minLen = sizeof(OVS_VPORT_FULL_STATS),
                                   .maxLen = sizeof(OVS_VPORT_FULL_STATS),
                                   .optional = TRUE },
        [OVS_VPORT_ATTR_OPTIONS] = { .type = NL_A_NESTED, .optional = TRUE },
    };
    PNL_ATTR vportAttrs[ARRAY_SIZE(ovsVportPolicy)];

    ASSERT(usrParamsCtx->inputBuffer != NULL);

    if (!NlAttrParse((PNL_MSG_HDR)msgIn,
        NLMSG_HDRLEN + GENL_HDRLEN + OVS_HDRLEN,
        NlMsgAttrsLen((PNL_MSG_HDR)msgIn),
        ovsVportPolicy, ARRAY_SIZE(ovsVportPolicy),
        vportAttrs, ARRAY_SIZE(vportAttrs))) {
        return STATUS_INVALID_PARAMETER;
    }

    /* Output buffer has been validated while validating transact dev op. */
    ASSERT(msgOut != NULL && usrParamsCtx->outputLength >= sizeof *msgOut);

    NdisAcquireRWLockWrite(gOvsSwitchContext->dispatchLock, &lockState, 0);
    if (vportAttrs[OVS_VPORT_ATTR_NAME] != NULL) {
        PSTR portName = NlAttrGet(vportAttrs[OVS_VPORT_ATTR_NAME]);
#ifdef DBG
        UINT32 portNameLen = NlAttrGetSize(vportAttrs[OVS_VPORT_ATTR_NAME]);
#endif
        /* the port name is expected to be null-terminated */
        ASSERT(portName[portNameLen - 1] == '\0');

        vport = OvsFindVportByOvsName(gOvsSwitchContext, portName);
    } else if (vportAttrs[OVS_VPORT_ATTR_PORT_NO] != NULL) {
        vport = OvsFindVportByPortNo(gOvsSwitchContext,
                    NlAttrGetU32(vportAttrs[OVS_VPORT_ATTR_PORT_NO]));
    }

    if (!vport) {
        nlError = NL_ERROR_NODEV;
        goto Cleanup;
    }

    /*
     * XXX: when we implement OVS_DP_ATTR_USER_FEATURES in datapath,
     * we'll need to check the OVS_DP_F_VPORT_PIDS flag: if it is set,
     * it means we have an array of pids, instead of a single pid.
     * Currently, we support only one pid.
     */
    if (vportAttrs[OVS_VPORT_ATTR_UPCALL_PID]) {
        vport->upcallPid = NlAttrGetU32(vportAttrs[OVS_VPORT_ATTR_UPCALL_PID]);
    }

    if (vportAttrs[OVS_VPORT_ATTR_TYPE]) {
        OVS_VPORT_TYPE type = NlAttrGetU32(vportAttrs[OVS_VPORT_ATTR_TYPE]);
        if (type != vport->ovsType) {
            nlError = NL_ERROR_INVAL;
            goto Cleanup;
        }
    }

    if (vportAttrs[OVS_VPORT_ATTR_OPTIONS]) {
        OVS_LOG_ERROR("Vport options not supported");
        nlError = NL_ERROR_NOTSUPP;
        goto Cleanup;
    }

    status = OvsCreateMsgFromVport(vport, msgIn, usrParamsCtx->outputBuffer,
                                   usrParamsCtx->outputLength,
                                   gOvsSwitchContext->dpNo);

    *replyLen = msgOut->nlMsg.nlmsgLen;

Cleanup:
    NdisReleaseRWLock(gOvsSwitchContext->dispatchLock, &lockState);

    if (nlError != NL_ERROR_SUCCESS) {
        POVS_MESSAGE_ERROR msgError = (POVS_MESSAGE_ERROR)
            usrParamsCtx->outputBuffer;

        ASSERT(msgError);
        NlBuildErrorMsg(msgIn, msgError, nlError, replyLen);
        ASSERT(*replyLen != 0);
    }

    return STATUS_SUCCESS;
}

/*
 * --------------------------------------------------------------------------
 *  Command Handler for 'OVS_VPORT_CMD_DEL'.
 * --------------------------------------------------------------------------
 */
NTSTATUS
OvsDeleteVportCmdHandler(POVS_USER_PARAMS_CONTEXT usrParamsCtx,
                         UINT32 *replyLen)
{
    NDIS_STATUS status = STATUS_SUCCESS;
    LOCK_STATE_EX lockState;

    POVS_MESSAGE msgIn = (POVS_MESSAGE)usrParamsCtx->inputBuffer;
    POVS_MESSAGE msgOut = (POVS_MESSAGE)usrParamsCtx->outputBuffer;
    POVS_VPORT_ENTRY vport = NULL;
    NL_ERROR nlError = NL_ERROR_SUCCESS;
    PSTR portName = NULL;
    UINT32 portNameLen = 0;

    static const NL_POLICY ovsVportPolicy[] = {
        [OVS_VPORT_ATTR_PORT_NO] = { .type = NL_A_U32, .optional = TRUE },
        [OVS_VPORT_ATTR_NAME] = { .type = NL_A_STRING, .maxLen = IFNAMSIZ,
                                  .optional = TRUE },
    };
    PNL_ATTR vportAttrs[ARRAY_SIZE(ovsVportPolicy)];

    ASSERT(usrParamsCtx->inputBuffer != NULL);

    if (!NlAttrParse((PNL_MSG_HDR)msgIn,
        NLMSG_HDRLEN + GENL_HDRLEN + OVS_HDRLEN,
        NlMsgAttrsLen((PNL_MSG_HDR)msgIn),
        ovsVportPolicy, ARRAY_SIZE(ovsVportPolicy),
        vportAttrs, ARRAY_SIZE(vportAttrs))) {
        return STATUS_INVALID_PARAMETER;
    }

    /* Output buffer has been validated while validating transact dev op. */
    ASSERT(msgOut != NULL && usrParamsCtx->outputLength >= sizeof *msgOut);

    NdisAcquireRWLockWrite(gOvsSwitchContext->dispatchLock, &lockState, 0);
    if (vportAttrs[OVS_VPORT_ATTR_NAME] != NULL) {
        portName = NlAttrGet(vportAttrs[OVS_VPORT_ATTR_NAME]);
        portNameLen = NlAttrGetSize(vportAttrs[OVS_VPORT_ATTR_NAME]);

        /* the port name is expected to be null-terminated */
        ASSERT(portName[portNameLen - 1] == '\0');

        vport = OvsFindVportByOvsName(gOvsSwitchContext, portName);
    }
    else if (vportAttrs[OVS_VPORT_ATTR_PORT_NO] != NULL) {
        vport = OvsFindVportByPortNo(gOvsSwitchContext,
            NlAttrGetU32(vportAttrs[OVS_VPORT_ATTR_PORT_NO]));
    }

    if (!vport) {
        nlError = NL_ERROR_NODEV;
        goto Cleanup;
    }

    status = OvsCreateMsgFromVport(vport, msgIn, usrParamsCtx->outputBuffer,
                                   usrParamsCtx->outputLength,
                                   gOvsSwitchContext->dpNo);

    *replyLen = msgOut->nlMsg.nlmsgLen;

    /*
     * Mark the port as deleted from OVS userspace. If the port does not exist
     * on the Hyper-V switch, it gets deallocated. Otherwise, it stays.
     */
    status = OvsRemoveAndDeleteVport(usrParamsCtx,
                                     gOvsSwitchContext,
                                     vport,
                                     FALSE,
                                     TRUE);
    if (status) {
        nlError = NlMapStatusToNlErr(status);
    }

Cleanup:
    NdisReleaseRWLock(gOvsSwitchContext->dispatchLock, &lockState);

    if ((nlError != NL_ERROR_SUCCESS) && (nlError != NL_ERROR_PENDING)) {
        POVS_MESSAGE_ERROR msgError = (POVS_MESSAGE_ERROR)
            usrParamsCtx->outputBuffer;

        ASSERT(msgError);
        NlBuildErrorMsg(msgIn, msgError, nlError, replyLen);
        ASSERT(*replyLen != 0);
    }

    return (status == STATUS_PENDING) ? STATUS_PENDING : STATUS_SUCCESS;
}

static VOID
OvsTunnelVportPendingRemove(PVOID context,
                            NTSTATUS status,
                            UINT32 *replyLen)
{
    POVS_TUNFLT_INIT_CONTEXT tunnelContext =
        (POVS_TUNFLT_INIT_CONTEXT) context;
    POVS_SWITCH_CONTEXT switchContext = tunnelContext->switchContext;
    POVS_VPORT_ENTRY vport = tunnelContext->vport;
    POVS_MESSAGE msgIn = (POVS_MESSAGE)tunnelContext->inputBuffer;
    POVS_MESSAGE msgOut = (POVS_MESSAGE)tunnelContext->outputBuffer;
    NL_ERROR nlError = NlMapStatusToNlErr(status);
    LOCK_STATE_EX lockState;

    NdisAcquireRWLockWrite(switchContext->dispatchLock, &lockState, 0);

    if (msgIn && msgOut) {
        /* Check the received status to reply to the caller. */
        if (STATUS_SUCCESS == status) {
            OvsCreateMsgFromVport(vport,
                                  msgIn,
                                  msgOut,
                                  tunnelContext->outputLength,
                                  switchContext->dpNo);

            *replyLen = msgOut->nlMsg.nlmsgLen;
        } else {
            POVS_MESSAGE_ERROR msgError = (POVS_MESSAGE_ERROR)msgOut;
            ASSERT(msgError);
            NlBuildErrorMsg(msgIn, msgError, nlError, replyLen);
            ASSERT(*replyLen != 0);
        }
    }

    ASSERT(vport->isAbsentOnHv == TRUE);
    ASSERT(vport->portNo != OVS_DPPORT_NUMBER_INVALID);

    /* Remove the port from the relevant lists. */
    switchContext->numNonHvVports--;
    RemoveEntryList(&vport->ovsNameLink);
    RemoveEntryList(&vport->portNoLink);
    RemoveEntryList(&vport->tunnelVportLink);

    if (vport->priv) {
        OvsFreeMemoryWithTag(vport->priv, OVS_VXLAN_POOL_TAG);
        vport->priv = NULL;
    }

    OvsFreeMemoryWithTag(vport, OVS_VPORT_POOL_TAG);

    NdisReleaseRWLock(switchContext->dispatchLock, &lockState);
}

static VOID
OvsTunnelVportPendingInit(PVOID context,
                          NTSTATUS status,
                          UINT32 *replyLen)
{
    POVS_TUNFLT_INIT_CONTEXT tunnelContext =
        (POVS_TUNFLT_INIT_CONTEXT) context;
    POVS_VPORT_ENTRY vport = tunnelContext->vport;
    POVS_MESSAGE msgIn = (POVS_MESSAGE)tunnelContext->inputBuffer;
    POVS_MESSAGE msgOut = (POVS_MESSAGE)tunnelContext->outputBuffer;
    PCHAR portName;
    ULONG portNameLen = 0;
    UINT32 portType = 0;
    NL_ERROR nlError = NL_ERROR_SUCCESS;
    BOOLEAN error = TRUE;

    do {
        if (!NT_SUCCESS(status)) {
            nlError = NlMapStatusToNlErr(status);
            break;
        }

        static const NL_POLICY ovsVportPolicy[] = {
            [OVS_VPORT_ATTR_PORT_NO] = { .type = NL_A_U32, .optional = TRUE },
            [OVS_VPORT_ATTR_TYPE] = { .type = NL_A_U32, .optional = FALSE },
            [OVS_VPORT_ATTR_NAME] = { .type = NL_A_STRING, .maxLen = IFNAMSIZ,
            .optional = FALSE },
            [OVS_VPORT_ATTR_UPCALL_PID] = { .type = NL_A_UNSPEC,
            .optional = FALSE },
            [OVS_VPORT_ATTR_OPTIONS] = { .type = NL_A_NESTED, .optional = TRUE },
        };

        PNL_ATTR vportAttrs[ARRAY_SIZE(ovsVportPolicy)];

        /* input buffer has been validated while validating write dev op. */
        ASSERT(msgIn != NULL);

        /* Output buffer has been validated while validating transact dev op. */
        ASSERT(msgOut != NULL && tunnelContext->outputLength >= sizeof *msgOut);

        if (!NlAttrParse((PNL_MSG_HDR)msgIn,
            NLMSG_HDRLEN + GENL_HDRLEN + OVS_HDRLEN,
            NlMsgAttrsLen((PNL_MSG_HDR)msgIn),
            ovsVportPolicy, ARRAY_SIZE(ovsVportPolicy),
            vportAttrs, ARRAY_SIZE(vportAttrs))) {
            nlError = NL_ERROR_INVAL;
            break;
        }

        portName = NlAttrGet(vportAttrs[OVS_VPORT_ATTR_NAME]);
        portNameLen = NlAttrGetSize(vportAttrs[OVS_VPORT_ATTR_NAME]);
        portType = NlAttrGetU32(vportAttrs[OVS_VPORT_ATTR_TYPE]);

        if (vport->portNo != OVS_DPPORT_NUMBER_INVALID) {
            nlError = NL_ERROR_EXIST;
            break;
        }

        vport->ovsState = OVS_STATE_CONNECTED;
        vport->nicState = NdisSwitchNicStateConnected;

        /*
         * Allow the vport to be deleted, because there is no
         * corresponding hyper-v switch part.
         */
        vport->isAbsentOnHv = TRUE;

        if (vportAttrs[OVS_VPORT_ATTR_PORT_NO] != NULL) {
            /*
             * XXX: when we implement the limit for OVS port number to be
             * MAXUINT16, we'll need to check the port number received from the
             * userspace.
             */
            vport->portNo =
                NlAttrGetU32(vportAttrs[OVS_VPORT_ATTR_PORT_NO]);
        } else {
            vport->portNo =
                OvsComputeVportNo(gOvsSwitchContext);
            if (vport->portNo == OVS_DPPORT_NUMBER_INVALID) {
                nlError = NL_ERROR_NOMEM;
                break;
            }
        }

        /* The ovs port name must be uninitialized. */
        ASSERT(vport->ovsName[0] == '\0');
        ASSERT(portNameLen <= OVS_MAX_PORT_NAME_LENGTH);

        RtlCopyMemory(vport->ovsName, portName, portNameLen);
        /* if we don't have options, then vport->portOptions will be NULL */
        vport->portOptions = vportAttrs[OVS_VPORT_ATTR_OPTIONS];

        /*
         * XXX: when we implement OVS_DP_ATTR_USER_FEATURES in datapath,
         * we'll need to check the OVS_DP_F_VPORT_PIDS flag: if it is set,
         * it means we have an array of pids, instead of a single pid.
         * ATM we assume we have one pid only.
         */
        vport->upcallPid =
            NlAttrGetU32(vportAttrs[OVS_VPORT_ATTR_UPCALL_PID]);

        status = InitOvsVportCommon(gOvsSwitchContext, vport);
        ASSERT(status == STATUS_SUCCESS);

        OvsCreateMsgFromVport(vport,
                              msgIn,
                              msgOut,
                              tunnelContext->outputLength,
                              gOvsSwitchContext->dpNo);

        *replyLen = msgOut->nlMsg.nlmsgLen;

        error = FALSE;
    } while (error);

    if (error) {
        POVS_MESSAGE_ERROR msgError = (POVS_MESSAGE_ERROR)msgOut;

        OvsCleanupVxlanTunnel(NULL, vport, NULL, NULL);
        OvsFreeMemory(vport);

        ASSERT(msgError);
        NlBuildErrorMsg(msgIn, msgError, nlError, replyLen);
        ASSERT(*replyLen != 0);
    }
}

_Use_decl_annotations_
static VOID
OvsBindVportWithIpHelper(POVS_VPORT_ENTRY vport,
                         POVS_SWITCH_CONTEXT switchContext)
{
    OVS_LOG_TRACE("OvsBindVportWithIpHelper: %d", vport->portNo);
    ASSERT(switchContext->ipHelperBoundVportNo == 0);
    switchContext->ipHelperBoundVportNo = vport->portNo;
    OvsInternalAdapterUp(vport->portNo, &vport->netCfgInstanceId);
}

_Use_decl_annotations_
static VOID
OvsUnBindVportWithIpHelper(POVS_VPORT_ENTRY vport,
                           POVS_SWITCH_CONTEXT switchContext)
{
    OVS_LOG_TRACE("OvsUnBindVportWithIpHelper: %d", vport->portNo);
    ASSERT(switchContext->ipHelperBoundVportNo == vport->portNo);
    switchContext->ipHelperBoundVportNo = 0;
    OvsInternalAdapterDown(vport->portNo, vport->netCfgInstanceId);
}
