/** @file
  The implementation of the ARP protocol.

Copyright (c) 2006 - 2020, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "ArpImpl.h"

//
// Global variable of EFI ARP Protocol Interface.
//
EFI_ARP_PROTOCOL  mEfiArpProtocolTemplate = {
  ArpConfigure,
  ArpAdd,
  ArpFind,
  ArpDelete,
  ArpFlush,
  ArpRequest,
  ArpCancel
};

/**
  Initialize the instance context data.

  @param[in]   ArpService        Pointer to the arp service context data this
                                 instance belongs to.
  @param[out]  Instance          Pointer to the instance context data.

  @return None.

**/
VOID
ArpInitInstance (
  IN  ARP_SERVICE_DATA   *ArpService,
  OUT ARP_INSTANCE_DATA  *Instance
  )
{
  NET_CHECK_SIGNATURE (ArpService, ARP_SERVICE_DATA_SIGNATURE);

  Instance->Signature  = ARP_INSTANCE_DATA_SIGNATURE;
  Instance->ArpService = ArpService;

  CopyMem (&Instance->ArpProto, &mEfiArpProtocolTemplate, sizeof (Instance->ArpProto));

  Instance->Configured = FALSE;
  Instance->InDestroy  = FALSE;

  InitializeListHead (&Instance->List);
}

/**
  Process the Arp packets received from Mnp, the procedure conforms to RFC826.

  @param[in]  Context            Pointer to the context data registered to the
                                 Event.

  @return None.

**/
VOID
EFIAPI
ArpOnFrameRcvdDpc (
  IN VOID  *Context
  )
{
  EFI_STATUS                            Status;
  ARP_SERVICE_DATA                      *ArpService;
  EFI_MANAGED_NETWORK_COMPLETION_TOKEN  *RxToken;
  EFI_MANAGED_NETWORK_RECEIVE_DATA      *RxData;
  ARP_HEAD                              *Head;
  ARP_ADDRESS                           ArpAddress;
  ARP_CACHE_ENTRY                       *CacheEntry;
  LIST_ENTRY                            *Entry;
  ARP_INSTANCE_DATA                     *Instance;
  EFI_ARP_CONFIG_DATA                   *ConfigData;
  NET_ARP_ADDRESS                       SenderAddress[2];
  BOOLEAN                               ProtoMatched;
  BOOLEAN                               IsTarget;
  BOOLEAN                               MergeFlag;

  ArpService = (ARP_SERVICE_DATA *)Context;
  NET_CHECK_SIGNATURE (ArpService, ARP_SERVICE_DATA_SIGNATURE);

  RxToken = &ArpService->RxToken;

  if (RxToken->Status == EFI_ABORTED) {
    //
    // The Token is aborted, possibly by arp itself, just return and the receiving
    // process is stopped.
    //
    return;
  }

  if (EFI_ERROR (RxToken->Status)) {
    //
    // Restart the receiving if any other error Status occurs.
    //
    goto RESTART_RECEIVE;
  }

  //
  // Status is EFI_SUCCESS, process the received frame.
  //
  RxData = RxToken->Packet.RxData;
  //
  // Sanity check.
  //
  if (RxData->DataLength < sizeof (ARP_HEAD)) {
    //
    // Restart the receiving if packet size is not correct.
    //
    goto RECYCLE_RXDATA;
  }

  //
  // Convert the byte order of the multi-byte fields.
  //
  Head            = (ARP_HEAD *)RxData->PacketData;
  Head->HwType    = NTOHS (Head->HwType);
  Head->ProtoType = NTOHS (Head->ProtoType);
  Head->OpCode    = NTOHS (Head->OpCode);

  if (RxData->DataLength < (sizeof (ARP_HEAD) + 2 * Head->HwAddrLen + 2 * Head->ProtoAddrLen)) {
    goto RECYCLE_RXDATA;
  }

  if ((Head->HwType != ArpService->SnpMode.IfType) ||
      (Head->HwAddrLen != ArpService->SnpMode.HwAddressSize) ||
      (RxData->ProtocolType != ARP_ETHER_PROTO_TYPE))
  {
    //
    // The hardware type or the hardware address length doesn't match.
    // There is a sanity check for the protocol type too.
    //
    goto RECYCLE_RXDATA;
  }

  //
  // Set the pointers to the addresses contained in the arp packet.
  //
  ArpAddress.SenderHwAddr    = (UINT8 *)(Head + 1);
  ArpAddress.SenderProtoAddr = ArpAddress.SenderHwAddr + Head->HwAddrLen;
  ArpAddress.TargetHwAddr    = ArpAddress.SenderProtoAddr + Head->ProtoAddrLen;
  ArpAddress.TargetProtoAddr = ArpAddress.TargetHwAddr + Head->HwAddrLen;

  SenderAddress[Hardware].Type       = Head->HwType;
  SenderAddress[Hardware].Length     = Head->HwAddrLen;
  SenderAddress[Hardware].AddressPtr = ArpAddress.SenderHwAddr;

  SenderAddress[Protocol].Type       = Head->ProtoType;
  SenderAddress[Protocol].Length     = Head->ProtoAddrLen;
  SenderAddress[Protocol].AddressPtr = ArpAddress.SenderProtoAddr;

  //
  // First, check the denied cache table.
  //
  CacheEntry = ArpFindDeniedCacheEntry (
                 ArpService,
                 &SenderAddress[Protocol],
                 &SenderAddress[Hardware]
                 );
  if (CacheEntry != NULL) {
    //
    // This address (either hardware or protocol address, or both) is configured to
    // be a deny entry, silently skip the normal process.
    //
    goto RECYCLE_RXDATA;
  }

  ProtoMatched = FALSE;
  IsTarget     = FALSE;
  Instance     = NULL;
  NET_LIST_FOR_EACH (Entry, &ArpService->ChildrenList) {
    //
    // Iterate all the children.
    //
    Instance = NET_LIST_USER_STRUCT (Entry, ARP_INSTANCE_DATA, List);
    NET_CHECK_SIGNATURE (Instance, ARP_INSTANCE_DATA_SIGNATURE);
    ConfigData = &Instance->ConfigData;

    if ((Instance->Configured) &&
        (Head->ProtoType == ConfigData->SwAddressType) &&
        (Head->ProtoAddrLen == ConfigData->SwAddressLength))
    {
      //
      // The protocol type is matched for the received arp packet.
      //
      ProtoMatched = TRUE;
      if (0 == CompareMem (
                 (VOID *)ArpAddress.TargetProtoAddr,
                 ConfigData->StationAddress,
                 ConfigData->SwAddressLength
                 ))
      {
        //
        // The arp driver has the target address required by the received arp packet.
        //
        IsTarget = TRUE;
        break;
      }
    }
  }

  if (!ProtoMatched) {
    //
    // Protocol type unmatchable, skip.
    //
    goto RECYCLE_RXDATA;
  }

  //
  // Check whether the sender's address information is already in the cache.
  //
  MergeFlag  = FALSE;
  CacheEntry = ArpFindNextCacheEntryInTable (
                 &ArpService->ResolvedCacheTable,
                 NULL,
                 ByProtoAddress,
                 &SenderAddress[Protocol],
                 NULL
                 );
  if (CacheEntry != NULL) {
    //
    // Update the entry with the new information.
    //
    ArpFillAddressInCacheEntry (CacheEntry, &SenderAddress[Hardware], NULL);
    CacheEntry->DecayTime = CacheEntry->DefaultDecayTime;
    MergeFlag             = TRUE;
  }

  if (!IsTarget) {
    //
    // This arp packet isn't targeted to us, skip now.
    //
    goto RECYCLE_RXDATA;
  }

  if (!MergeFlag) {
    //
    // Add the triplet <protocol type, sender protocol address, sender hardware address>
    // to the translation table.
    //
    CacheEntry = ArpFindNextCacheEntryInTable (
                   &ArpService->PendingRequestTable,
                   NULL,
                   ByProtoAddress,
                   &SenderAddress[Protocol],
                   NULL
                   );
    if (CacheEntry == NULL) {
      //
      // Allocate a new CacheEntry.
      //
      CacheEntry = ArpAllocCacheEntry (NULL);
      if (CacheEntry == NULL) {
        goto RECYCLE_RXDATA;
      }
    }

    if (!IsListEmpty (&CacheEntry->List)) {
      RemoveEntryList (&CacheEntry->List);
    }

    //
    // Fill the addresses into the CacheEntry.
    //
    ArpFillAddressInCacheEntry (
      CacheEntry,
      &SenderAddress[Hardware],
      &SenderAddress[Protocol]
      );

    //
    // Inform the user.
    //
    ArpAddressResolved (CacheEntry, NULL, NULL);

    //
    // Add this entry into the ResolvedCacheTable
    //
    InsertHeadList (&ArpService->ResolvedCacheTable, &CacheEntry->List);
  }

  if (Head->OpCode == ARP_OPCODE_REQUEST) {
    //
    // Send back the ARP Reply. If we reach here, Instance is not NULL and CacheEntry
    // is not NULL.
    //
    ArpSendFrame (Instance, CacheEntry, ARP_OPCODE_REPLY);
  }

RECYCLE_RXDATA:

  //
  // Signal Mnp to recycle the RxData.
  //
  gBS->SignalEvent (RxData->RecycleEvent);

RESTART_RECEIVE:

  //
  // Continue to receive packets from Mnp.
  //
  Status = ArpService->Mnp->Receive (ArpService->Mnp, RxToken);

  DEBUG_CODE_BEGIN ();
  if (EFI_ERROR (Status)) {
    DEBUG ((
      DEBUG_ERROR,
      "ArpOnFrameRcvd: ArpService->Mnp->Receive "
      "failed, %r\n.",
      Status
      ));
  }

  DEBUG_CODE_END ();
}

/**
  Queue ArpOnFrameRcvdDpc as a DPC at TPL_CALLBACK.

  @param[in]  Event                  The Event this notify function registered to.
  @param[in]  Context                Pointer to the context data registered to the
                                     Event.

  @return None.

**/
VOID
EFIAPI
ArpOnFrameRcvd (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  //
  // Request ArpOnFrameRcvdDpc as a DPC at TPL_CALLBACK
  //
  QueueDpc (TPL_CALLBACK, ArpOnFrameRcvdDpc, Context);
}

/**
  Process the already sent arp packets.

  @param[in]  Context                Pointer to the context data registered to the
                                     Event.

  @return None.

**/
VOID
EFIAPI
ArpOnFrameSentDpc (
  IN VOID  *Context
  )
{
  EFI_MANAGED_NETWORK_COMPLETION_TOKEN  *TxToken;
  EFI_MANAGED_NETWORK_TRANSMIT_DATA     *TxData;

  ASSERT (Context != NULL);

  TxToken = (EFI_MANAGED_NETWORK_COMPLETION_TOKEN *)Context;
  TxData  = TxToken->Packet.TxData;

  DEBUG_CODE_BEGIN ();
  if (EFI_ERROR (TxToken->Status)) {
    DEBUG ((DEBUG_ERROR, "ArpOnFrameSent: TxToken->Status, %r.\n", TxToken->Status));
  }

  DEBUG_CODE_END ();

  //
  // Free the allocated memory and close the event.
  //
  FreePool (TxData->FragmentTable[0].FragmentBuffer);
  FreePool (TxData);
  gBS->CloseEvent (TxToken->Event);
  FreePool (TxToken);
}

/**
  Request ArpOnFrameSentDpc as a DPC at TPL_CALLBACK.

  @param[in]  Event                  The Event this notify function registered to.
  @param[in]  Context                Pointer to the context data registered to the
                                     Event.

  @return None.

**/
VOID
EFIAPI
ArpOnFrameSent (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  //
  // Request ArpOnFrameSentDpc as a DPC at TPL_CALLBACK
  //
  QueueDpc (TPL_CALLBACK, ArpOnFrameSentDpc, Context);
}

/**
  Process the arp cache olding and drive the retrying arp requests.

  @param[in]  Event                  The Event this notify function registered to.
  @param[in]  Context                Pointer to the context data registered to the
                                     Event.

  @return None.

**/
VOID
EFIAPI
ArpTimerHandler (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  ARP_SERVICE_DATA      *ArpService;
  LIST_ENTRY            *Entry;
  LIST_ENTRY            *NextEntry;
  LIST_ENTRY            *ContextEntry;
  ARP_CACHE_ENTRY       *CacheEntry;
  USER_REQUEST_CONTEXT  *RequestContext;

  ASSERT (Context != NULL);
  ArpService = (ARP_SERVICE_DATA *)Context;

  //
  // Iterate all the pending requests to see whether a retry is needed to send out
  // or the request finally fails because the retry time reaches the limitation.
  //
  NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &ArpService->PendingRequestTable) {
    CacheEntry = NET_LIST_USER_STRUCT (Entry, ARP_CACHE_ENTRY, List);

    if (CacheEntry->NextRetryTime <= ARP_PERIODIC_TIMER_INTERVAL) {
      //
      // Timeout, if we can retry more, send out the request again, otherwise abort
      // this request.
      //
      if (CacheEntry->RetryCount == 0) {
        //
        // Abort this request.
        //
        ArpAddressResolved (CacheEntry, NULL, NULL);
        ASSERT (IsListEmpty (&CacheEntry->UserRequestList));

        RemoveEntryList (&CacheEntry->List);
        FreePool (CacheEntry);
      } else {
        //
        // resend the ARP request.
        //
        ASSERT (!IsListEmpty (&CacheEntry->UserRequestList));

        ContextEntry   = CacheEntry->UserRequestList.ForwardLink;
        RequestContext = NET_LIST_USER_STRUCT (ContextEntry, USER_REQUEST_CONTEXT, List);

        ArpSendFrame (RequestContext->Instance, CacheEntry, ARP_OPCODE_REQUEST);

        CacheEntry->RetryCount--;
        CacheEntry->NextRetryTime = RequestContext->Instance->ConfigData.RetryTimeOut;
      }
    } else {
      //
      // Update the NextRetryTime.
      //
      CacheEntry->NextRetryTime -= ARP_PERIODIC_TIMER_INTERVAL;
    }
  }

  //
  // Check the timeouts for the DeniedCacheTable.
  //
  NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &ArpService->DeniedCacheTable) {
    CacheEntry = NET_LIST_USER_STRUCT (Entry, ARP_CACHE_ENTRY, List);
    ASSERT (IsListEmpty (&CacheEntry->UserRequestList));

    if (CacheEntry->DefaultDecayTime == 0) {
      //
      // It's a static entry, skip it.
      //
      continue;
    }

    if (CacheEntry->DecayTime <= ARP_PERIODIC_TIMER_INTERVAL) {
      //
      // Time out, remove it.
      //
      RemoveEntryList (&CacheEntry->List);
      FreePool (CacheEntry);
    } else {
      //
      // Update the DecayTime.
      //
      CacheEntry->DecayTime -= ARP_PERIODIC_TIMER_INTERVAL;
    }
  }

  //
  // Check the timeouts for the ResolvedCacheTable.
  //
  NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &ArpService->ResolvedCacheTable) {
    CacheEntry = NET_LIST_USER_STRUCT (Entry, ARP_CACHE_ENTRY, List);
    ASSERT (IsListEmpty (&CacheEntry->UserRequestList));

    if (CacheEntry->DefaultDecayTime == 0) {
      //
      // It's a static entry, skip it.
      //
      continue;
    }

    if (CacheEntry->DecayTime <= ARP_PERIODIC_TIMER_INTERVAL) {
      //
      // Time out, remove it.
      //
      RemoveEntryList (&CacheEntry->List);
      FreePool (CacheEntry);
    } else {
      //
      // Update the DecayTime.
      //
      CacheEntry->DecayTime -= ARP_PERIODIC_TIMER_INTERVAL;
    }
  }
}

/**
  Match the two NET_ARP_ADDRESSes.

  @param[in]  AddressOne             Pointer to the first address to match.
  @param[in]  AddressTwo             Pointer to the second address to match.

  @return The two addresses match or not.

**/
BOOLEAN
ArpMatchAddress (
  IN NET_ARP_ADDRESS  *AddressOne,
  IN NET_ARP_ADDRESS  *AddressTwo
  )
{
  ASSERT (AddressOne != NULL && AddressTwo != NULL);

  if ((AddressOne->Type != AddressTwo->Type) ||
      (AddressOne->Length != AddressTwo->Length))
  {
    //
    // Either Type or Length doesn't match.
    //
    return FALSE;
  }

  if ((AddressOne->AddressPtr != NULL) &&
      (CompareMem (
         AddressOne->AddressPtr,
         AddressTwo->AddressPtr,
         AddressOne->Length
         ) != 0))
  {
    //
    // The address is not the same.
    //
    return FALSE;
  }

  return TRUE;
}

/**
  Find the CacheEntry which matches the requirements in the specified CacheTable.

  @param[in]  CacheTable             Pointer to the arp cache table.
  @param[in]  StartEntry             Pointer to the start entry this search begins with
                                     in the cache table.
  @param[in]  FindOpType             The search type.
  @param[in]  ProtocolAddress        Pointer to the protocol address to match.
  @param[in]  HardwareAddress        Pointer to the hardware address to match.

  @return Pointer to the matched arp cache entry, if NULL, no match is found.

**/
ARP_CACHE_ENTRY *
ArpFindNextCacheEntryInTable (
  IN LIST_ENTRY       *CacheTable,
  IN LIST_ENTRY       *StartEntry,
  IN FIND_OPTYPE      FindOpType,
  IN NET_ARP_ADDRESS  *ProtocolAddress OPTIONAL,
  IN NET_ARP_ADDRESS  *HardwareAddress OPTIONAL
  )
{
  LIST_ENTRY       *Entry;
  ARP_CACHE_ENTRY  *CacheEntry;

  if (StartEntry == NULL) {
    //
    // Start from the beginning of the table if no StartEntry is specified.
    //
    StartEntry = CacheTable;
  }

  for (Entry = StartEntry->ForwardLink; Entry != CacheTable; Entry = Entry->ForwardLink) {
    CacheEntry = NET_LIST_USER_STRUCT (Entry, ARP_CACHE_ENTRY, List);

    if ((FindOpType & MATCH_SW_ADDRESS) != 0) {
      //
      // Find by the software address.
      //
      if (!ArpMatchAddress (ProtocolAddress, &CacheEntry->Addresses[Protocol])) {
        //
        // The ProtocolAddress doesn't match, continue to the next cache entry.
        //
        continue;
      }
    }

    if ((FindOpType & MATCH_HW_ADDRESS) != 0) {
      //
      // Find by the hardware address.
      //
      if (!ArpMatchAddress (HardwareAddress, &CacheEntry->Addresses[Hardware])) {
        //
        // The HardwareAddress doesn't match, continue to the next cache entry.
        //
        continue;
      }
    }

    //
    // The CacheEntry meets the requirements now, return this entry.
    //
    return CacheEntry;
  }

  //
  // No matching.
  //
  return NULL;
}

/**
  Find the CacheEntry, using ProtocolAddress or HardwareAddress or both, as the keyword,
  in the DeniedCacheTable.

  @param[in]  ArpService             Pointer to the arp service context data.
  @param[in]  ProtocolAddress        Pointer to the protocol address.
  @param[in]  HardwareAddress        Pointer to the hardware address.

  @return Pointer to the matched cache entry, if NULL no match is found.

**/
ARP_CACHE_ENTRY *
ArpFindDeniedCacheEntry (
  IN ARP_SERVICE_DATA  *ArpService,
  IN NET_ARP_ADDRESS   *ProtocolAddress OPTIONAL,
  IN NET_ARP_ADDRESS   *HardwareAddress OPTIONAL
  )
{
  ARP_CACHE_ENTRY  *CacheEntry;

  ASSERT ((ProtocolAddress != NULL) || (HardwareAddress != NULL));
  NET_CHECK_SIGNATURE (ArpService, ARP_SERVICE_DATA_SIGNATURE);

  CacheEntry = NULL;

  if ((ProtocolAddress != NULL) && (ProtocolAddress->AddressPtr != NULL)) {
    //
    // Find the cache entry in the DeniedCacheTable by the protocol address.
    //
    CacheEntry = ArpFindNextCacheEntryInTable (
                   &ArpService->DeniedCacheTable,
                   NULL,
                   ByProtoAddress,
                   ProtocolAddress,
                   NULL
                   );
    if (CacheEntry != NULL) {
      //
      // There is a match.
      //
      return CacheEntry;
    }
  }

  if ((HardwareAddress != NULL) && (HardwareAddress->AddressPtr != NULL)) {
    //
    // Find the cache entry in the DeniedCacheTable by the hardware address.
    //
    CacheEntry = ArpFindNextCacheEntryInTable (
                   &ArpService->DeniedCacheTable,
                   NULL,
                   ByHwAddress,
                   NULL,
                   HardwareAddress
                   );
  }

  return CacheEntry;
}

/**
  Allocate a cache entry and initialize it.

  @param[in]  Instance               Pointer to the instance context data.

  @return Pointer to the new created cache entry.

**/
ARP_CACHE_ENTRY *
ArpAllocCacheEntry (
  IN ARP_INSTANCE_DATA  *Instance
  )
{
  ARP_CACHE_ENTRY  *CacheEntry;
  NET_ARP_ADDRESS  *Address;
  UINT16           Index;

  //
  // Allocate memory for the cache entry.
  //
  CacheEntry = AllocatePool (sizeof (ARP_CACHE_ENTRY));
  if (CacheEntry == NULL) {
    return NULL;
  }

  //
  // Init the lists.
  //
  InitializeListHead (&CacheEntry->List);
  InitializeListHead (&CacheEntry->UserRequestList);

  for (Index = 0; Index < 2; Index++) {
    //
    // Init the address pointers to point to the concrete buffer.
    //
    Address             = &CacheEntry->Addresses[Index];
    Address->AddressPtr = Address->Buffer.ProtoAddress;
  }

  //
  // Zero the hardware address first.
  //
  ZeroMem (CacheEntry->Addresses[Hardware].AddressPtr, ARP_MAX_HARDWARE_ADDRESS_LEN);

  if (Instance != NULL) {
    //
    // Inherit the parameters from the instance configuration.
    //
    CacheEntry->RetryCount       = Instance->ConfigData.RetryCount;
    CacheEntry->NextRetryTime    = Instance->ConfigData.RetryTimeOut;
    CacheEntry->DefaultDecayTime = Instance->ConfigData.EntryTimeOut;
    CacheEntry->DecayTime        = Instance->ConfigData.EntryTimeOut;
  } else {
    //
    // Use the default parameters if this cache entry isn't allocate in a
    // instance's  scope.
    //
    CacheEntry->RetryCount       = ARP_DEFAULT_RETRY_COUNT;
    CacheEntry->NextRetryTime    = ARP_DEFAULT_RETRY_INTERVAL;
    CacheEntry->DefaultDecayTime = ARP_DEFAULT_TIMEOUT_VALUE;
    CacheEntry->DecayTime        = ARP_DEFAULT_TIMEOUT_VALUE;
  }

  return CacheEntry;
}

/**
  Turn the CacheEntry into the resolved status.

  @param[in]  CacheEntry             Pointer to the resolved cache entry.
  @param[in]  Instance               Pointer to the instance context data.
  @param[in]  UserEvent              Pointer to the UserEvent to notify.

  @return The count of notifications sent to the instance.

**/
UINTN
ArpAddressResolved (
  IN ARP_CACHE_ENTRY    *CacheEntry,
  IN ARP_INSTANCE_DATA  *Instance OPTIONAL,
  IN EFI_EVENT          UserEvent OPTIONAL
  )
{
  LIST_ENTRY            *Entry;
  LIST_ENTRY            *NextEntry;
  USER_REQUEST_CONTEXT  *Context;
  UINTN                 Count;

  Count = 0;

  //
  // Iterate all the linked user requests to notify them.
  //
  NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &CacheEntry->UserRequestList) {
    Context = NET_LIST_USER_STRUCT (Entry, USER_REQUEST_CONTEXT, List);

    if (((Instance == NULL) || (Context->Instance == Instance)) &&
        ((UserEvent == NULL) || (Context->UserRequestEvent == UserEvent)))
    {
      //
      // Copy the address to the user-provided buffer and notify the user.
      //
      CopyMem (
        Context->UserHwAddrBuffer,
        CacheEntry->Addresses[Hardware].AddressPtr,
        CacheEntry->Addresses[Hardware].Length
        );
      gBS->SignalEvent (Context->UserRequestEvent);

      //
      // Remove this user request and free the context data.
      //
      RemoveEntryList (&Context->List);
      FreePool (Context);

      Count++;
    }
  }

  //
  // Dispatch the DPCs queued by the NotifyFunction of the Context->UserRequestEvent.
  //
  DispatchDpc ();

  return Count;
}

/**
  Fill the addresses in the CacheEntry using the information passed in by
  HwAddr and SwAddr.

  @param[in]  CacheEntry             Pointer to the cache entry.
  @param[in]  HwAddr                 Pointer to the software address.
  @param[in]  SwAddr                 Pointer to the hardware address.

  @return None.

**/
VOID
ArpFillAddressInCacheEntry (
  IN ARP_CACHE_ENTRY  *CacheEntry,
  IN NET_ARP_ADDRESS  *HwAddr OPTIONAL,
  IN NET_ARP_ADDRESS  *SwAddr OPTIONAL
  )
{
  NET_ARP_ADDRESS  *Address[2];
  NET_ARP_ADDRESS  *CacheAddress;
  UINT32           Index;

  Address[Hardware] = HwAddr;
  Address[Protocol] = SwAddr;

  for (Index = 0; Index < 2; Index++) {
    if (Address[Index] != NULL) {
      //
      // Fill the address if the passed in pointer is not NULL.
      //
      CacheAddress = &CacheEntry->Addresses[Index];

      CacheAddress->Type   = Address[Index]->Type;
      CacheAddress->Length = Address[Index]->Length;

      if (Address[Index]->AddressPtr != NULL) {
        //
        // Copy it if the AddressPtr points to some buffer.
        //
        CopyMem (
          CacheAddress->AddressPtr,
          Address[Index]->AddressPtr,
          CacheAddress->Length
          );
      } else {
        //
        // Zero the corresponding address buffer in the CacheEntry.
        //
        ZeroMem (CacheAddress->AddressPtr, CacheAddress->Length);
      }
    }
  }
}

/**
  Configure the instance using the ConfigData. ConfigData is already validated.

  @param[in]  Instance           Pointer to the instance context data to be
                                 configured.
  @param[in]  ConfigData         Pointer to the configuration data used to
                                 configure the instance.

  @retval EFI_SUCCESS            The instance is configured with the ConfigData.
  @retval EFI_ACCESS_DENIED      The instance is already configured and the
                                 ConfigData tries to reset some unchangeable
                                 fields.
  @retval EFI_INVALID_PARAMETER  The ConfigData provides a non-unicast IPv4 address
                                 when the SwAddressType is IPv4.
  @retval EFI_OUT_OF_RESOURCES   The instance fails to configure due to memory
                                 limitation.

**/
EFI_STATUS
ArpConfigureInstance (
  IN ARP_INSTANCE_DATA    *Instance,
  IN EFI_ARP_CONFIG_DATA  *ConfigData OPTIONAL
  )
{
  EFI_ARP_CONFIG_DATA  *OldConfigData;
  IP4_ADDR             Ip;

  OldConfigData = &Instance->ConfigData;

  if (ConfigData != NULL) {
    if (Instance->Configured) {
      //
      // The instance is configured, check the unchangeable fields.
      //
      if ((OldConfigData->SwAddressType != ConfigData->SwAddressType) ||
          (OldConfigData->SwAddressLength != ConfigData->SwAddressLength) ||
          (CompareMem (
             OldConfigData->StationAddress,
             ConfigData->StationAddress,
             OldConfigData->SwAddressLength
             ) != 0))
      {
        //
        // Deny the unallowed changes.
        //
        return EFI_ACCESS_DENIED;
      }
    } else {
      //
      // The instance is not configured.
      //

      if (ConfigData->SwAddressType == IPV4_ETHER_PROTO_TYPE) {
        CopyMem (&Ip, ConfigData->StationAddress, sizeof (IP4_ADDR));

        if (IP4_IS_UNSPECIFIED (Ip) || IP4_IS_LOCAL_BROADCAST (Ip)) {
          //
          // The station address should not be zero or broadcast address.
          //
          return EFI_INVALID_PARAMETER;
        }
      }

      //
      // Save the configuration.
      //
      CopyMem (OldConfigData, ConfigData, sizeof (*OldConfigData));

      OldConfigData->StationAddress = AllocatePool (OldConfigData->SwAddressLength);
      if (OldConfigData->StationAddress == NULL) {
        DEBUG ((
          DEBUG_ERROR,
          "ArpConfigInstance: AllocatePool for the StationAddress "
          "failed.\n"
          ));
        return EFI_OUT_OF_RESOURCES;
      }

      //
      // Save the StationAddress.
      //
      CopyMem (
        OldConfigData->StationAddress,
        ConfigData->StationAddress,
        OldConfigData->SwAddressLength
        );

      //
      // Set the state to configured.
      //
      Instance->Configured = TRUE;
    }

    //
    // Use the implementation specific values if the following field is zero.
    //
    OldConfigData->EntryTimeOut = (ConfigData->EntryTimeOut == 0) ?
                                  ARP_DEFAULT_TIMEOUT_VALUE : ConfigData->EntryTimeOut;

    OldConfigData->RetryCount = (ConfigData->RetryCount == 0) ?
                                ARP_DEFAULT_RETRY_COUNT : ConfigData->RetryCount;

    OldConfigData->RetryTimeOut = (ConfigData->RetryTimeOut == 0) ?
                                  ARP_DEFAULT_RETRY_INTERVAL : ConfigData->RetryTimeOut;
  } else {
    //
    // Reset the configuration.
    //

    if (Instance->Configured) {
      //
      // Cancel the arp requests issued by this instance.
      //
      Instance->ArpProto.Cancel (&Instance->ArpProto, NULL, NULL);

      //
      // Free the buffer previously allocated to hold the station address.
      //
      FreePool (OldConfigData->StationAddress);
    }

    Instance->Configured = FALSE;
  }

  return EFI_SUCCESS;
}

/**
  Send out an arp frame using the CacheEntry and the ArpOpCode.

  @param[in]  Instance               Pointer to the instance context data.
  @param[in]  CacheEntry             Pointer to the configuration data used to
                                     configure the instance.
  @param[in]  ArpOpCode              The opcode used to send out this Arp frame, either
                                     request or reply.

  @return None.

**/
VOID
ArpSendFrame (
  IN ARP_INSTANCE_DATA  *Instance,
  IN ARP_CACHE_ENTRY    *CacheEntry,
  IN UINT16             ArpOpCode
  )
{
  EFI_STATUS                            Status;
  EFI_MANAGED_NETWORK_COMPLETION_TOKEN  *TxToken;
  EFI_MANAGED_NETWORK_TRANSMIT_DATA     *TxData;
  UINT32                                TotalLength;
  UINT8                                 *Packet;
  ARP_SERVICE_DATA                      *ArpService;
  EFI_SIMPLE_NETWORK_MODE               *SnpMode;
  EFI_ARP_CONFIG_DATA                   *ConfigData;
  UINT8                                 *TmpPtr;
  ARP_HEAD                              *ArpHead;

  ASSERT ((Instance != NULL) && (CacheEntry != NULL));

  //
  // Allocate memory for the TxToken.
  //
  TxToken = AllocatePool (sizeof (EFI_MANAGED_NETWORK_COMPLETION_TOKEN));
  if (TxToken == NULL) {
    DEBUG ((DEBUG_ERROR, "ArpSendFrame: Allocate memory for TxToken failed.\n"));
    return;
  }

  TxToken->Event = NULL;
  TxData         = NULL;
  Packet         = NULL;

  //
  // Create the event for this TxToken.
  //
  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  ArpOnFrameSent,
                  (VOID *)TxToken,
                  &TxToken->Event
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "ArpSendFrame: CreateEvent failed for TxToken->Event.\n"));
    goto CLEAN_EXIT;
  }

  //
  // Allocate memory for the TxData used in the TxToken.
  //
  TxData = AllocatePool (sizeof (EFI_MANAGED_NETWORK_TRANSMIT_DATA));
  if (TxData == NULL) {
    DEBUG ((DEBUG_ERROR, "ArpSendFrame: Allocate memory for TxData failed.\n"));
    goto CLEAN_EXIT;
  }

  ArpService = Instance->ArpService;
  SnpMode    = &ArpService->SnpMode;
  ConfigData = &Instance->ConfigData;

  //
  // Calculate the buffer length for this arp frame.
  //
  TotalLength = SnpMode->MediaHeaderSize + sizeof (ARP_HEAD) +
                2 * (ConfigData->SwAddressLength + SnpMode->HwAddressSize);

  //
  // Allocate buffer for the arp frame.
  //
  Packet = AllocatePool (TotalLength);
  if (Packet == NULL) {
    DEBUG ((DEBUG_ERROR, "ArpSendFrame: Allocate memory for Packet failed.\n"));
    ASSERT (Packet != NULL);
  }

  TmpPtr = Packet;

  //
  // The destination MAC address.
  //
  if (ArpOpCode == ARP_OPCODE_REQUEST) {
    CopyMem (TmpPtr, &SnpMode->BroadcastAddress, SnpMode->HwAddressSize);
  } else {
    CopyMem (
      TmpPtr,
      CacheEntry->Addresses[Hardware].AddressPtr,
      SnpMode->HwAddressSize
      );
  }

  TmpPtr += SnpMode->HwAddressSize;

  //
  // The source MAC address.
  //
  CopyMem (TmpPtr, &SnpMode->CurrentAddress, SnpMode->HwAddressSize);
  TmpPtr += SnpMode->HwAddressSize;

  //
  // The ethernet protocol type.
  //
  *(UINT16 *)TmpPtr = HTONS (ARP_ETHER_PROTO_TYPE);
  TmpPtr           += 2;

  //
  // The ARP Head.
  //
  ArpHead               = (ARP_HEAD *)TmpPtr;
  ArpHead->HwType       = HTONS ((UINT16)SnpMode->IfType);
  ArpHead->ProtoType    = HTONS (ConfigData->SwAddressType);
  ArpHead->HwAddrLen    = (UINT8)SnpMode->HwAddressSize;
  ArpHead->ProtoAddrLen = ConfigData->SwAddressLength;
  ArpHead->OpCode       = HTONS (ArpOpCode);
  TmpPtr               += sizeof (ARP_HEAD);

  //
  // The sender hardware address.
  //
  CopyMem (TmpPtr, &SnpMode->CurrentAddress, SnpMode->HwAddressSize);
  TmpPtr += SnpMode->HwAddressSize;

  //
  // The sender protocol address.
  //
  CopyMem (TmpPtr, ConfigData->StationAddress, ConfigData->SwAddressLength);
  TmpPtr += ConfigData->SwAddressLength;

  //
  // The target hardware address.
  //
  CopyMem (
    TmpPtr,
    CacheEntry->Addresses[Hardware].AddressPtr,
    SnpMode->HwAddressSize
    );
  TmpPtr += SnpMode->HwAddressSize;

  //
  // The target protocol address.
  //
  CopyMem (
    TmpPtr,
    CacheEntry->Addresses[Protocol].AddressPtr,
    ConfigData->SwAddressLength
    );

  //
  // Set all the fields of the TxData.
  //
  TxData->DestinationAddress = NULL;
  TxData->SourceAddress      = NULL;
  TxData->ProtocolType       = 0;
  TxData->DataLength         = TotalLength - SnpMode->MediaHeaderSize;
  TxData->HeaderLength       = (UINT16)SnpMode->MediaHeaderSize;
  TxData->FragmentCount      = 1;

  TxData->FragmentTable[0].FragmentBuffer = Packet;
  TxData->FragmentTable[0].FragmentLength = TotalLength;

  //
  // Associate the TxData with the TxToken.
  //
  TxToken->Packet.TxData = TxData;
  TxToken->Status        = EFI_NOT_READY;

  //
  // Send out this arp packet by Mnp.
  //
  Status = ArpService->Mnp->Transmit (ArpService->Mnp, TxToken);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Mnp->Transmit failed, %r.\n", Status));
    goto CLEAN_EXIT;
  }

  return;

CLEAN_EXIT:

  if (Packet != NULL) {
    FreePool (Packet);
  }

  if (TxData != NULL) {
    FreePool (TxData);
  }

  if (TxToken->Event != NULL) {
    gBS->CloseEvent (TxToken->Event);
  }

  FreePool (TxToken);
}

/**
  Delete the cache entries in the specified CacheTable, using the BySwAddress,
  SwAddressType, AddressBuffer combination as the matching key, if Force is TRUE,
  the cache is deleted event it's a static entry.

  @param[in]  CacheTable             Pointer to the cache table to do the deletion.
  @param[in]  BySwAddress            Delete the cache entry by software address or by
                                     hardware address.
  @param[in]  SwAddressType          The software address used to do the deletion.
  @param[in]  AddressBuffer          Pointer to the buffer containing the address to
                                     match for the deletion.
  @param[in]  Force                  This deletion is forced or not.

  @return The count of the deleted cache entries.

**/
UINTN
ArpDeleteCacheEntryInTable (
  IN LIST_ENTRY  *CacheTable,
  IN BOOLEAN     BySwAddress,
  IN UINT16      SwAddressType,
  IN UINT8       *AddressBuffer OPTIONAL,
  IN BOOLEAN     Force
  )
{
  LIST_ENTRY       *Entry;
  LIST_ENTRY       *NextEntry;
  ARP_CACHE_ENTRY  *CacheEntry;
  UINTN            Count;

  Count = 0;

  NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, CacheTable) {
    CacheEntry = NET_LIST_USER_STRUCT (Entry, ARP_CACHE_ENTRY, List);

    if ((CacheEntry->DefaultDecayTime == 0) && !Force) {
      //
      // It's a static entry and we are not forced to delete it, skip.
      //
      continue;
    }

    if (BySwAddress) {
      if (SwAddressType == CacheEntry->Addresses[Protocol].Type) {
        //
        // Protocol address type matched. Check the address.
        //
        if ((AddressBuffer == NULL) ||
            (CompareMem (
               AddressBuffer,
               CacheEntry->Addresses[Protocol].AddressPtr,
               CacheEntry->Addresses[Protocol].Length
               ) == 0))
        {
          //
          // Address matched.
          //
          goto MATCHED;
        }
      }
    } else {
      if ((AddressBuffer == NULL) ||
          (CompareMem (
             AddressBuffer,
             CacheEntry->Addresses[Hardware].AddressPtr,
             CacheEntry->Addresses[Hardware].Length
             ) == 0))
      {
        //
        // Address matched.
        //
        goto MATCHED;
      }
    }

    continue;

MATCHED:

    //
    // Delete this entry.
    //
    RemoveEntryList (&CacheEntry->List);
    ASSERT (IsListEmpty (&CacheEntry->UserRequestList));
    FreePool (CacheEntry);

    Count++;
  }

  return Count;
}

/**
  Delete cache entries in all the cache tables.

  @param[in]  Instance               Pointer to the instance context data.
  @param[in]  BySwAddress            Delete the cache entry by software address or by
                                     hardware address.
  @param[in]  AddressBuffer          Pointer to the buffer containing the address to
                                     match for the deletion.
  @param[in]  Force                  This deletion is forced or not.

  @return The count of the deleted cache entries.

**/
UINTN
ArpDeleteCacheEntry (
  IN ARP_INSTANCE_DATA  *Instance,
  IN BOOLEAN            BySwAddress,
  IN UINT8              *AddressBuffer OPTIONAL,
  IN BOOLEAN            Force
  )
{
  ARP_SERVICE_DATA  *ArpService;
  UINTN             Count;

  NET_CHECK_SIGNATURE (Instance, ARP_INSTANCE_DATA_SIGNATURE);

  ArpService = Instance->ArpService;

  //
  // Delete the cache entries in the DeniedCacheTable.
  //
  Count = ArpDeleteCacheEntryInTable (
            &ArpService->DeniedCacheTable,
            BySwAddress,
            Instance->ConfigData.SwAddressType,
            AddressBuffer,
            Force
            );

  //
  // Delete the cache entries in the ResolvedCacheTable.
  //
  Count += ArpDeleteCacheEntryInTable (
             &ArpService->ResolvedCacheTable,
             BySwAddress,
             Instance->ConfigData.SwAddressType,
             AddressBuffer,
             Force
             );

  return Count;
}

/**
  Cancel the arp request.

  @param[in]  Instance               Pointer to the instance context data.
  @param[in]  TargetSwAddress        Pointer to the buffer containing the target
                                     software address to match the arp request.
  @param[in]  UserEvent              The user event used to notify this request
                                     cancellation.

  @return The count of the cancelled requests.

**/
UINTN
ArpCancelRequest (
  IN ARP_INSTANCE_DATA  *Instance,
  IN VOID               *TargetSwAddress OPTIONAL,
  IN EFI_EVENT          UserEvent        OPTIONAL
  )
{
  ARP_SERVICE_DATA  *ArpService;
  LIST_ENTRY        *Entry;
  LIST_ENTRY        *NextEntry;
  ARP_CACHE_ENTRY   *CacheEntry;
  UINTN             Count;

  NET_CHECK_SIGNATURE (Instance, ARP_INSTANCE_DATA_SIGNATURE);

  ArpService = Instance->ArpService;

  Count = 0;
  NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &ArpService->PendingRequestTable) {
    CacheEntry = NET_LIST_USER_STRUCT (Entry, ARP_CACHE_ENTRY, List);

    if ((TargetSwAddress == NULL) ||
        (CompareMem (
           TargetSwAddress,
           CacheEntry->Addresses[Protocol].AddressPtr,
           CacheEntry->Addresses[Protocol].Length
           ) == 0))
    {
      //
      // This request entry matches the TargetSwAddress or all requests are to be
      // cancelled as TargetSwAddress is NULL.
      //
      Count += ArpAddressResolved (CacheEntry, Instance, UserEvent);

      if (IsListEmpty (&CacheEntry->UserRequestList)) {
        //
        // No user requests any more, remove this request cache entry.
        //
        RemoveEntryList (&CacheEntry->List);
        FreePool (CacheEntry);
      }
    }
  }

  return Count;
}

/**
  Find the cache entry in the cache table.

  @param[in]  Instance           Pointer to the instance context data.
  @param[in]  BySwAddress        Set to TRUE to look for matching software protocol
                                 addresses. Set to FALSE to look for matching
                                 hardware protocol addresses.
  @param[in]  AddressBuffer      Pointer to address buffer. Set to NULL to match
                                 all addresses.
  @param[out] EntryLength        The size of an entry in the entries buffer.
  @param[out] EntryCount         The number of ARP cache entries that are found by
                                 the specified criteria.
  @param[out] Entries            Pointer to the buffer that will receive the ARP
                                 cache entries.
  @param[in]  Refresh            Set to TRUE to refresh the timeout value of the
                                 matching ARP cache entry.

  @retval EFI_SUCCESS            The requested ARP cache entries are copied into
                                 the buffer.
  @retval EFI_NOT_FOUND          No matching entries found.
  @retval EFI_OUT_OF_RESOURCE    There is a memory allocation failure.

**/
EFI_STATUS
ArpFindCacheEntry (
  IN ARP_INSTANCE_DATA   *Instance,
  IN BOOLEAN             BySwAddress,
  IN VOID                *AddressBuffer OPTIONAL,
  OUT UINT32             *EntryLength   OPTIONAL,
  OUT UINT32             *EntryCount    OPTIONAL,
  OUT EFI_ARP_FIND_DATA  **Entries      OPTIONAL,
  IN BOOLEAN             Refresh
  )
{
  EFI_STATUS         Status;
  ARP_SERVICE_DATA   *ArpService;
  NET_ARP_ADDRESS    MatchAddress;
  FIND_OPTYPE        FindOpType;
  LIST_ENTRY         *StartEntry;
  ARP_CACHE_ENTRY    *CacheEntry;
  NET_MAP            FoundEntries;
  UINT32             FoundCount;
  EFI_ARP_FIND_DATA  *FindData;
  LIST_ENTRY         *CacheTable;
  UINT32             FoundEntryLength;

  ArpService = Instance->ArpService;

  //
  // Init the FoundEntries used to hold the found cache entries.
  //
  NetMapInit (&FoundEntries);

  //
  // Set the MatchAddress.
  //
  if (BySwAddress) {
    MatchAddress.Type   = Instance->ConfigData.SwAddressType;
    MatchAddress.Length = Instance->ConfigData.SwAddressLength;
    FindOpType          = ByProtoAddress;
  } else {
    MatchAddress.Type   = ArpService->SnpMode.IfType;
    MatchAddress.Length = (UINT8)ArpService->SnpMode.HwAddressSize;
    FindOpType          = ByHwAddress;
  }

  MatchAddress.AddressPtr = AddressBuffer;

  //
  // Search the DeniedCacheTable
  //
  StartEntry = NULL;
  while (TRUE) {
    //
    // Try to find the matched entries in the DeniedCacheTable.
    //
    CacheEntry = ArpFindNextCacheEntryInTable (
                   &ArpService->DeniedCacheTable,
                   StartEntry,
                   FindOpType,
                   &MatchAddress,
                   &MatchAddress
                   );
    if (CacheEntry == NULL) {
      //
      // Once the CacheEntry is NULL, there are no more matches.
      //
      break;
    }

    //
    // Insert the found entry into the map.
    //
    NetMapInsertTail (
      &FoundEntries,
      (VOID *)CacheEntry,
      (VOID *)&ArpService->DeniedCacheTable
      );

    //
    // Let the next search start from this cache entry.
    //
    StartEntry = &CacheEntry->List;

    if (Refresh) {
      //
      // Refresh the DecayTime if needed.
      //
      CacheEntry->DecayTime = CacheEntry->DefaultDecayTime;
    }
  }

  //
  // Search the ResolvedCacheTable
  //
  StartEntry = NULL;
  while (TRUE) {
    CacheEntry = ArpFindNextCacheEntryInTable (
                   &ArpService->ResolvedCacheTable,
                   StartEntry,
                   FindOpType,
                   &MatchAddress,
                   &MatchAddress
                   );
    if (CacheEntry == NULL) {
      //
      // Once the CacheEntry is NULL, there are no more matches.
      //
      break;
    }

    //
    // Insert the found entry into the map.
    //
    NetMapInsertTail (
      &FoundEntries,
      (VOID *)CacheEntry,
      (VOID *)&ArpService->ResolvedCacheTable
      );

    //
    // Let the next search start from this cache entry.
    //
    StartEntry = &CacheEntry->List;

    if (Refresh) {
      //
      // Refresh the DecayTime if needed.
      //
      CacheEntry->DecayTime = CacheEntry->DefaultDecayTime;
    }
  }

  Status = EFI_SUCCESS;

  FoundCount = (UINT32)NetMapGetCount (&FoundEntries);
  if (FoundCount == 0) {
    Status = EFI_NOT_FOUND;
    goto CLEAN_EXIT;
  }

  //
  // Found the entry length, make sure its 8 bytes alignment.
  //
  FoundEntryLength = (((sizeof (EFI_ARP_FIND_DATA) + Instance->ConfigData.SwAddressLength +
                        ArpService->SnpMode.HwAddressSize) + 3) & ~(0x3));

  if (EntryLength != NULL) {
    *EntryLength = FoundEntryLength;
  }

  if (EntryCount != NULL) {
    //
    // Return the found entry count.
    //
    *EntryCount = FoundCount;
  }

  if (Entries == NULL) {
    goto CLEAN_EXIT;
  }

  //
  // Allocate buffer to copy the found entries.
  //
  FindData = AllocatePool (FoundCount * FoundEntryLength);
  if (FindData == NULL) {
    DEBUG ((DEBUG_ERROR, "ArpFindCacheEntry: Failed to allocate memory.\n"));
    Status = EFI_OUT_OF_RESOURCES;
    goto CLEAN_EXIT;
  }

  //
  // Return the address to the user.
  //
  *Entries = FindData;

  //
  // Dump the entries.
  //
  while (!NetMapIsEmpty (&FoundEntries)) {
    //
    // Get a cache entry from the map.
    //
    CacheEntry = NetMapRemoveHead (&FoundEntries, (VOID **)&CacheTable);

    //
    // Set the fields in FindData.
    //
    FindData->Size            = FoundEntryLength;
    FindData->DenyFlag        = (BOOLEAN)(CacheTable == &ArpService->DeniedCacheTable);
    FindData->StaticFlag      = (BOOLEAN)(CacheEntry->DefaultDecayTime == 0);
    FindData->HwAddressType   = ArpService->SnpMode.IfType;
    FindData->SwAddressType   = Instance->ConfigData.SwAddressType;
    FindData->HwAddressLength = (UINT8)ArpService->SnpMode.HwAddressSize;
    FindData->SwAddressLength = Instance->ConfigData.SwAddressLength;

    //
    // Copy the software address.
    //
    CopyMem (
      FindData + 1,
      CacheEntry->Addresses[Protocol].AddressPtr,
      FindData->SwAddressLength
      );

    //
    // Copy the hardware address.
    //
    CopyMem (
      (UINT8 *)(FindData + 1) + FindData->SwAddressLength,
      CacheEntry->Addresses[Hardware].AddressPtr,
      FindData->HwAddressLength
      );

    //
    // Slip to the next FindData.
    //
    FindData = (EFI_ARP_FIND_DATA *)((UINT8 *)FindData + FoundEntryLength);
  }

CLEAN_EXIT:

  NetMapClean (&FoundEntries);

  return Status;
}
