/** @file
  Implementation of Neighbor Discovery support routines.

  Copyright (c) 2009 - 2011, Intel Corporation. All rights reserved.<BR>

  This program and the accompanying materials
  are licensed and made available under the terms and conditions of the BSD License
  which accompanies this distribution.  The full text of the license may be found at
  http://opensource.org/licenses/bsd-license.php.

  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/

#include "Ip6Impl.h"

EFI_MAC_ADDRESS mZeroMacAddress;

/**
  Update the ReachableTime in IP6 service binding instance data, in milliseconds.

  @param[in, out] IpSb     Points to the IP6_SERVICE.

**/
VOID
Ip6UpdateReachableTime (
  IN OUT IP6_SERVICE  *IpSb
  )
{
  UINT32              Random;

  Random = (NetRandomInitSeed () / 4294967295UL) * IP6_RANDOM_FACTOR_SCALE;
  Random = Random + IP6_MIN_RANDOM_FACTOR_SCALED;
  IpSb->ReachableTime = (IpSb->BaseReachableTime * Random) / IP6_RANDOM_FACTOR_SCALE;
}

/**
  Build a array of EFI_IP6_NEIGHBOR_CACHE to be returned to the caller. The number
  of EFI_IP6_NEIGHBOR_CACHE is also returned.

  @param[in]  IpInstance        The pointer to IP6_PROTOCOL instance.
  @param[out] NeighborCount     The number of returned neighbor cache entries.
  @param[out] NeighborCache     The pointer to the array of EFI_IP6_NEIGHBOR_CACHE.

  @retval EFI_SUCCESS           The EFI_IP6_NEIGHBOR_CACHE successfully built.
  @retval EFI_OUT_OF_RESOURCES  Failed to allocate the memory for the route table.

**/
EFI_STATUS
Ip6BuildEfiNeighborCache (
  IN IP6_PROTOCOL            *IpInstance,
  OUT UINT32                 *NeighborCount,
  OUT EFI_IP6_NEIGHBOR_CACHE **NeighborCache
  )
{
  IP6_NEIGHBOR_ENTRY        *Neighbor;
  LIST_ENTRY                *Entry;
  IP6_SERVICE               *IpSb;
  UINT32                    Count;
  EFI_IP6_NEIGHBOR_CACHE    *EfiNeighborCache;
  EFI_IP6_NEIGHBOR_CACHE    *NeighborCacheTmp;

  NET_CHECK_SIGNATURE (IpInstance, IP6_PROTOCOL_SIGNATURE);
  ASSERT (NeighborCount != NULL && NeighborCache != NULL);

  IpSb  = IpInstance->Service;
  Count = 0;

  NET_LIST_FOR_EACH (Entry, &IpSb->NeighborTable) {
    Count++;
  }

  if (Count == 0) {
    return EFI_SUCCESS;
  }

  NeighborCacheTmp = AllocatePool (Count * sizeof (EFI_IP6_NEIGHBOR_CACHE));
  if (NeighborCacheTmp == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  *NeighborCount = Count;
  Count          = 0;

  NET_LIST_FOR_EACH (Entry, &IpSb->NeighborTable) {
    Neighbor = NET_LIST_USER_STRUCT (Entry, IP6_NEIGHBOR_ENTRY, Link);

    EfiNeighborCache = NeighborCacheTmp + Count;

   EfiNeighborCache->State = Neighbor->State;
    IP6_COPY_ADDRESS (&EfiNeighborCache->Neighbor, &Neighbor->Neighbor);
    IP6_COPY_LINK_ADDRESS (&EfiNeighborCache->LinkAddress, &Neighbor->LinkAddress);

    Count++;
  }

  ASSERT (*NeighborCount == Count);
  *NeighborCache = NeighborCacheTmp;

  return EFI_SUCCESS;
}

/**
  Build a array of EFI_IP6_ADDRESS_INFO to be returned to the caller. The number
  of prefix entries is also returned.

  @param[in]  IpInstance        The pointer to IP6_PROTOCOL instance.
  @param[out] PrefixCount       The number of returned prefix entries.
  @param[out] PrefixTable       The pointer to the array of PrefixTable.

  @retval EFI_SUCCESS           The prefix table successfully built.
  @retval EFI_OUT_OF_RESOURCES  Failed to allocate the memory for the prefix table.

**/
EFI_STATUS
Ip6BuildPrefixTable (
  IN IP6_PROTOCOL           *IpInstance,
  OUT UINT32                *PrefixCount,
  OUT EFI_IP6_ADDRESS_INFO  **PrefixTable
  )
{
  LIST_ENTRY                *Entry;
  IP6_SERVICE               *IpSb;
  UINT32                    Count;
  IP6_PREFIX_LIST_ENTRY     *PrefixList;
  EFI_IP6_ADDRESS_INFO      *EfiPrefix;
  EFI_IP6_ADDRESS_INFO      *PrefixTableTmp;

  NET_CHECK_SIGNATURE (IpInstance, IP6_PROTOCOL_SIGNATURE);
  ASSERT (PrefixCount != NULL && PrefixTable != NULL);

  IpSb  = IpInstance->Service;
  Count = 0;

  NET_LIST_FOR_EACH (Entry, &IpSb->OnlinkPrefix) {
    Count++;
  }

  if (Count == 0) {
    return EFI_SUCCESS;
  }

  PrefixTableTmp = AllocatePool (Count * sizeof (EFI_IP6_ADDRESS_INFO));
  if (PrefixTableTmp == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  *PrefixCount = Count;
  Count        = 0;

  NET_LIST_FOR_EACH (Entry, &IpSb->OnlinkPrefix) {
    PrefixList = NET_LIST_USER_STRUCT (Entry, IP6_PREFIX_LIST_ENTRY, Link);
    EfiPrefix  = PrefixTableTmp + Count;
    IP6_COPY_ADDRESS (&EfiPrefix->Address, &PrefixList->Prefix);
    EfiPrefix->PrefixLength = PrefixList->PrefixLength;

    Count++;
  }

  ASSERT (*PrefixCount == Count);
  *PrefixTable = PrefixTableTmp;

  return EFI_SUCCESS;
}

/**
  Allocate and initialize a IP6 prefix list entry.

  @param[in]  IpSb              The pointer to IP6_SERVICE instance.
  @param[in]  OnLinkOrAuto      If TRUE, the entry is created for the on link prefix list.
                                Otherwise, it is created for the autoconfiguration prefix list.
  @param[in]  ValidLifetime     The length of time in seconds that the prefix
                                is valid for the purpose of on-link determination.
  @param[in]  PreferredLifetime The length of time in seconds that addresses
                                generated from the prefix via stateless address
                                autoconfiguration remain preferred.
  @param[in]  PrefixLength      The prefix length of the Prefix.
  @param[in]  Prefix            The prefix address.

  @return NULL if it failed to allocate memory for the prefix node. Otherwise, point
          to the created or existing prefix list entry.

**/
IP6_PREFIX_LIST_ENTRY *
Ip6CreatePrefixListEntry (
  IN IP6_SERVICE            *IpSb,
  IN BOOLEAN                OnLinkOrAuto,
  IN UINT32                 ValidLifetime,
  IN UINT32                 PreferredLifetime,
  IN UINT8                  PrefixLength,
  IN EFI_IPv6_ADDRESS       *Prefix
  )
{
  IP6_PREFIX_LIST_ENTRY     *PrefixEntry;
  IP6_ROUTE_ENTRY           *RtEntry;
  LIST_ENTRY                *ListHead;
  LIST_ENTRY                *Entry;
  IP6_PREFIX_LIST_ENTRY     *TmpPrefixEntry;

  if (Prefix == NULL || PreferredLifetime > ValidLifetime || PrefixLength >= IP6_PREFIX_NUM) {
    return NULL;
  }

  NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);

  PrefixEntry = Ip6FindPrefixListEntry (
                  IpSb,
                  OnLinkOrAuto,
                  PrefixLength,
                  Prefix
                  );
  if (PrefixEntry != NULL) {
    PrefixEntry->RefCnt ++;
    return PrefixEntry;
  }

  PrefixEntry = AllocatePool (sizeof (IP6_PREFIX_LIST_ENTRY));
  if (PrefixEntry == NULL) {
    return NULL;
  }

  PrefixEntry->RefCnt            = 1;
  PrefixEntry->ValidLifetime     = ValidLifetime;
  PrefixEntry->PreferredLifetime = PreferredLifetime;
  PrefixEntry->PrefixLength      = PrefixLength;
  IP6_COPY_ADDRESS (&PrefixEntry->Prefix, Prefix);

  ListHead = OnLinkOrAuto ? &IpSb->OnlinkPrefix : &IpSb->AutonomousPrefix;

  //
  // Create a direct route entry for on-link prefix and insert to route area.
  //
  if (OnLinkOrAuto) {
    RtEntry = Ip6CreateRouteEntry (Prefix, PrefixLength, NULL);
    if (RtEntry == NULL) {
      FreePool (PrefixEntry);
      return NULL;
    }

    RtEntry->Flag = IP6_DIRECT_ROUTE;
    InsertHeadList (&IpSb->RouteTable->RouteArea[PrefixLength], &RtEntry->Link);
    IpSb->RouteTable->TotalNum++;
  }

  //
  // Insert the prefix entry in the order that a prefix with longer prefix length
  // is put ahead in the list.
  //
  NET_LIST_FOR_EACH (Entry, ListHead) {
    TmpPrefixEntry = NET_LIST_USER_STRUCT(Entry, IP6_PREFIX_LIST_ENTRY, Link);

    if (TmpPrefixEntry->PrefixLength < PrefixEntry->PrefixLength) {
      break;
    }
  }

  NetListInsertBefore (Entry, &PrefixEntry->Link);

  return PrefixEntry;
}

/**
  Destory a IP6 prefix list entry.

  @param[in]  IpSb              The pointer to IP6_SERVICE instance.
  @param[in]  PrefixEntry       The to be destroyed prefix list entry.
  @param[in]  OnLinkOrAuto      If TRUE, the entry is removed from on link prefix list.
                                Otherwise remove from autoconfiguration prefix list.
  @param[in]  ImmediateDelete   If TRUE, remove the entry directly.
                                Otherwise, check the reference count to see whether
                                it should be removed.

**/
VOID
Ip6DestroyPrefixListEntry (
  IN IP6_SERVICE            *IpSb,
  IN IP6_PREFIX_LIST_ENTRY  *PrefixEntry,
  IN BOOLEAN                OnLinkOrAuto,
  IN BOOLEAN                ImmediateDelete
  )
{
  LIST_ENTRY      *Entry;
  IP6_INTERFACE   *IpIf;
  EFI_STATUS      Status;

  if ((!ImmediateDelete) && (PrefixEntry->RefCnt > 0) && ((--PrefixEntry->RefCnt) > 0)) {
    return ;
  }

  if (OnLinkOrAuto) {
      //
      // Remove the direct route for onlink prefix from route table.
      //
      do {
        Status = Ip6DelRoute (
                   IpSb->RouteTable,
                   &PrefixEntry->Prefix,
                   PrefixEntry->PrefixLength,
                   NULL
                   );
      } while (Status != EFI_NOT_FOUND);
  } else {
    //
    // Remove the corresponding addresses generated from this autonomous prefix.
    //
    NET_LIST_FOR_EACH (Entry, &IpSb->Interfaces) {
      IpIf = NET_LIST_USER_STRUCT_S (Entry, IP6_INTERFACE, Link, IP6_INTERFACE_SIGNATURE);

      Ip6RemoveAddr (IpSb, &IpIf->AddressList, &IpIf->AddressCount, &PrefixEntry->Prefix, PrefixEntry->PrefixLength);
    }
  }

  RemoveEntryList (&PrefixEntry->Link);
  FreePool (PrefixEntry);
}

/**
  Search the list array to find an IP6 prefix list entry.

  @param[in]  IpSb              The pointer to IP6_SERVICE instance.
  @param[in]  OnLinkOrAuto      If TRUE, the search the link prefix list,
                                Otherwise search the autoconfiguration prefix list.
  @param[in]  PrefixLength      The prefix length of the Prefix
  @param[in]  Prefix            The prefix address.

  @return NULL if cannot find the IP6 prefix list entry. Otherwise, return the
          pointer to the IP6 prefix list entry.

**/
IP6_PREFIX_LIST_ENTRY *
Ip6FindPrefixListEntry (
  IN IP6_SERVICE            *IpSb,
  IN BOOLEAN                OnLinkOrAuto,
  IN UINT8                  PrefixLength,
  IN EFI_IPv6_ADDRESS       *Prefix
  )
{
  IP6_PREFIX_LIST_ENTRY     *PrefixList;
  LIST_ENTRY                *Entry;
  LIST_ENTRY                *ListHead;

  NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
  ASSERT (Prefix != NULL);

  if (OnLinkOrAuto) {
    ListHead = &IpSb->OnlinkPrefix;
  } else {
    ListHead = &IpSb->AutonomousPrefix;
  }

  NET_LIST_FOR_EACH (Entry, ListHead) {
    PrefixList = NET_LIST_USER_STRUCT (Entry, IP6_PREFIX_LIST_ENTRY, Link);
    if (PrefixLength != 255) {
      //
      // Perform exactly prefix match.
      //
      if (PrefixList->PrefixLength == PrefixLength &&
        NetIp6IsNetEqual (&PrefixList->Prefix, Prefix, PrefixLength)) {
        return PrefixList;
      }
    } else {
      //
      // Perform the longest prefix match. The list is already sorted with
      // the longest length prefix put at the head of the list.
      //
      if (NetIp6IsNetEqual (&PrefixList->Prefix, Prefix, PrefixList->PrefixLength)) {
        return PrefixList;
      }
    }
  }

  return NULL;
}

/**
  Release the resource in the prefix list table, and destroy the list entry and
  corresponding addresses or route entries.

  @param[in]  IpSb              The pointer to the IP6_SERVICE instance.
  @param[in]  ListHead          The list entry head of the prefix list table.

**/
VOID
Ip6CleanPrefixListTable (
  IN IP6_SERVICE            *IpSb,
  IN LIST_ENTRY             *ListHead
  )
{
  IP6_PREFIX_LIST_ENTRY     *PrefixList;
  BOOLEAN                   OnLink;

  OnLink = (BOOLEAN) (ListHead == &IpSb->OnlinkPrefix);

  while (!IsListEmpty (ListHead)) {
    PrefixList = NET_LIST_HEAD (ListHead, IP6_PREFIX_LIST_ENTRY, Link);
    Ip6DestroyPrefixListEntry (IpSb, PrefixList, OnLink, TRUE);
  }
}

/**
  Callback function when address resolution is finished. It will cancel
  all the queued frames if the address resolution failed, or transmit them
  if the request succeeded.

  @param[in] Context The context of the callback, a pointer to IP6_NEIGHBOR_ENTRY.

**/
VOID
Ip6OnArpResolved (
  IN VOID                   *Context
  )
{
  LIST_ENTRY                *Entry;
  LIST_ENTRY                *Next;
  IP6_NEIGHBOR_ENTRY        *ArpQue;
  IP6_SERVICE               *IpSb;
  IP6_LINK_TX_TOKEN         *Token;
  EFI_STATUS                Status;
  BOOLEAN                   Sent;

  ArpQue = (IP6_NEIGHBOR_ENTRY *) Context;
  if ((ArpQue == NULL) || (ArpQue->Interface == NULL)) {
    return ;
  }

  IpSb   = ArpQue->Interface->Service;
  if ((IpSb == NULL) || (IpSb->Signature != IP6_SERVICE_SIGNATURE)) {
    return ;
  }

  //
  // ARP resolve failed for some reason. Release all the frame
  // and ARP queue itself. Ip6FreeArpQue will call the frame's
  // owner back.
  //
  if (NET_MAC_EQUAL (&ArpQue->LinkAddress, &mZeroMacAddress, IpSb->SnpMode.HwAddressSize)) {
    Ip6FreeNeighborEntry (IpSb, ArpQue, FALSE, TRUE, EFI_NO_MAPPING, NULL, NULL);
    return ;
  }

  //
  // ARP resolve succeeded, Transmit all the frame.
  //
  Sent = FALSE;
  NET_LIST_FOR_EACH_SAFE (Entry, Next, &ArpQue->Frames) {
    RemoveEntryList (Entry);

    Token = NET_LIST_USER_STRUCT (Entry, IP6_LINK_TX_TOKEN, Link);
    IP6_COPY_LINK_ADDRESS (&Token->DstMac, &ArpQue->LinkAddress);

    //
    // Insert the tx token before transmitting it via MNP as the FrameSentDpc
    // may be called before Mnp->Transmit returns which will remove this tx
    // token from the SentFrames list. Remove it from the list if the returned
    // Status of Mnp->Transmit is not EFI_SUCCESS as in this case the
    // FrameSentDpc won't be queued.
    //
    InsertTailList (&ArpQue->Interface->SentFrames, &Token->Link);

    Status = IpSb->Mnp->Transmit (IpSb->Mnp, &Token->MnpToken);
    if (EFI_ERROR (Status)) {
      RemoveEntryList (&Token->Link);
      Token->CallBack (Token->Packet, Status, 0, Token->Context);

      Ip6FreeLinkTxToken (Token);
      continue;
    } else {
      Sent = TRUE;
    }
  }

  //
  // Free the ArpQue only but not the whole neighbor entry.
  //
  Ip6FreeNeighborEntry (IpSb, ArpQue, FALSE, FALSE, EFI_SUCCESS, NULL, NULL);

  if (Sent && (ArpQue->State == EfiNeighborStale)) {
    ArpQue->State = EfiNeighborDelay;
    ArpQue->Ticks = (UINT32) IP6_GET_TICKS (IP6_DELAY_FIRST_PROBE_TIME);
  }
}

/**
  Allocate and initialize an IP6 neighbor cache entry.

  @param[in]  IpSb              The pointer to the IP6_SERVICE instance.
  @param[in]  CallBack          The callback function to be called when
                                address resolution is finished.
  @param[in]  Ip6Address        Points to the IPv6 address of the neighbor.
  @param[in]  LinkAddress       Points to the MAC address of the neighbor.
                                Ignored if NULL.

  @return NULL if failed to allocate memory for the neighbor cache entry.
          Otherwise, point to the created neighbor cache entry.

**/
IP6_NEIGHBOR_ENTRY *
Ip6CreateNeighborEntry (
  IN IP6_SERVICE            *IpSb,
  IN IP6_ARP_CALLBACK       CallBack,
  IN EFI_IPv6_ADDRESS       *Ip6Address,
  IN EFI_MAC_ADDRESS        *LinkAddress OPTIONAL
  )
{
  IP6_NEIGHBOR_ENTRY        *Entry;
  IP6_DEFAULT_ROUTER        *DefaultRouter;

  NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
  ASSERT (Ip6Address!= NULL);

  Entry = AllocateZeroPool (sizeof (IP6_NEIGHBOR_ENTRY));
  if (Entry == NULL) {
    return NULL;
  }

  Entry->RefCnt    = 1;
  Entry->IsRouter  = FALSE;
  Entry->ArpFree   = FALSE;
  Entry->Dynamic   = FALSE;
  Entry->State     = EfiNeighborInComplete;
  Entry->Transmit  = IP6_MAX_MULTICAST_SOLICIT + 1;
  Entry->CallBack  = CallBack;
  Entry->Interface = NULL;

  InitializeListHead (&Entry->Frames);

  IP6_COPY_ADDRESS (&Entry->Neighbor, Ip6Address);

  if (LinkAddress != NULL) {
    IP6_COPY_LINK_ADDRESS (&Entry->LinkAddress, LinkAddress);
  } else {
    IP6_COPY_LINK_ADDRESS (&Entry->LinkAddress, &mZeroMacAddress);
  }

  InsertHeadList (&IpSb->NeighborTable, &Entry->Link);

  //
  // If corresponding default router entry exists, establish the relationship.
  //
  DefaultRouter = Ip6FindDefaultRouter (IpSb, Ip6Address);
  if (DefaultRouter != NULL) {
    DefaultRouter->NeighborCache = Entry;
  }

  return Entry;
}

/**
  Search a IP6 neighbor cache entry.

  @param[in]  IpSb              The pointer to the IP6_SERVICE instance.
  @param[in]  Ip6Address        Points to the IPv6 address of the neighbor.

  @return NULL if it failed to find the matching neighbor cache entry.
          Otherwise, point to the found neighbor cache entry.

**/
IP6_NEIGHBOR_ENTRY *
Ip6FindNeighborEntry (
  IN IP6_SERVICE            *IpSb,
  IN EFI_IPv6_ADDRESS       *Ip6Address
  )
{
  LIST_ENTRY                *Entry;
  LIST_ENTRY                *Next;
  IP6_NEIGHBOR_ENTRY        *Neighbor;

  NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
  ASSERT (Ip6Address != NULL);

  NET_LIST_FOR_EACH_SAFE (Entry, Next, &IpSb->NeighborTable) {
    Neighbor = NET_LIST_USER_STRUCT (Entry, IP6_NEIGHBOR_ENTRY, Link);
    if (EFI_IP6_EQUAL (Ip6Address, &Neighbor->Neighbor)) {
      RemoveEntryList (Entry);
      InsertHeadList (&IpSb->NeighborTable, Entry);

      return Neighbor;
    }
  }

  return NULL;
}

/**
  Free a IP6 neighbor cache entry and remove all the frames on the address
  resolution queue that pass the FrameToCancel. That is, either FrameToCancel
  is NULL, or it returns true for the frame.

  @param[in]  IpSb              The pointer to the IP6_SERVICE instance.
  @param[in]  NeighborCache     The to be free neighbor cache entry.
  @param[in]  SendIcmpError     If TRUE, send out ICMP error.
  @param[in]  FullFree          If TRUE, remove the neighbor cache entry.
                                Otherwise remove the pending frames.
  @param[in]  IoStatus          The status returned to the cancelled frames'
                                callback function.
  @param[in]  FrameToCancel     Function to select which frame to cancel.
                                This is an optional parameter that may be NULL.
  @param[in]  Context           Opaque parameter to the FrameToCancel.
                                Ignored if FrameToCancel is NULL.

  @retval EFI_INVALID_PARAMETER The input parameter is invalid.
  @retval EFI_SUCCESS           The operation finished successfully.

**/
EFI_STATUS
Ip6FreeNeighborEntry (
  IN IP6_SERVICE            *IpSb,
  IN IP6_NEIGHBOR_ENTRY     *NeighborCache,
  IN BOOLEAN                SendIcmpError,
  IN BOOLEAN                FullFree,
  IN EFI_STATUS             IoStatus,
  IN IP6_FRAME_TO_CANCEL    FrameToCancel OPTIONAL,
  IN VOID                   *Context      OPTIONAL
  )
{
  IP6_LINK_TX_TOKEN         *TxToken;
  LIST_ENTRY                *Entry;
  LIST_ENTRY                *Next;
  IP6_DEFAULT_ROUTER        *DefaultRouter;

  //
  // If FrameToCancel fails, the token will not be released.
  // To avoid the memory leak, stop this usage model.
  //
  if (FullFree && FrameToCancel != NULL) {
    return EFI_INVALID_PARAMETER;
  }

  NET_LIST_FOR_EACH_SAFE (Entry, Next, &NeighborCache->Frames) {
    TxToken = NET_LIST_USER_STRUCT (Entry, IP6_LINK_TX_TOKEN, Link);

    if (SendIcmpError && !IP6_IS_MULTICAST (&TxToken->Packet->Ip.Ip6->DestinationAddress)) {
      Ip6SendIcmpError (
        IpSb,
        TxToken->Packet,
        NULL,
        &TxToken->Packet->Ip.Ip6->SourceAddress,
        ICMP_V6_DEST_UNREACHABLE,
        ICMP_V6_ADDR_UNREACHABLE,
        NULL
        );
    }

    if ((FrameToCancel == NULL) || FrameToCancel (TxToken, Context)) {
      RemoveEntryList (Entry);
      TxToken->CallBack (TxToken->Packet, IoStatus, 0, TxToken->Context);
      Ip6FreeLinkTxToken (TxToken);
    }
  }

  if (NeighborCache->ArpFree && IsListEmpty (&NeighborCache->Frames)) {
    RemoveEntryList (&NeighborCache->ArpList);
    NeighborCache->ArpFree = FALSE;
  }

  if (FullFree) {
    if (NeighborCache->IsRouter) {
      DefaultRouter = Ip6FindDefaultRouter (IpSb, &NeighborCache->Neighbor);
      if (DefaultRouter != NULL) {
        Ip6DestroyDefaultRouter (IpSb, DefaultRouter);
      }
    }

    RemoveEntryList (&NeighborCache->Link);
    FreePool (NeighborCache);
  }

  return EFI_SUCCESS;
}

/**
  Allocate and initialize an IP6 default router entry.

  @param[in]  IpSb              The pointer to the IP6_SERVICE instance.
  @param[in]  Ip6Address        The IPv6 address of the default router.
  @param[in]  RouterLifetime    The lifetime associated with the default
                                router, in units of seconds.

  @return NULL if it failed to allocate memory for the default router node.
          Otherwise, point to the created default router node.

**/
IP6_DEFAULT_ROUTER *
Ip6CreateDefaultRouter (
  IN IP6_SERVICE            *IpSb,
  IN EFI_IPv6_ADDRESS       *Ip6Address,
  IN UINT16                 RouterLifetime
  )
{
  IP6_DEFAULT_ROUTER        *Entry;
  IP6_ROUTE_ENTRY           *RtEntry;

  NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
  ASSERT (Ip6Address != NULL);

  Entry = AllocatePool (sizeof (IP6_DEFAULT_ROUTER));
  if (Entry == NULL) {
    return NULL;
  }

  Entry->RefCnt        = 1;
  Entry->Lifetime      = RouterLifetime;
  Entry->NeighborCache = Ip6FindNeighborEntry (IpSb, Ip6Address);
  IP6_COPY_ADDRESS (&Entry->Router, Ip6Address);

  //
  // Add a default route into route table with both Destination and PrefixLength set to zero.
  //
  RtEntry = Ip6CreateRouteEntry (NULL, 0, Ip6Address);
  if (RtEntry == NULL) {
    FreePool (Entry);
    return NULL;
  }

  InsertHeadList (&IpSb->RouteTable->RouteArea[0], &RtEntry->Link);
  IpSb->RouteTable->TotalNum++;

  InsertTailList (&IpSb->DefaultRouterList, &Entry->Link);

  return Entry;
}

/**
  Destroy an IP6 default router entry.

  @param[in]  IpSb              The pointer to the IP6_SERVICE instance.
  @param[in]  DefaultRouter     The to be destroyed IP6_DEFAULT_ROUTER.

**/
VOID
Ip6DestroyDefaultRouter (
  IN IP6_SERVICE            *IpSb,
  IN IP6_DEFAULT_ROUTER     *DefaultRouter
  )
{
  EFI_STATUS                Status;

  RemoveEntryList (&DefaultRouter->Link);

  //
  // Update the Destination Cache - all entries using the time-out router as next-hop
  // should perform next-hop determination again.
  //
  do {
    Status = Ip6DelRoute (IpSb->RouteTable, NULL, 0, &DefaultRouter->Router);
  } while (Status != EFI_NOT_FOUND);

  FreePool (DefaultRouter);
}

/**
  Clean an IP6 default router list.

  @param[in]  IpSb              The pointer to the IP6_SERVICE instance.

**/
VOID
Ip6CleanDefaultRouterList (
  IN IP6_SERVICE            *IpSb
  )
{
  IP6_DEFAULT_ROUTER        *DefaultRouter;

  while (!IsListEmpty (&IpSb->DefaultRouterList)) {
    DefaultRouter = NET_LIST_HEAD (&IpSb->DefaultRouterList, IP6_DEFAULT_ROUTER, Link);
    Ip6DestroyDefaultRouter (IpSb, DefaultRouter);
  }
}

/**
  Search a default router node from an IP6 default router list.

  @param[in]  IpSb          The pointer to the IP6_SERVICE instance.
  @param[in]  Ip6Address    The IPv6 address of the to be searched default router node.

  @return NULL if it failed to find the matching default router node.
          Otherwise, point to the found default router node.

**/
IP6_DEFAULT_ROUTER *
Ip6FindDefaultRouter (
  IN IP6_SERVICE            *IpSb,
  IN EFI_IPv6_ADDRESS       *Ip6Address
  )
{
  LIST_ENTRY                *Entry;
  IP6_DEFAULT_ROUTER        *DefaultRouter;

  NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
  ASSERT (Ip6Address != NULL);

  NET_LIST_FOR_EACH (Entry, &IpSb->DefaultRouterList) {
    DefaultRouter = NET_LIST_USER_STRUCT (Entry, IP6_DEFAULT_ROUTER, Link);
    if (EFI_IP6_EQUAL (Ip6Address, &DefaultRouter->Router)) {
      return DefaultRouter;
    }
  }

  return NULL;
}

/**
  The function to be called after DAD (Duplicate Address Detection) is performed.

  @param[in]  IsDadPassed   If TRUE, the DAD operation succeed. Otherwise, the DAD operation failed.
  @param[in]  IpIf          Points to the IP6_INTERFACE.
  @param[in]  DadEntry      The DAD entry which already performed DAD.

**/
VOID
Ip6OnDADFinished (
  IN BOOLEAN        IsDadPassed,
  IN IP6_INTERFACE  *IpIf,
  IN IP6_DAD_ENTRY  *DadEntry
  )
{
  IP6_SERVICE               *IpSb;
  IP6_ADDRESS_INFO          *AddrInfo;
  EFI_DHCP6_PROTOCOL        *Dhcp6;
  UINT16                    OptBuf[4];
  EFI_DHCP6_PACKET_OPTION   *Oro;
  EFI_DHCP6_RETRANSMISSION  InfoReqReXmit;

  IpSb     = IpIf->Service;
  AddrInfo = DadEntry->AddressInfo;

  if (IsDadPassed) {
    //
    // DAD succeed.
    //
    if (NetIp6IsLinkLocalAddr (&AddrInfo->Address)) {
      ASSERT (!IpSb->LinkLocalOk);

      IP6_COPY_ADDRESS (&IpSb->LinkLocalAddr, &AddrInfo->Address);
      IpSb->LinkLocalOk = TRUE;
      IpIf->Configured  = TRUE;

      //
      // Check whether DHCP6 need to be started.
      //
      Dhcp6 = IpSb->Ip6ConfigInstance.Dhcp6;

      if (IpSb->Dhcp6NeedStart) {
        Dhcp6->Start (Dhcp6);
        IpSb->Dhcp6NeedStart = FALSE;
      }

      if (IpSb->Dhcp6NeedInfoRequest) {
        //
        // Set the exta options to send. Here we only want the option request option
        // with DNS SERVERS.
        //
        Oro         = (EFI_DHCP6_PACKET_OPTION *) OptBuf;
        Oro->OpCode = HTONS (IP6_CONFIG_DHCP6_OPTION_ORO);
        Oro->OpLen  = HTONS (2);
        *((UINT16 *) &Oro->Data[0]) = HTONS (IP6_CONFIG_DHCP6_OPTION_DNS_SERVERS);

        InfoReqReXmit.Irt = 4;
        InfoReqReXmit.Mrc = 64;
        InfoReqReXmit.Mrt = 60;
        InfoReqReXmit.Mrd = 0;

        Dhcp6->InfoRequest (
                 Dhcp6,
                 TRUE,
                 Oro,
                 0,
                 NULL,
                 &InfoReqReXmit,
                 IpSb->Ip6ConfigInstance.Dhcp6Event,
                 Ip6ConfigOnDhcp6Reply,
                 &IpSb->Ip6ConfigInstance
                 );
      }

      //
      // Add an on-link prefix for link-local address.
      //
      Ip6CreatePrefixListEntry (
        IpSb,
        TRUE,
        (UINT32) IP6_INFINIT_LIFETIME,
        (UINT32) IP6_INFINIT_LIFETIME,
        IP6_LINK_LOCAL_PREFIX_LENGTH,
        &IpSb->LinkLocalAddr
        );

    } else {
      //
      // Global scope unicast address.
      //
      Ip6AddAddr (IpIf, AddrInfo);

      //
      // Add an on-link prefix for this address.
      //
      Ip6CreatePrefixListEntry (
        IpSb,
        TRUE,
        AddrInfo->ValidLifetime,
        AddrInfo->PreferredLifetime,
        AddrInfo->PrefixLength,
        &AddrInfo->Address
        );

      IpIf->Configured = TRUE;
    }
  } else {
    //
    // Leave the group we joined before.
    //
    Ip6LeaveGroup (IpSb, &DadEntry->Destination);
  }

  if (DadEntry->Callback != NULL) {
    DadEntry->Callback (IsDadPassed, &AddrInfo->Address, DadEntry->Context);
  }

  if (!IsDadPassed && NetIp6IsLinkLocalAddr (&AddrInfo->Address)) {
    FreePool (AddrInfo);
    RemoveEntryList (&DadEntry->Link);
    FreePool (DadEntry);
    //
    // Disable IP operation since link-local address is a duplicate address.
    //
    IpSb->LinkLocalDadFail = TRUE;
    IpSb->Mnp->Configure (IpSb->Mnp, NULL);
    gBS->SetTimer (IpSb->Timer, TimerCancel, 0);
    gBS->SetTimer (IpSb->FasterTimer, TimerCancel, 0);
    return ;
  }

  if (!IsDadPassed || NetIp6IsLinkLocalAddr (&AddrInfo->Address)) {
    //
    // Free the AddressInfo we hold if DAD fails or it is a link-local address.
    //
    FreePool (AddrInfo);
  }

  RemoveEntryList (&DadEntry->Link);
  FreePool (DadEntry);
}

/**
  Create a DAD (Duplicate Address Detection) entry and queue it to be performed.

  @param[in]  IpIf          Points to the IP6_INTERFACE.
  @param[in]  AddressInfo   The address information which needs DAD performed.
  @param[in]  Callback      The callback routine that will be called after DAD
                            is performed. This is an optional parameter that
                            may be NULL.
  @param[in]  Context       The opaque parameter for a DAD callback routine.
                            This is an optional parameter that may be NULL.

  @retval EFI_SUCCESS           The DAD entry was created and queued.
  @retval EFI_OUT_OF_RESOURCES  Failed to allocate the memory to complete the
                                operation.


**/
EFI_STATUS
Ip6InitDADProcess (
  IN IP6_INTERFACE          *IpIf,
  IN IP6_ADDRESS_INFO       *AddressInfo,
  IN IP6_DAD_CALLBACK       Callback  OPTIONAL,
  IN VOID                   *Context  OPTIONAL
  )
{
  IP6_DAD_ENTRY                             *Entry;
  EFI_IP6_CONFIG_DUP_ADDR_DETECT_TRANSMITS  *DadXmits;
  IP6_SERVICE                               *IpSb;
  EFI_STATUS                                Status;
  UINT32                                    MaxDelayTick;

  NET_CHECK_SIGNATURE (IpIf, IP6_INTERFACE_SIGNATURE);
  ASSERT (AddressInfo != NULL);

  Status   = EFI_SUCCESS;
  IpSb     = IpIf->Service;
  DadXmits = &IpSb->Ip6ConfigInstance.DadXmits;

  //
  // Allocate the resources and insert info
  //
  Entry = AllocatePool (sizeof (IP6_DAD_ENTRY));
  if (Entry == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Map the incoming unicast address to solicited-node multicast address
  //
  Ip6CreateSNMulticastAddr (&AddressInfo->Address, &Entry->Destination);

  //
  // Join in the solicited-node multicast address.
  //
  Status = Ip6JoinGroup (IpSb, IpIf, &Entry->Destination);
  if (EFI_ERROR (Status)) {
    FreePool (Entry);
    return Status;
  }

  Entry->Signature    = IP6_DAD_ENTRY_SIGNATURE;
  Entry->MaxTransmit  = DadXmits->DupAddrDetectTransmits;
  Entry->Transmit     = 0;
  Entry->Receive      = 0;
  MaxDelayTick        = IP6_MAX_RTR_SOLICITATION_DELAY / IP6_TIMER_INTERVAL_IN_MS;
  Entry->RetransTick  = (MaxDelayTick * ((NET_RANDOM (NetRandomInitSeed ()) % 5) + 1)) / 5;
  Entry->AddressInfo  = AddressInfo;
  Entry->Callback     = Callback;
  Entry->Context      = Context;
  InsertTailList (&IpIf->DupAddrDetectList, &Entry->Link);

  if (Entry->MaxTransmit == 0) {
    //
    // DAD is disabled on this interface, immediately mark this DAD successful.
    //
    Ip6OnDADFinished (TRUE, IpIf, Entry);
  }

  return EFI_SUCCESS;
}

/**
  Search IP6_DAD_ENTRY from the Duplicate Address Detection List.

  @param[in]  IpSb          The pointer to the IP6_SERVICE instance.
  @param[in]  Target        The address information which needs DAD performed .
  @param[out] Interface     If not NULL, output the IP6 interface that configures
                            the tentative address.

  @return NULL if failed to find the matching DAD entry.
          Otherwise, point to the found DAD entry.

**/
IP6_DAD_ENTRY *
Ip6FindDADEntry (
  IN  IP6_SERVICE      *IpSb,
  IN  EFI_IPv6_ADDRESS *Target,
  OUT IP6_INTERFACE    **Interface OPTIONAL
  )
{
  LIST_ENTRY                *Entry;
  LIST_ENTRY                *Entry2;
  IP6_INTERFACE             *IpIf;
  IP6_DAD_ENTRY             *DupAddrDetect;
  IP6_ADDRESS_INFO          *AddrInfo;

  NET_LIST_FOR_EACH (Entry, &IpSb->Interfaces) {
    IpIf = NET_LIST_USER_STRUCT (Entry, IP6_INTERFACE, Link);

    NET_LIST_FOR_EACH (Entry2, &IpIf->DupAddrDetectList) {
      DupAddrDetect = NET_LIST_USER_STRUCT_S (Entry2, IP6_DAD_ENTRY, Link, IP6_DAD_ENTRY_SIGNATURE);
      AddrInfo      = DupAddrDetect->AddressInfo;
      if (EFI_IP6_EQUAL (&AddrInfo->Address, Target)) {
        if (Interface != NULL) {
          *Interface = IpIf;
        }
        return DupAddrDetect;
      }
    }
  }

  return NULL;
}

/**
  Generate router solicit message and send it out to Destination Address or
  All Router Link Local scope multicast address.

  @param[in]  IpSb               The IP service to send the packet.
  @param[in]  Interface          If not NULL, points to the IP6 interface to send
                                 the packet.
  @param[in]  SourceAddress      If not NULL, the source address of the message.
  @param[in]  DestinationAddress If not NULL, the destination address of the message.
  @param[in]  SourceLinkAddress  If not NULL, the MAC address of the source.
                                 A source link-layer address option will be appended
                                 to the message.

  @retval EFI_OUT_OF_RESOURCES   Insufficient resources to complete the
                                 operation.
  @retval EFI_SUCCESS            The router solicit message was successfully sent.

**/
EFI_STATUS
Ip6SendRouterSolicit (
  IN IP6_SERVICE            *IpSb,
  IN IP6_INTERFACE          *Interface          OPTIONAL,
  IN EFI_IPv6_ADDRESS       *SourceAddress      OPTIONAL,
  IN EFI_IPv6_ADDRESS       *DestinationAddress OPTIONAL,
  IN EFI_MAC_ADDRESS        *SourceLinkAddress  OPTIONAL
  )
{
  NET_BUF                   *Packet;
  EFI_IP6_HEADER            Head;
  IP6_ICMP_INFORMATION_HEAD *IcmpHead;
  IP6_ETHER_ADDR_OPTION     *LinkLayerOption;
  UINT16                    PayloadLen;
  IP6_INTERFACE             *IpIf;

  NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);

  IpIf = Interface;
  if (IpIf == NULL && IpSb->DefaultInterface != NULL) {
    IpIf = IpSb->DefaultInterface;
  }

  //
  // Generate the packet to be sent
  //

  PayloadLen = (UINT16) sizeof (IP6_ICMP_INFORMATION_HEAD);
  if (SourceLinkAddress != NULL) {
    PayloadLen += sizeof (IP6_ETHER_ADDR_OPTION);
  }

  Packet = NetbufAlloc (sizeof (EFI_IP6_HEADER) + (UINT32) PayloadLen);
  if (Packet == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Create the basic IPv6 header.
  //
  Head.FlowLabelL     = 0;
  Head.FlowLabelH     = 0;
  Head.PayloadLength  = HTONS (PayloadLen);
  Head.NextHeader     = IP6_ICMP;
  Head.HopLimit       = IP6_HOP_LIMIT;

  if (SourceAddress != NULL) {
    IP6_COPY_ADDRESS (&Head.SourceAddress, SourceAddress);
  } else {
    ZeroMem (&Head.SourceAddress, sizeof (EFI_IPv6_ADDRESS));
  }


  if (DestinationAddress != NULL) {
    IP6_COPY_ADDRESS (&Head.DestinationAddress, DestinationAddress);
  } else {
    Ip6SetToAllNodeMulticast (TRUE, IP6_LINK_LOCAL_SCOPE, &Head.DestinationAddress);
  }

  NetbufReserve (Packet, sizeof (EFI_IP6_HEADER));

  //
  // Fill in the ICMP header, and Source link-layer address if contained.
  //

  IcmpHead = (IP6_ICMP_INFORMATION_HEAD *) NetbufAllocSpace (Packet, sizeof (IP6_ICMP_INFORMATION_HEAD), FALSE);
  ASSERT (IcmpHead != NULL);
  ZeroMem (IcmpHead, sizeof (IP6_ICMP_INFORMATION_HEAD));
  IcmpHead->Head.Type = ICMP_V6_ROUTER_SOLICIT;
  IcmpHead->Head.Code = 0;

  LinkLayerOption = NULL;
  if (SourceLinkAddress != NULL) {
    LinkLayerOption = (IP6_ETHER_ADDR_OPTION *) NetbufAllocSpace (
                                                  Packet,
                                                  sizeof (IP6_ETHER_ADDR_OPTION),
                                                  FALSE
                                                  );
    ASSERT (LinkLayerOption != NULL);
    LinkLayerOption->Type   = Ip6OptionEtherSource;
    LinkLayerOption->Length = (UINT8) sizeof (IP6_ETHER_ADDR_OPTION);
    CopyMem (LinkLayerOption->EtherAddr, SourceLinkAddress, 6);
  }

  //
  // Transmit the packet
  //
  return Ip6Output (IpSb, IpIf, NULL, Packet, &Head, NULL, 0, Ip6SysPacketSent, NULL);
}

/**
  Generate a Neighbor Advertisement message and send it out to Destination Address.

  @param[in]  IpSb               The IP service to send the packet.
  @param[in]  SourceAddress      The source address of the message.
  @param[in]  DestinationAddress The destination address of the message.
  @param[in]  TargetIp6Address   The target address field in the Neighbor Solicitation
                                 message that prompted this advertisement.
  @param[in]  TargetLinkAddress  The MAC address for the target, i.e. the sender
                                 of the advertisement.
  @param[in]  IsRouter           If TRUE, indicates the sender is a router.
  @param[in]  Override           If TRUE, indicates the advertisement should override
                                 an existing cache entry and update the MAC address.
  @param[in]  Solicited          If TRUE, indicates the advertisement was sent
                                 in response to a Neighbor Solicitation from
                                 the Destination address.

  @retval EFI_OUT_OF_RESOURCES   Insufficient resources to complete the
                                 operation.
  @retval EFI_SUCCESS            The Neighbor Advertise message was successfully sent.

**/
EFI_STATUS
Ip6SendNeighborAdvertise (
  IN IP6_SERVICE            *IpSb,
  IN EFI_IPv6_ADDRESS       *SourceAddress,
  IN EFI_IPv6_ADDRESS       *DestinationAddress,
  IN EFI_IPv6_ADDRESS       *TargetIp6Address,
  IN EFI_MAC_ADDRESS        *TargetLinkAddress,
  IN BOOLEAN                IsRouter,
  IN BOOLEAN                Override,
  IN BOOLEAN                Solicited
  )
{
  NET_BUF                   *Packet;
  EFI_IP6_HEADER            Head;
  IP6_ICMP_INFORMATION_HEAD *IcmpHead;
  IP6_ETHER_ADDR_OPTION     *LinkLayerOption;
  EFI_IPv6_ADDRESS          *Target;
  UINT16                    PayloadLen;

  NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);

  //
  // The Neighbor Advertisement message must include a Target link-layer address option
  // when responding to multicast solicitation and should include such option when
  // responding to unicast solicitation. It also must include such option as unsolicited
  // advertisement.
  //
  ASSERT (DestinationAddress != NULL && TargetIp6Address != NULL && TargetLinkAddress != NULL);

  PayloadLen = (UINT16) (sizeof (IP6_ICMP_INFORMATION_HEAD) + sizeof (EFI_IPv6_ADDRESS) + sizeof (IP6_ETHER_ADDR_OPTION));

  //
  // Generate the packet to be sent
  //

  Packet = NetbufAlloc (sizeof (EFI_IP6_HEADER) + (UINT32) PayloadLen);
  if (Packet == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Create the basic IPv6 header.
  //
  Head.FlowLabelL     = 0;
  Head.FlowLabelH     = 0;
  Head.PayloadLength  = HTONS (PayloadLen);
  Head.NextHeader     = IP6_ICMP;
  Head.HopLimit       = IP6_HOP_LIMIT;

  IP6_COPY_ADDRESS (&Head.SourceAddress, SourceAddress);
  IP6_COPY_ADDRESS (&Head.DestinationAddress, DestinationAddress);

  NetbufReserve (Packet, sizeof (EFI_IP6_HEADER));

  //
  // Fill in the ICMP header, Target address, and Target link-layer address.
  // Set the Router flag, Solicited flag and Override flag.
  //

  IcmpHead = (IP6_ICMP_INFORMATION_HEAD *) NetbufAllocSpace (Packet, sizeof (IP6_ICMP_INFORMATION_HEAD), FALSE);
  ASSERT (IcmpHead != NULL);
  ZeroMem (IcmpHead, sizeof (IP6_ICMP_INFORMATION_HEAD));
  IcmpHead->Head.Type = ICMP_V6_NEIGHBOR_ADVERTISE;
  IcmpHead->Head.Code = 0;

  if (IsRouter) {
    IcmpHead->Fourth |= IP6_IS_ROUTER_FLAG;
  }

  if (Solicited) {
    IcmpHead->Fourth |= IP6_SOLICITED_FLAG;
  }

  if (Override) {
    IcmpHead->Fourth |= IP6_OVERRIDE_FLAG;
  }

  Target = (EFI_IPv6_ADDRESS *) NetbufAllocSpace (Packet, sizeof (EFI_IPv6_ADDRESS), FALSE);
  ASSERT (Target != NULL);
  IP6_COPY_ADDRESS (Target, TargetIp6Address);

  LinkLayerOption = (IP6_ETHER_ADDR_OPTION *) NetbufAllocSpace (
                                                Packet,
                                                sizeof (IP6_ETHER_ADDR_OPTION),
                                                FALSE
                                                );
  ASSERT (LinkLayerOption != NULL);
  LinkLayerOption->Type   = Ip6OptionEtherTarget;
  LinkLayerOption->Length = 1;
  CopyMem (LinkLayerOption->EtherAddr, TargetLinkAddress, 6);

  //
  // Transmit the packet
  //
  return Ip6Output (IpSb, NULL, NULL, Packet, &Head, NULL, 0, Ip6SysPacketSent, NULL);
}

/**
  Generate the Neighbor Solicitation message and send it to the Destination Address.

  @param[in]  IpSb               The IP service to send the packet
  @param[in]  SourceAddress      The source address of the message.
  @param[in]  DestinationAddress The destination address of the message.
  @param[in]  TargetIp6Address   The IP address of the target of the solicitation.
                                 It must not be a multicast address.
  @param[in]  SourceLinkAddress  The MAC address for the sender. If not NULL,
                                 a source link-layer address option will be appended
                                 to the message.

  @retval EFI_INVALID_PARAMETER  Any input parameter is invalid.
  @retval EFI_OUT_OF_RESOURCES   Insufficient resources to complete the
                                 operation.
  @retval EFI_SUCCESS            The Neighbor Advertise message was successfully sent.

**/
EFI_STATUS
Ip6SendNeighborSolicit (
  IN IP6_SERVICE            *IpSb,
  IN EFI_IPv6_ADDRESS       *SourceAddress,
  IN EFI_IPv6_ADDRESS       *DestinationAddress,
  IN EFI_IPv6_ADDRESS       *TargetIp6Address,
  IN EFI_MAC_ADDRESS        *SourceLinkAddress OPTIONAL
  )
{
  NET_BUF                   *Packet;
  EFI_IP6_HEADER            Head;
  IP6_ICMP_INFORMATION_HEAD *IcmpHead;
  IP6_ETHER_ADDR_OPTION     *LinkLayerOption;
  EFI_IPv6_ADDRESS          *Target;
  BOOLEAN                   IsDAD;
  UINT16                    PayloadLen;
  IP6_NEIGHBOR_ENTRY        *Neighbor;

  //
  // Check input parameters
  //
  NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);
  if (DestinationAddress == NULL || TargetIp6Address == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  IsDAD = FALSE;

  if (SourceAddress == NULL || (SourceAddress != NULL && NetIp6IsUnspecifiedAddr (SourceAddress))) {
    IsDAD = TRUE;
  }

  //
  // The Neighbor Solicitation message should include a source link-layer address option
  // if the solicitation is not sent by performing DAD - Duplicate Address Detection.
  // Otherwise must not include it.
  //
  PayloadLen = (UINT16) (sizeof (IP6_ICMP_INFORMATION_HEAD) + sizeof (EFI_IPv6_ADDRESS));

  if (!IsDAD) {
    if (SourceLinkAddress == NULL) {
      return EFI_INVALID_PARAMETER;
    }

    PayloadLen = (UINT16) (PayloadLen + sizeof (IP6_ETHER_ADDR_OPTION));
  }

  //
  // Generate the packet to be sent
  //

  Packet = NetbufAlloc (sizeof (EFI_IP6_HEADER) + (UINT32) PayloadLen);
  if (Packet == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Create the basic IPv6 header
  //
  Head.FlowLabelL     = 0;
  Head.FlowLabelH     = 0;
  Head.PayloadLength  = HTONS (PayloadLen);
  Head.NextHeader     = IP6_ICMP;
  Head.HopLimit       = IP6_HOP_LIMIT;

  if (SourceAddress != NULL) {
    IP6_COPY_ADDRESS (&Head.SourceAddress, SourceAddress);
  } else {
    ZeroMem (&Head.SourceAddress, sizeof (EFI_IPv6_ADDRESS));
  }

  IP6_COPY_ADDRESS (&Head.DestinationAddress, DestinationAddress);

  NetbufReserve (Packet, sizeof (EFI_IP6_HEADER));

  //
  // Fill in the ICMP header, Target address, and Source link-layer address.
  //
  IcmpHead = (IP6_ICMP_INFORMATION_HEAD *) NetbufAllocSpace (Packet, sizeof (IP6_ICMP_INFORMATION_HEAD), FALSE);
  ASSERT (IcmpHead != NULL);
  ZeroMem (IcmpHead, sizeof (IP6_ICMP_INFORMATION_HEAD));
  IcmpHead->Head.Type = ICMP_V6_NEIGHBOR_SOLICIT;
  IcmpHead->Head.Code = 0;

  Target = (EFI_IPv6_ADDRESS *) NetbufAllocSpace (Packet, sizeof (EFI_IPv6_ADDRESS), FALSE);
  ASSERT (Target != NULL);
  IP6_COPY_ADDRESS (Target, TargetIp6Address);

  LinkLayerOption = NULL;
  if (!IsDAD) {

    //
    // Fill in the source link-layer address option
    //
    LinkLayerOption = (IP6_ETHER_ADDR_OPTION *) NetbufAllocSpace (
                                                  Packet,
                                                  sizeof (IP6_ETHER_ADDR_OPTION),
                                                  FALSE
                                                  );
    ASSERT (LinkLayerOption != NULL);
    LinkLayerOption->Type   = Ip6OptionEtherSource;
    LinkLayerOption->Length = 1;
    CopyMem (LinkLayerOption->EtherAddr, SourceLinkAddress, 6);
  }

  //
  // Create a Neighbor Cache entry in the INCOMPLETE state when performing
  // address resolution.
  //
  if (!IsDAD && Ip6IsSNMulticastAddr (DestinationAddress)) {
    Neighbor = Ip6FindNeighborEntry (IpSb, TargetIp6Address);
    if (Neighbor == NULL) {
      Neighbor = Ip6CreateNeighborEntry (IpSb, Ip6OnArpResolved, TargetIp6Address, NULL);
      ASSERT (Neighbor != NULL);
    }
  }

  //
  // Transmit the packet
  //
  return Ip6Output (IpSb, IpSb->DefaultInterface, NULL, Packet, &Head, NULL, 0, Ip6SysPacketSent, NULL);
}

/**
  Process the Neighbor Solicitation message. The message may be sent for Duplicate
  Address Detection or Address Resolution.

  @param[in]  IpSb               The IP service that received the packet.
  @param[in]  Head               The IP head of the message.
  @param[in]  Packet             The content of the message with IP head removed.

  @retval EFI_SUCCESS            The packet processed successfully.
  @retval EFI_INVALID_PARAMETER  The packet is invalid.
  @retval EFI_ICMP_ERROR         The packet indicates that DAD is failed.
  @retval Others                 Failed to process the packet.

**/
EFI_STATUS
Ip6ProcessNeighborSolicit (
  IN IP6_SERVICE            *IpSb,
  IN EFI_IP6_HEADER         *Head,
  IN NET_BUF                *Packet
  )
{
  IP6_ICMP_INFORMATION_HEAD Icmp;
  EFI_IPv6_ADDRESS          Target;
  IP6_ETHER_ADDR_OPTION     LinkLayerOption;
  BOOLEAN                   IsDAD;
  BOOLEAN                   IsUnicast;
  BOOLEAN                   IsMaintained;
  IP6_DAD_ENTRY             *DupAddrDetect;
  IP6_INTERFACE             *IpIf;
  IP6_NEIGHBOR_ENTRY        *Neighbor;
  BOOLEAN                   Solicited;
  BOOLEAN                   UpdateCache;
  EFI_IPv6_ADDRESS          Dest;
  UINT16                    OptionLen;
  UINT8                     *Option;
  BOOLEAN                   Provided;
  EFI_STATUS                Status;
  VOID                      *MacAddress;

  NetbufCopy (Packet, 0, sizeof (Icmp), (UINT8 *) &Icmp);
  NetbufCopy (Packet, sizeof (Icmp), sizeof (Target), Target.Addr);

  //
  // Perform Message Validation:
  // The IP Hop Limit field has a value of 255, i.e., the packet
  // could not possibly have been forwarded by a router.
  // ICMP Code is 0.
  // Target Address is not a multicast address.
  //
  Status = EFI_INVALID_PARAMETER;

  if (Head->HopLimit != IP6_HOP_LIMIT || Icmp.Head.Code != 0 || !NetIp6IsValidUnicast (&Target)) {
    goto Exit;
  }

  //
  // ICMP length is 24 or more octets.
  //
  OptionLen = 0;
  if (Head->PayloadLength < IP6_ND_LENGTH) {
    goto Exit;
  } else {
    OptionLen = (UINT16) (Head->PayloadLength - IP6_ND_LENGTH);
    if (OptionLen != 0) {
      Option    = NetbufGetByte (Packet, IP6_ND_LENGTH, NULL);
      ASSERT (Option != NULL);

      //
      // All included options should have a length that is greater than zero.
      //
      if (!Ip6IsNDOptionValid (Option, OptionLen)) {
        goto Exit;
      }
    }
  }

  IsDAD        = NetIp6IsUnspecifiedAddr (&Head->SourceAddress);
  IsUnicast    = (BOOLEAN) !Ip6IsSNMulticastAddr (&Head->DestinationAddress);
  IsMaintained = Ip6IsOneOfSetAddress (IpSb, &Target, &IpIf, NULL);

  Provided = FALSE;
  if (OptionLen >= sizeof (IP6_ETHER_ADDR_OPTION)) {
    NetbufCopy (
      Packet,
      IP6_ND_LENGTH,
      sizeof (IP6_ETHER_ADDR_OPTION),
      (UINT8 *) &LinkLayerOption
      );
    //
    // The solicitation for neighbor discovery should include a source link-layer
    // address option. If the option is not recognized, silently ignore it.
    //
    if (LinkLayerOption.Type == Ip6OptionEtherSource) {
      if (IsDAD) {
        //
        // If the IP source address is the unspecified address, the source
        // link-layer address option must not be included in the message.
        //
        goto Exit;
      }

      Provided = TRUE;
    }
  }

  //
  // If the IP source address is the unspecified address, the IP
  // destination address is a solicited-node multicast address.
  //
  if (IsDAD && IsUnicast) {
    goto Exit;
  }

  //
  // If the target address is tentative, and the source address is a unicast address,
  // the solicitation's sender is performing address resolution on the target;
  //  the solicitation should be silently ignored.
  //
  if (!IsDAD && !IsMaintained) {
    goto Exit;
  }

  //
  // If received unicast neighbor solicitation but destination is not this node,
  // drop the packet.
  //
  if (IsUnicast && !IsMaintained) {
    goto Exit;
  }

  //
  // In DAD, when target address is a tentative address,
  // process the received neighbor solicitation message but not send out response.
  //
  if (IsDAD && !IsMaintained) {
    DupAddrDetect = Ip6FindDADEntry (IpSb, &Target, &IpIf);
    if (DupAddrDetect != NULL) {
      if (DupAddrDetect->Transmit == 0) {
        //
        // The NS is from another node to performing DAD on the same address since
        // we haven't send out any NS yet. Fail DAD for the tentative address.
        //
        Ip6OnDADFinished (FALSE, IpIf, DupAddrDetect);
        Status = EFI_ICMP_ERROR;
        goto Exit;
      }

      //
      // Check the MAC address of the incoming packet.
      //
      if (IpSb->RecvRequest.MnpToken.Packet.RxData == NULL) {
        goto Exit;
      }

      MacAddress = IpSb->RecvRequest.MnpToken.Packet.RxData->SourceAddress;
      if (MacAddress != NULL) {
        if (CompareMem (
              MacAddress,
              &IpSb->SnpMode.CurrentAddress,
              IpSb->SnpMode.HwAddressSize
              ) != 0) {
          //
          // The NS is from another node to performing DAD on the same address.
          // Fail DAD for the tentative address.
          //
          Ip6OnDADFinished (FALSE, IpIf, DupAddrDetect);
          Status = EFI_ICMP_ERROR;
        } else {
          //
          // The below layer loopback the NS we sent. Record it and wait for more.
          //
          DupAddrDetect->Receive++;
          Status = EFI_SUCCESS;
        }
      }
    }
    goto Exit;
  }

  //
  // If the solicitation does not contain a link-layer address, DO NOT create or
  // update the neighbor cache entries.
  //
  if (Provided) {
    Neighbor    = Ip6FindNeighborEntry (IpSb, &Head->SourceAddress);
    UpdateCache = FALSE;

    if (Neighbor == NULL) {
      Neighbor = Ip6CreateNeighborEntry (IpSb, Ip6OnArpResolved, &Head->SourceAddress, NULL);
      if (Neighbor == NULL) {
        Status = EFI_OUT_OF_RESOURCES;
        goto Exit;
      }
      UpdateCache = TRUE;
    } else {
      if (CompareMem (Neighbor->LinkAddress.Addr, LinkLayerOption.EtherAddr, 6) != 0) {
        UpdateCache = TRUE;
      }
    }

    if (UpdateCache) {
      Neighbor->State = EfiNeighborStale;
      Neighbor->Ticks = (UINT32) IP6_INFINIT_LIFETIME;
      CopyMem (Neighbor->LinkAddress.Addr, LinkLayerOption.EtherAddr, 6);
      //
      // Send queued packets if exist.
      //
      Neighbor->CallBack ((VOID *) Neighbor);
    }
  }

  //
  // Sends a Neighbor Advertisement as response.
  // Set the Router flag to zero since the node is a host.
  // If the source address of the solicitation is unspeicifed, and target address
  // is one of the maintained address, reply a unsolicited multicast advertisement.
  //
  if (IsDAD && IsMaintained) {
    Solicited = FALSE;
    Ip6SetToAllNodeMulticast (FALSE, IP6_LINK_LOCAL_SCOPE, &Dest);
  } else {
    Solicited = TRUE;
    IP6_COPY_ADDRESS (&Dest, &Head->SourceAddress);
  }

  Status = Ip6SendNeighborAdvertise (
             IpSb,
             &Target,
             &Dest,
             &Target,
             &IpSb->SnpMode.CurrentAddress,
             FALSE,
             TRUE,
             Solicited
             );
Exit:
  NetbufFree (Packet);
  return Status;
}

/**
  Process the Neighbor Advertisement message.

  @param[in]  IpSb               The IP service that received the packet.
  @param[in]  Head               The IP head of the message.
  @param[in]  Packet             The content of the message with IP head removed.

  @retval EFI_SUCCESS            The packet processed successfully.
  @retval EFI_INVALID_PARAMETER  The packet is invalid.
  @retval EFI_ICMP_ERROR         The packet indicates that DAD is failed.
  @retval Others                 Failed to process the packet.

**/
EFI_STATUS
Ip6ProcessNeighborAdvertise (
  IN IP6_SERVICE            *IpSb,
  IN EFI_IP6_HEADER         *Head,
  IN NET_BUF                *Packet
  )
{
  IP6_ICMP_INFORMATION_HEAD Icmp;
  EFI_IPv6_ADDRESS          Target;
  IP6_ETHER_ADDR_OPTION     LinkLayerOption;
  BOOLEAN                   Provided;
  INTN                      Compare;
  IP6_NEIGHBOR_ENTRY        *Neighbor;
  IP6_DEFAULT_ROUTER        *DefaultRouter;
  BOOLEAN                   Solicited;
  BOOLEAN                   IsRouter;
  BOOLEAN                   Override;
  IP6_DAD_ENTRY             *DupAddrDetect;
  IP6_INTERFACE             *IpIf;
  UINT16                    OptionLen;
  UINT8                     *Option;
  EFI_STATUS                Status;

  NetbufCopy (Packet, 0, sizeof (Icmp), (UINT8 *) &Icmp);
  NetbufCopy (Packet, sizeof (Icmp), sizeof (Target), Target.Addr);

  //
  // Validate the incoming Neighbor Advertisement
  //
  Status = EFI_INVALID_PARAMETER;
  //
  // The IP Hop Limit field has a value of 255, i.e., the packet
  // could not possibly have been forwarded by a router.
  // ICMP Code is 0.
  // Target Address is not a multicast address.
  //
  if (Head->HopLimit != IP6_HOP_LIMIT || Icmp.Head.Code != 0 || !NetIp6IsValidUnicast (&Target)) {
    goto Exit;
  }

  //
  // ICMP length is 24 or more octets.
  //
  Provided  = FALSE;
  OptionLen = 0;
  if (Head->PayloadLength < IP6_ND_LENGTH) {
    goto Exit;
  } else {
    OptionLen = (UINT16) (Head->PayloadLength - IP6_ND_LENGTH);
    if (OptionLen != 0) {
      Option    = NetbufGetByte (Packet, IP6_ND_LENGTH, NULL);
      ASSERT (Option != NULL);

      //
      // All included options should have a length that is greater than zero.
      //
      if (!Ip6IsNDOptionValid (Option, OptionLen)) {
        goto Exit;
      }
    }
  }

  //
  // If the IP destination address is a multicast address, Solicited Flag is ZERO.
  //
  Solicited = FALSE;
  if ((Icmp.Fourth & IP6_SOLICITED_FLAG) == IP6_SOLICITED_FLAG) {
    Solicited = TRUE;
  }
  if (IP6_IS_MULTICAST (&Head->DestinationAddress) && Solicited) {
    goto Exit;
  }

  //
  // DAD - Check whether the Target is one of our tentative address.
  //
  DupAddrDetect = Ip6FindDADEntry (IpSb, &Target, &IpIf);
  if (DupAddrDetect != NULL) {
    //
    // DAD fails, some other node is using this address.
    //
    NetbufFree (Packet);
    Ip6OnDADFinished (FALSE, IpIf, DupAddrDetect);
    return EFI_ICMP_ERROR;
  }

  //
  // Search the Neighbor Cache for the target's entry. If no entry exists,
  // the advertisement should be silently discarded.
  //
  Neighbor = Ip6FindNeighborEntry (IpSb, &Target);
  if (Neighbor == NULL) {
    goto Exit;
  }

  //
  // Get IsRouter Flag and Override Flag
  //
  IsRouter = FALSE;
  Override = FALSE;
  if ((Icmp.Fourth & IP6_IS_ROUTER_FLAG) == IP6_IS_ROUTER_FLAG) {
    IsRouter = TRUE;
  }
  if ((Icmp.Fourth & IP6_OVERRIDE_FLAG) == IP6_OVERRIDE_FLAG) {
    Override = TRUE;
  }

  //
  // Check whether link layer option is included.
  //
  if (OptionLen >= sizeof (IP6_ETHER_ADDR_OPTION)) {
    NetbufCopy (
      Packet,
      IP6_ND_LENGTH,
      sizeof (IP6_ETHER_ADDR_OPTION),
      (UINT8 *) &LinkLayerOption
      );

    if (LinkLayerOption.Type == Ip6OptionEtherTarget) {
      Provided = TRUE;
    }
  }

  Compare = 0;
  if (Provided) {
    Compare = CompareMem (Neighbor->LinkAddress.Addr, LinkLayerOption.EtherAddr, 6);
  }

  if (!Neighbor->IsRouter && IsRouter) {
    DefaultRouter = Ip6FindDefaultRouter (IpSb, &Target);
    if (DefaultRouter != NULL) {
      DefaultRouter->NeighborCache = Neighbor;
    }
  }

  if (Neighbor->State == EfiNeighborInComplete) {
    //
    // If the target's Neighbor Cache entry is in INCOMPLETE state and no
    // Target Link-Layer address option is included while link layer has
    // address, the message should be silently discarded.
    //
    if (!Provided) {
      goto Exit;
    }
    //
    // Update the Neighbor Cache
    //
    CopyMem (Neighbor->LinkAddress.Addr, LinkLayerOption.EtherAddr, 6);
    if (Solicited) {
      Neighbor->State = EfiNeighborReachable;
      Neighbor->Ticks = IP6_GET_TICKS (IpSb->ReachableTime);
    } else {
      Neighbor->State = EfiNeighborStale;
      Neighbor->Ticks = (UINT32) IP6_INFINIT_LIFETIME;
      //
      // Send any packets queued for the neighbor awaiting address resolution.
      //
      Neighbor->CallBack ((VOID *) Neighbor);
    }

    Neighbor->IsRouter = IsRouter;

  } else {
    if (!Override && Compare != 0) {
      //
      // When the Override Flag is clear and supplied link-layer address differs from
      // that in the cache, if the state of the entry is not REACHABLE, ignore the
      // message. Otherwise set it to STALE but do not update the entry in any
      // other way.
      //
      if (Neighbor->State == EfiNeighborReachable) {
        Neighbor->State = EfiNeighborStale;
        Neighbor->Ticks = (UINT32) IP6_INFINIT_LIFETIME;
      }
    } else {
      if (Compare != 0) {
        CopyMem (Neighbor->LinkAddress.Addr, LinkLayerOption.EtherAddr, 6);
      }
      //
      // Update the entry's state
      //
      if (Solicited) {
        Neighbor->State = EfiNeighborReachable;
        Neighbor->Ticks = IP6_GET_TICKS (IpSb->ReachableTime);
      } else {
        if (Compare != 0) {
          Neighbor->State = EfiNeighborStale;
          Neighbor->Ticks = (UINT32) IP6_INFINIT_LIFETIME;
        }
      }

      //
      // When IsRouter is changed from TRUE to FALSE, remove the router from the
      // Default Router List and remove the Destination Cache entries for all destinations
      // using the neighbor as a router.
      //
      if (Neighbor->IsRouter && !IsRouter) {
        DefaultRouter = Ip6FindDefaultRouter (IpSb, &Target);
        if (DefaultRouter != NULL) {
          Ip6DestroyDefaultRouter (IpSb, DefaultRouter);
        }
      }

      Neighbor->IsRouter = IsRouter;
    }
  }

  if (Neighbor->State == EfiNeighborReachable) {
    Neighbor->CallBack ((VOID *) Neighbor);
  }

  Status = EFI_SUCCESS;

Exit:
  NetbufFree (Packet);
  return Status;
}

/**
  Process the Router Advertisement message according to RFC4861.

  @param[in]  IpSb               The IP service that received the packet.
  @param[in]  Head               The IP head of the message.
  @param[in]  Packet             The content of the message with the IP head removed.

  @retval EFI_SUCCESS            The packet processed successfully.
  @retval EFI_INVALID_PARAMETER  The packet is invalid.
  @retval EFI_OUT_OF_RESOURCES   Insufficient resources to complete the
                                 operation.
  @retval Others                 Failed to process the packet.

**/
EFI_STATUS
Ip6ProcessRouterAdvertise (
  IN IP6_SERVICE            *IpSb,
  IN EFI_IP6_HEADER         *Head,
  IN NET_BUF                *Packet
  )
{
  IP6_ICMP_INFORMATION_HEAD Icmp;
  UINT32                    ReachableTime;
  UINT32                    RetransTimer;
  UINT16                    RouterLifetime;
  UINT16                    Offset;
  UINT8                     Type;
  UINT8                     Length;
  IP6_ETHER_ADDR_OPTION     LinkLayerOption;
  UINT32                    Fourth;
  UINT8                     CurHopLimit;
  BOOLEAN                   Mflag;
  BOOLEAN                   Oflag;
  IP6_DEFAULT_ROUTER        *DefaultRouter;
  IP6_NEIGHBOR_ENTRY        *NeighborCache;
  EFI_MAC_ADDRESS           LinkLayerAddress;
  IP6_MTU_OPTION            MTUOption;
  IP6_PREFIX_INFO_OPTION    PrefixOption;
  IP6_PREFIX_LIST_ENTRY     *PrefixList;
  BOOLEAN                   OnLink;
  BOOLEAN                   Autonomous;
  EFI_IPv6_ADDRESS          StatelessAddress;
  EFI_STATUS                Status;
  UINT16                    OptionLen;
  UINT8                     *Option;
  INTN                      Result;

  Status = EFI_INVALID_PARAMETER;

  if (IpSb->Ip6ConfigInstance.Policy != Ip6ConfigPolicyAutomatic) {
    //
    // Skip the process below as it's not required under the current policy.
    //
    goto Exit;
  }

  NetbufCopy (Packet, 0, sizeof (Icmp), (UINT8 *) &Icmp);

  //
  // Validate the incoming Router Advertisement
  //

  //
  // The IP source address must be a link-local address
  //
  if (!NetIp6IsLinkLocalAddr (&Head->SourceAddress)) {
    goto Exit;
  }
  //
  // The IP Hop Limit field has a value of 255, i.e. the packet
  // could not possibly have been forwarded by a router.
  // ICMP Code is 0.
  // ICMP length (derived from the IP length) is 16 or more octets.
  //
  if (Head->HopLimit != IP6_HOP_LIMIT || Icmp.Head.Code != 0 ||
      Head->PayloadLength < IP6_RA_LENGTH) {
    goto Exit;
  }

  //
  // All included options have a length that is greater than zero.
  //
  OptionLen = (UINT16) (Head->PayloadLength - IP6_RA_LENGTH);
  if (OptionLen != 0) {
    Option    = NetbufGetByte (Packet, IP6_RA_LENGTH, NULL);
    ASSERT (Option != NULL);

    if (!Ip6IsNDOptionValid (Option, OptionLen)) {
      goto Exit;
    }
  }

  //
  // Process Fourth field.
  // In Router Advertisement, Fourth is composed of CurHopLimit (8bit), M flag, O flag,
  // and Router Lifetime (16 bit).
  //

  Fourth = NTOHL (Icmp.Fourth);
  CopyMem (&RouterLifetime, &Fourth, sizeof (UINT16));

  //
  // If the source address already in the default router list, update it.
  // Otherwise create a new entry.
  // A Lifetime of zero indicates that the router is not a default router.
  //
  DefaultRouter = Ip6FindDefaultRouter (IpSb, &Head->SourceAddress);
  if (DefaultRouter == NULL) {
    if (RouterLifetime != 0) {
      DefaultRouter = Ip6CreateDefaultRouter (IpSb, &Head->SourceAddress, RouterLifetime);
      if (DefaultRouter == NULL) {
        Status = EFI_OUT_OF_RESOURCES;
        goto Exit;
      }
    }
  } else {
    if (RouterLifetime != 0) {
      DefaultRouter->Lifetime = RouterLifetime;
      //
      // Check the corresponding neighbor cache entry here.
      //
      if (DefaultRouter->NeighborCache == NULL) {
        DefaultRouter->NeighborCache = Ip6FindNeighborEntry (IpSb, &Head->SourceAddress);
      }
    } else {
      //
      // If the address is in the host's default router list and the router lifetime is zero,
      // immediately time-out the entry.
      //
      Ip6DestroyDefaultRouter (IpSb, DefaultRouter);
    }
  }

  CurHopLimit = *((UINT8 *) &Fourth + 3);
  if (CurHopLimit != 0) {
    IpSb->CurHopLimit = CurHopLimit;
  }

  Mflag = FALSE;
  Oflag = FALSE;
  if ((*((UINT8 *) &Fourth + 2) & IP6_M_ADDR_CONFIG_FLAG) == IP6_M_ADDR_CONFIG_FLAG) {
    Mflag = TRUE;
  } else {
    if ((*((UINT8 *) &Fourth + 2) & IP6_O_CONFIG_FLAG) == IP6_O_CONFIG_FLAG) {
      Oflag = TRUE;
    }
  }

  if (Mflag || Oflag) {
    //
    // Use Ip6Config to get available addresses or other configuration from DHCP.
    //
    Ip6ConfigStartStatefulAutoConfig (&IpSb->Ip6ConfigInstance, Oflag);
  }

  //
  // Process Reachable Time and Retrans Timer fields.
  //
  NetbufCopy (Packet, sizeof (Icmp), sizeof (UINT32), (UINT8 *) &ReachableTime);
  NetbufCopy (Packet, sizeof (Icmp) + sizeof (UINT32), sizeof (UINT32), (UINT8 *) &RetransTimer);
  ReachableTime = NTOHL (ReachableTime);
  RetransTimer  = NTOHL (RetransTimer);

  if (ReachableTime != 0 && ReachableTime != IpSb->BaseReachableTime) {
    //
    // If new value is not unspecified and differs from the previous one, record it
    // in BaseReachableTime and recompute a ReachableTime.
    //
    IpSb->BaseReachableTime = ReachableTime;
    Ip6UpdateReachableTime (IpSb);
  }

  if (RetransTimer != 0) {
    IpSb->RetransTimer = RetransTimer;
  }

  //
  // IsRouter flag must be set to TRUE if corresponding neighbor cache entry exists.
  //
  NeighborCache = Ip6FindNeighborEntry (IpSb, &Head->SourceAddress);
  if (NeighborCache != NULL) {
    NeighborCache->IsRouter = TRUE;
  }

  //
  // If an valid router advertisment is received, stops router solicitation.
  //
  IpSb->RouterAdvertiseReceived = TRUE;

  //
  // The only defined options that may appear are the Source
  // Link-Layer Address, Prefix information and MTU options.
  // All included options have a length that is greater than zero.
  //
  Offset = 16;
  while (Offset < Head->PayloadLength) {
    NetbufCopy (Packet, Offset, sizeof (UINT8), &Type);
    switch (Type) {
    case Ip6OptionEtherSource:
      //
      // Update the neighbor cache
      //
      NetbufCopy (Packet, Offset, sizeof (IP6_ETHER_ADDR_OPTION), (UINT8 *) &LinkLayerOption);
      if (LinkLayerOption.Length <= 0) {
        goto Exit;
      }

      ZeroMem (&LinkLayerAddress, sizeof (EFI_MAC_ADDRESS));
      CopyMem (&LinkLayerAddress, LinkLayerOption.EtherAddr, 6);

      if (NeighborCache == NULL) {
        NeighborCache = Ip6CreateNeighborEntry (
                          IpSb,
                          Ip6OnArpResolved,
                          &Head->SourceAddress,
                          &LinkLayerAddress
                          );
        if (NeighborCache == NULL) {
          Status = EFI_OUT_OF_RESOURCES;
          goto Exit;
        }
        NeighborCache->IsRouter = TRUE;
        NeighborCache->State    = EfiNeighborStale;
        NeighborCache->Ticks    = (UINT32) IP6_INFINIT_LIFETIME;
      } else {
        Result = CompareMem (&LinkLayerAddress, &NeighborCache->LinkAddress, 6);

        //
        // If the link-local address is the same as that already in the cache,
        // the cache entry's state remains unchanged. Otherwise update the
        // reachability state to STALE.
        //
        if ((NeighborCache->State == EfiNeighborInComplete) || (Result != 0)) {
          CopyMem (&NeighborCache->LinkAddress, &LinkLayerAddress, 6);

          NeighborCache->Ticks = (UINT32) IP6_INFINIT_LIFETIME;

          if (NeighborCache->State == EfiNeighborInComplete) {
            //
            // Send queued packets if exist.
            //
            NeighborCache->State = EfiNeighborStale;
            NeighborCache->CallBack ((VOID *) NeighborCache);
          } else {
            NeighborCache->State = EfiNeighborStale;
          }
        }
      }

      Offset = (UINT16) (Offset + (UINT16) LinkLayerOption.Length * 8);
      break;
    case Ip6OptionPrefixInfo:
      NetbufCopy (Packet, Offset, sizeof (IP6_PREFIX_INFO_OPTION), (UINT8 *) &PrefixOption);
      if (PrefixOption.Length != 4) {
        goto Exit;
      }
      PrefixOption.ValidLifetime     = NTOHL (PrefixOption.ValidLifetime);
      PrefixOption.PreferredLifetime = NTOHL (PrefixOption.PreferredLifetime);

      //
      // Get L and A flag, recorded in the lower 2 bits of Reserved1
      //
      OnLink = FALSE;
      if ((PrefixOption.Reserved1 & IP6_ON_LINK_FLAG) == IP6_ON_LINK_FLAG) {
        OnLink = TRUE;
      }
      Autonomous = FALSE;
      if ((PrefixOption.Reserved1 & IP6_AUTO_CONFIG_FLAG) == IP6_AUTO_CONFIG_FLAG) {
        Autonomous = TRUE;
      }

      //
      // If the prefix is the link-local prefix, silently ignore the prefix option.
      //
      if (PrefixOption.PrefixLength == IP6_LINK_LOCAL_PREFIX_LENGTH &&
          NetIp6IsLinkLocalAddr (&PrefixOption.Prefix)
          ) {
        Offset += sizeof (IP6_PREFIX_INFO_OPTION);
        break;
      }
      //
      // Do following if on-link flag is set according to RFC4861.
      //
      if (OnLink) {
        PrefixList = Ip6FindPrefixListEntry (
                       IpSb,
                       TRUE,
                       PrefixOption.PrefixLength,
                       &PrefixOption.Prefix
                       );
        //
        // Create a new entry for the prefix, if the ValidLifetime is zero,
        // silently ignore the prefix option.
        //
        if (PrefixList == NULL && PrefixOption.ValidLifetime != 0) {
          PrefixList = Ip6CreatePrefixListEntry (
                         IpSb,
                         TRUE,
                         PrefixOption.ValidLifetime,
                         PrefixOption.PreferredLifetime,
                         PrefixOption.PrefixLength,
                         &PrefixOption.Prefix
                         );
          if (PrefixList == NULL) {
            Status = EFI_OUT_OF_RESOURCES;
            goto Exit;
          }
        } else if (PrefixList != NULL) {
          if (PrefixOption.ValidLifetime != 0) {
            PrefixList->ValidLifetime = PrefixOption.ValidLifetime;
          } else {
            //
            // If the prefix exists and incoming ValidLifetime is zero, immediately
            // remove the prefix.
            Ip6DestroyPrefixListEntry (IpSb, PrefixList, OnLink, TRUE);
          }
        }
      }

      //
      // Do following if Autonomous flag is set according to RFC4862.
      //
      if (Autonomous && PrefixOption.PreferredLifetime <= PrefixOption.ValidLifetime) {
        PrefixList = Ip6FindPrefixListEntry (
                       IpSb,
                       FALSE,
                       PrefixOption.PrefixLength,
                       &PrefixOption.Prefix
                       );
        //
        // Create a new entry for the prefix, and form an address by prefix + interface id
        // If the sum of the prefix length and interface identifier length
        // does not equal 128 bits, the Prefix Information option MUST be ignored.
        //
        if (PrefixList == NULL &&
            PrefixOption.ValidLifetime != 0 &&
            PrefixOption.PrefixLength + IpSb->InterfaceIdLen * 8 == 128
            ) {
          //
          // Form the address in network order.
          //
          CopyMem (&StatelessAddress, &PrefixOption.Prefix, sizeof (UINT64));
          CopyMem (&StatelessAddress.Addr[8], IpSb->InterfaceId, sizeof (UINT64));

          //
          // If the address is not yet in the assigned address list, adds it into.
          //
          if (!Ip6IsOneOfSetAddress (IpSb, &StatelessAddress, NULL, NULL)) {
            //
            // And also not in the DAD process, check its uniqeness firstly.
            //
            if (Ip6FindDADEntry (IpSb, &StatelessAddress, NULL) == NULL) {
              Status = Ip6SetAddress (
                         IpSb->DefaultInterface,
                         &StatelessAddress,
                         FALSE,
                         PrefixOption.PrefixLength,
                         PrefixOption.ValidLifetime,
                         PrefixOption.PreferredLifetime,
                         NULL,
                         NULL
                         );
              if (EFI_ERROR (Status)) {
                goto Exit;
              }
            }
          }

          //
          // Adds the prefix option to stateless prefix option list.
          //
          PrefixList = Ip6CreatePrefixListEntry (
                         IpSb,
                         FALSE,
                         PrefixOption.ValidLifetime,
                         PrefixOption.PreferredLifetime,
                         PrefixOption.PrefixLength,
                         &PrefixOption.Prefix
                         );
          if (PrefixList == NULL) {
            Status = EFI_OUT_OF_RESOURCES;
            goto Exit;
          }
        } else if (PrefixList != NULL) {

          //
          // Reset the preferred lifetime of the address if the advertised prefix exists.
          // Perform specific action to valid lifetime together.
          //
          PrefixList->PreferredLifetime = PrefixOption.PreferredLifetime;
          if ((PrefixOption.ValidLifetime > 7200) ||
              (PrefixOption.ValidLifetime > PrefixList->ValidLifetime)) {
            //
            // If the received Valid Lifetime is greater than 2 hours or
            // greater than RemainingLifetime, set the valid lifetime of the
            // corresponding address to the advertised Valid Lifetime.
            //
            PrefixList->ValidLifetime = PrefixOption.ValidLifetime;

          } else if (PrefixList->ValidLifetime <= 7200) {
            //
            // If RemainingLifetime is less than or equls to 2 hours, ignore the
            // Prefix Information option with regards to the valid lifetime.
            // TODO: If this option has been authenticated, set the valid lifetime.
            //
          } else {
            //
            // Otherwise, reset the valid lifetime of the corresponding
            // address to 2 hours.
            //
            PrefixList->ValidLifetime = 7200;
          }
        }
      }

      Offset += sizeof (IP6_PREFIX_INFO_OPTION);
      break;
    case Ip6OptionMtu:
      NetbufCopy (Packet, Offset, sizeof (IP6_MTU_OPTION), (UINT8 *) &MTUOption);
      if (MTUOption.Length != 1) {
        goto Exit;
      }

      //
      // Use IPv6 minimum link MTU 1280 bytes as the maximum packet size in order
      // to omit implementation of Path MTU Discovery. Thus ignore the MTU option
      // in Router Advertisement.
      //

      Offset += sizeof (IP6_MTU_OPTION);
      break;
    default:
      //
      // Silently ignore unrecognized options
      //
      NetbufCopy (Packet, Offset + sizeof (UINT8), sizeof (UINT8), &Length);
      if (Length <= 0) {
        goto Exit;
      }

      Offset = (UINT16) (Offset + (UINT16) Length * 8);
      break;
    }
  }

  Status = EFI_SUCCESS;

Exit:
  NetbufFree (Packet);
  return Status;
}

/**
  Process the ICMPv6 redirect message. Find the instance, then update
  its route cache.

  @param[in]  IpSb               The IP6 service binding instance that received
                                 the packet.
  @param[in]  Head               The IP head of the received ICMPv6 packet.
  @param[in]  Packet             The content of the ICMPv6 redirect packet with
                                 the IP head removed.

  @retval EFI_INVALID_PARAMETER  The parameter is invalid.
  @retval EFI_OUT_OF_RESOURCES   Insuffcient resources to complete the
                                 operation.
  @retval EFI_SUCCESS            Successfully updated the route caches.

**/
EFI_STATUS
Ip6ProcessRedirect (
  IN IP6_SERVICE            *IpSb,
  IN EFI_IP6_HEADER         *Head,
  IN NET_BUF                *Packet
  )
{
  IP6_ICMP_INFORMATION_HEAD *Icmp;
  EFI_IPv6_ADDRESS          *Target;
  EFI_IPv6_ADDRESS          *IcmpDest;
  UINT8                     *Option;
  UINT16                    OptionLen;
  IP6_ROUTE_ENTRY           *RouteEntry;
  IP6_ROUTE_CACHE_ENTRY     *RouteCache;
  IP6_NEIGHBOR_ENTRY        *NeighborCache;
  INT32                     Length;
  UINT8                     OptLen;
  IP6_ETHER_ADDR_OPTION     *LinkLayerOption;
  EFI_MAC_ADDRESS           Mac;
  UINT32                    Index;
  BOOLEAN                   IsRouter;
  EFI_STATUS                Status;
  INTN                      Result;

  Status = EFI_INVALID_PARAMETER;

  Icmp = (IP6_ICMP_INFORMATION_HEAD *) NetbufGetByte (Packet, 0, NULL);
  if (Icmp == NULL) {
    goto Exit;
  }

  //
  // Validate the incoming Redirect message
  //

  //
  // The IP Hop Limit field has a value of 255, i.e. the packet
  // could not possibly have been forwarded by a router.
  // ICMP Code is 0.
  // ICMP length (derived from the IP length) is 40 or more octets.
  //
  if (Head->HopLimit != IP6_HOP_LIMIT || Icmp->Head.Code != 0 ||
      Head->PayloadLength < IP6_REDITECT_LENGTH) {
    goto Exit;
  }

  //
  // The IP source address must be a link-local address
  //
  if (!NetIp6IsLinkLocalAddr (&Head->SourceAddress)) {
    goto Exit;
  }

  //
  // The dest of this ICMP redirect message is not us.
  //
  if (!Ip6IsOneOfSetAddress (IpSb, &Head->DestinationAddress, NULL, NULL)) {
    goto Exit;
  }

  //
  // All included options have a length that is greater than zero.
  //
  OptionLen = (UINT16) (Head->PayloadLength - IP6_REDITECT_LENGTH);
  if (OptionLen != 0) {
    Option    = NetbufGetByte (Packet, IP6_REDITECT_LENGTH, NULL);
    ASSERT (Option != NULL);

    if (!Ip6IsNDOptionValid (Option, OptionLen)) {
      goto Exit;
    }
  }

  Target   = (EFI_IPv6_ADDRESS *) (Icmp + 1);
  IcmpDest = Target + 1;

  //
  // The ICMP Destination Address field in the redirect message does not contain
  // a multicast address.
  //
  if (IP6_IS_MULTICAST (IcmpDest)) {
    goto Exit;
  }

  //
  // The ICMP Target Address is either a link-local address (when redirected to
  // a router) or the same as the ICMP Destination Address (when redirected to
  // the on-link destination).
  //
  IsRouter = (BOOLEAN) !EFI_IP6_EQUAL (Target, IcmpDest);
  if (!NetIp6IsLinkLocalAddr (Target) && IsRouter) {
    goto Exit;
  }

  //
  // Check the options. The only interested option here is the target-link layer
  // address option.
  //
  Length          = Packet->TotalSize - 40;
  Option          = (UINT8 *) (IcmpDest + 1);
  LinkLayerOption = NULL;
  while (Length > 0) {
    switch (*Option) {
    case Ip6OptionEtherTarget:

      LinkLayerOption = (IP6_ETHER_ADDR_OPTION *) Option;
      OptLen          = LinkLayerOption->Length;
      if (OptLen != 1) {
        //
        // For ethernet, the length must be 1.
        //
        goto Exit;
      }
      break;

    default:

      OptLen = *(Option + 1);
      if (OptLen == 0) {
        //
        // A length of 0 is invalid.
        //
        goto Exit;
      }
      break;
    }

    Length -= 8 * OptLen;
    Option += 8 * OptLen;
  }

  if (Length != 0) {
    goto Exit;
  }

  //
  // The IP source address of the Redirect is the same as the current
  // first-hop router for the specified ICMP Destination Address.
  //
  RouteCache = Ip6FindRouteCache (IpSb->RouteTable, IcmpDest, &Head->DestinationAddress);
  if (RouteCache != NULL) {
    if (!EFI_IP6_EQUAL (&RouteCache->NextHop, &Head->SourceAddress)) {
      //
      // The source of this Redirect message must match the NextHop of the
      // corresponding route cache entry.
      //
      goto Exit;
    }

    //
    // Update the NextHop.
    //
    IP6_COPY_ADDRESS (&RouteCache->NextHop, Target);

    if (!IsRouter) {
      RouteEntry = (IP6_ROUTE_ENTRY *) RouteCache->Tag;
      RouteEntry->Flag = RouteEntry->Flag | IP6_DIRECT_ROUTE;
    }

  } else {
    //
    // Get the Route Entry.
    //
    RouteEntry = Ip6FindRouteEntry (IpSb->RouteTable, IcmpDest, NULL);
    if (RouteEntry == NULL) {
      RouteEntry = Ip6CreateRouteEntry (IcmpDest, 0, NULL);
      if (RouteEntry == NULL) {
        Status = EFI_OUT_OF_RESOURCES;
        goto Exit;
      }
    }

    if (!IsRouter) {
      RouteEntry->Flag = IP6_DIRECT_ROUTE;
    }

    //
    // Create a route cache for this.
    //
    RouteCache = Ip6CreateRouteCacheEntry (
                   IcmpDest,
                   &Head->DestinationAddress,
                   Target,
                   (UINTN) RouteEntry
                   );
    if (RouteCache == NULL) {
      Status = EFI_OUT_OF_RESOURCES;
      goto Exit;
    }

    //
    // Insert the newly created route cache entry.
    //
    Index = IP6_ROUTE_CACHE_HASH (IcmpDest, &Head->DestinationAddress);
    InsertHeadList (&IpSb->RouteTable->Cache.CacheBucket[Index], &RouteCache->Link);
  }

  //
  // Try to locate the neighbor cache for the Target.
  //
  NeighborCache = Ip6FindNeighborEntry (IpSb, Target);

  if (LinkLayerOption != NULL) {
    if (NeighborCache == NULL) {
      //
      // Create a neighbor cache for the Target.
      //
      ZeroMem (&Mac, sizeof (EFI_MAC_ADDRESS));
      CopyMem (&Mac, LinkLayerOption->EtherAddr, 6);
      NeighborCache = Ip6CreateNeighborEntry (IpSb, Ip6OnArpResolved, Target, &Mac);
      if (NeighborCache == NULL) {
        //
        // Just report a success here. The neighbor cache can be created in
        // some other place.
        //
        Status = EFI_SUCCESS;
        goto Exit;
      }

      NeighborCache->State = EfiNeighborStale;
      NeighborCache->Ticks = (UINT32) IP6_INFINIT_LIFETIME;
    } else {
      Result = CompareMem (LinkLayerOption->EtherAddr, &NeighborCache->LinkAddress, 6);

      //
      // If the link-local address is the same as that already in the cache,
      // the cache entry's state remains unchanged. Otherwise update the
      // reachability state to STALE.
      //
      if ((NeighborCache->State == EfiNeighborInComplete) || (Result != 0)) {
        CopyMem (&NeighborCache->LinkAddress, LinkLayerOption->EtherAddr, 6);

        NeighborCache->Ticks = (UINT32) IP6_INFINIT_LIFETIME;

        if (NeighborCache->State == EfiNeighborInComplete) {
          //
          // Send queued packets if exist.
          //
          NeighborCache->State = EfiNeighborStale;
          NeighborCache->CallBack ((VOID *) NeighborCache);
        } else {
          NeighborCache->State = EfiNeighborStale;
        }
      }
    }
  }

  if (NeighborCache != NULL && IsRouter) {
    //
    // The Target is a router, set IsRouter to TRUE.
    //
    NeighborCache->IsRouter = TRUE;
  }

  Status = EFI_SUCCESS;

Exit:
  NetbufFree (Packet);
  return Status;
}

/**
  Add Neighbor cache entries. It is a work function for EfiIp6Neighbors().

  @param[in]  IpSb               The IP6 service binding instance.
  @param[in]  TargetIp6Address   Pointer to Target IPv6 address.
  @param[in]  TargetLinkAddress  Pointer to link-layer address of the target. Ignored if NULL.
  @param[in]  Timeout            Time in 100-ns units that this entry will remain in the neighbor
                                 cache. It will be deleted after Timeout. A value of zero means that
                                 the entry is permanent. A non-zero value means that the entry is
                                 dynamic.
  @param[in]  Override           If TRUE, the cached link-layer address of the matching entry will
                                 be overridden and updated; if FALSE, and if a
                                 corresponding cache entry already existed, EFI_ACCESS_DENIED
                                 will be returned.

  @retval  EFI_SUCCESS           The neighbor cache entry has been added.
  @retval  EFI_OUT_OF_RESOURCES  Could not add the entry to the neighbor cache
                                 due to insufficient resources.
  @retval  EFI_NOT_FOUND         TargetLinkAddress is NULL.
  @retval  EFI_ACCESS_DENIED     The to-be-added entry is already defined in the neighbor cache,
                                 and that entry is tagged as un-overridden (when DeleteFlag
                                 is FALSE).

**/
EFI_STATUS
Ip6AddNeighbor (
  IN IP6_SERVICE            *IpSb,
  IN EFI_IPv6_ADDRESS       *TargetIp6Address,
  IN EFI_MAC_ADDRESS        *TargetLinkAddress OPTIONAL,
  IN UINT32                 Timeout,
  IN BOOLEAN                Override
  )
{
  IP6_NEIGHBOR_ENTRY        *Neighbor;

  Neighbor = Ip6FindNeighborEntry (IpSb, TargetIp6Address);
  if (Neighbor != NULL) {
    if (!Override) {
      return EFI_ACCESS_DENIED;
    } else {
      if (TargetLinkAddress != NULL) {
        IP6_COPY_LINK_ADDRESS (&Neighbor->LinkAddress, TargetLinkAddress);
      }
    }
  } else {
    if (TargetLinkAddress == NULL) {
      return EFI_NOT_FOUND;
    }

    Neighbor = Ip6CreateNeighborEntry (IpSb, Ip6OnArpResolved, TargetIp6Address, TargetLinkAddress);
    if (Neighbor == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }
  }

  Neighbor->State = EfiNeighborReachable;

  if (Timeout != 0) {
    Neighbor->Ticks   = IP6_GET_TICKS (Timeout / TICKS_PER_MS);
    Neighbor->Dynamic = TRUE;
  } else {
    Neighbor->Ticks   = (UINT32) IP6_INFINIT_LIFETIME;
  }

  return EFI_SUCCESS;
}

/**
  Delete or update Neighbor cache entries. It is a work function for EfiIp6Neighbors().

  @param[in]  IpSb               The IP6 service binding instance.
  @param[in]  TargetIp6Address   Pointer to Target IPv6 address.
  @param[in]  TargetLinkAddress  Pointer to link-layer address of the target. Ignored if NULL.
  @param[in]  Timeout            Time in 100-ns units that this entry will remain in the neighbor
                                 cache. It will be deleted after Timeout. A value of zero means that
                                 the entry is permanent. A non-zero value means that the entry is
                                 dynamic.
  @param[in]  Override           If TRUE, the cached link-layer address of the matching entry will
                                 be overridden and updated; if FALSE, and if a
                                 corresponding cache entry already existed, EFI_ACCESS_DENIED
                                 will be returned.

  @retval  EFI_SUCCESS           The neighbor cache entry has been updated or deleted.
  @retval  EFI_NOT_FOUND         This entry is not in the neighbor cache.

**/
EFI_STATUS
Ip6DelNeighbor (
  IN IP6_SERVICE            *IpSb,
  IN EFI_IPv6_ADDRESS       *TargetIp6Address,
  IN EFI_MAC_ADDRESS        *TargetLinkAddress OPTIONAL,
  IN UINT32                 Timeout,
  IN BOOLEAN                Override
  )
{
  IP6_NEIGHBOR_ENTRY        *Neighbor;

  Neighbor = Ip6FindNeighborEntry (IpSb, TargetIp6Address);
  if (Neighbor == NULL) {
    return EFI_NOT_FOUND;
  }

  RemoveEntryList (&Neighbor->Link);
  FreePool (Neighbor);

  return EFI_SUCCESS;
}

/**
  The heartbeat timer of ND module in IP6_TIMER_INTERVAL_IN_MS milliseconds.
  This time routine handles DAD module and neighbor state transition.
  It is also responsible for sending out router solicitations.

  @param[in]  Event                 The IP6 service instance's heartbeat timer.
  @param[in]  Context               The IP6 service instance.

**/
VOID
EFIAPI
Ip6NdFasterTimerTicking (
  IN EFI_EVENT              Event,
  IN VOID                   *Context
  )
{
  LIST_ENTRY                *Entry;
  LIST_ENTRY                *Next;
  LIST_ENTRY                *Entry2;
  IP6_INTERFACE             *IpIf;
  IP6_DELAY_JOIN_LIST       *DelayNode;
  EFI_IPv6_ADDRESS          Source;
  IP6_DAD_ENTRY             *DupAddrDetect;
  EFI_STATUS                Status;
  IP6_NEIGHBOR_ENTRY        *NeighborCache;
  EFI_IPv6_ADDRESS          Destination;
  IP6_SERVICE               *IpSb;
  BOOLEAN                   Flag;

  IpSb = (IP6_SERVICE *) Context;
  NET_CHECK_SIGNATURE (IpSb, IP6_SERVICE_SIGNATURE);

  ZeroMem (&Source, sizeof (EFI_IPv6_ADDRESS));

  //
  // A host SHOULD transmit up to MAX_RTR_SOLICITATIONS (3) Router
  // Solicitation messages, each separated by at least
  // RTR_SOLICITATION_INTERVAL (4) seconds.
  //
  if ((IpSb->Ip6ConfigInstance.Policy == Ip6ConfigPolicyAutomatic) &&
      !IpSb->RouterAdvertiseReceived &&
      IpSb->SolicitTimer > 0
      ) {
    if ((IpSb->Ticks == 0) || (--IpSb->Ticks == 0)) {
      Status = Ip6SendRouterSolicit (IpSb, NULL, NULL, NULL, NULL);
      if (!EFI_ERROR (Status)) {
        IpSb->SolicitTimer--;
        IpSb->Ticks = (UINT32) IP6_GET_TICKS (IP6_RTR_SOLICITATION_INTERVAL);
      }
    }
  }

  NET_LIST_FOR_EACH (Entry, &IpSb->Interfaces) {
    IpIf = NET_LIST_USER_STRUCT (Entry, IP6_INTERFACE, Link);

    //
    // Process the delay list to join the solicited-node multicast address.
    //
    NET_LIST_FOR_EACH_SAFE (Entry2, Next, &IpIf->DelayJoinList) {
      DelayNode = NET_LIST_USER_STRUCT (Entry2, IP6_DELAY_JOIN_LIST, Link);
      if ((DelayNode->DelayTime == 0) || (--DelayNode->DelayTime == 0)) {
        //
        // The timer expires, init the duplicate address detection.
        //
        Ip6InitDADProcess (
          DelayNode->Interface,
          DelayNode->AddressInfo,
          DelayNode->DadCallback,
          DelayNode->Context
          );

        //
        // Remove the delay node
        //
        RemoveEntryList (&DelayNode->Link);
        FreePool (DelayNode);
      }
    }

    //
    // Process the duplicate address detection list.
    //
    NET_LIST_FOR_EACH_SAFE (Entry2, Next, &IpIf->DupAddrDetectList) {
      DupAddrDetect = NET_LIST_USER_STRUCT (Entry2, IP6_DAD_ENTRY, Link);

      if ((DupAddrDetect->RetransTick == 0) || (--DupAddrDetect->RetransTick == 0)) {
        //
        // The timer expires, check the remaining transmit counts.
        //
        if (DupAddrDetect->Transmit < DupAddrDetect->MaxTransmit) {
          //
          // Send the Neighbor Solicitation message with
          // Source - unspecified address, destination - solicited-node multicast address
          // Target - the address to be validated
          //
          Status = Ip6SendNeighborSolicit (
                     IpSb,
                     NULL,
                     &DupAddrDetect->Destination,
                     &DupAddrDetect->AddressInfo->Address,
                     NULL
                     );
          if (EFI_ERROR (Status)) {
            return;
          }

          DupAddrDetect->Transmit++;
          DupAddrDetect->RetransTick = IP6_GET_TICKS (IpSb->RetransTimer);
        } else {
          //
          // All required solicitation has been sent out, and the RetransTime after the last
          // Neighbor Solicit is elapsed, finish the DAD process.
          //
          Flag = FALSE;
          if ((DupAddrDetect->Receive == 0) ||
              (DupAddrDetect->Transmit == DupAddrDetect->Receive)) {
            Flag = TRUE;
          }

          Ip6OnDADFinished (Flag, IpIf, DupAddrDetect);
        }
      }
    }
  }

  //
  // Polling the state of Neighbor cache
  //
  NET_LIST_FOR_EACH_SAFE (Entry, Next, &IpSb->NeighborTable) {
    NeighborCache = NET_LIST_USER_STRUCT (Entry, IP6_NEIGHBOR_ENTRY, Link);

    switch (NeighborCache->State) {
    case EfiNeighborInComplete:
      if (NeighborCache->Ticks > 0) {
        --NeighborCache->Ticks;
      }

      //
      // Retransmit Neighbor Solicitation messages approximately every
      // RetransTimer milliseconds while awaiting a response.
      //
      if (NeighborCache->Ticks == 0) {
        if (NeighborCache->Transmit > 1) {
          //
          // Send out multicast neighbor solicitation for address resolution.
          // After last neighbor solicitation message has been sent out, wait
          // for RetransTimer and then remove entry if no response is received.
          //
          Ip6CreateSNMulticastAddr (&NeighborCache->Neighbor, &Destination);
          Status = Ip6SelectSourceAddress (IpSb, &NeighborCache->Neighbor, &Source);
          if (EFI_ERROR (Status)) {
            return;
          }

          Status = Ip6SendNeighborSolicit (
                     IpSb,
                     &Source,
                     &Destination,
                     &NeighborCache->Neighbor,
                     &IpSb->SnpMode.CurrentAddress
                     );
          if (EFI_ERROR (Status)) {
            return;
          }
        }

        //
        // Update the retransmit times.
        //
        if (NeighborCache->Transmit > 0) {
          --NeighborCache->Transmit;
          NeighborCache->Ticks = IP6_GET_TICKS (IpSb->RetransTimer);
        }
      }

      if (NeighborCache->Transmit == 0) {
        //
        // Timeout, send ICMP destination unreachable packet and then remove entry
        //
        Status = Ip6FreeNeighborEntry (
                   IpSb,
                   NeighborCache,
                   TRUE,
                   TRUE,
                   EFI_ICMP_ERROR,
                   NULL,
                   NULL
                   );
        if (EFI_ERROR (Status)) {
          return;
        }
      }

      break;

    case EfiNeighborReachable:
      //
      // This entry is inserted by EfiIp6Neighbors() as static entry
      // and will not timeout.
      //
      if (!NeighborCache->Dynamic && (NeighborCache->Ticks == IP6_INFINIT_LIFETIME)) {
        break;
      }

      if ((NeighborCache->Ticks == 0) || (--NeighborCache->Ticks == 0)) {
        if (NeighborCache->Dynamic) {
          //
          // This entry is inserted by EfiIp6Neighbors() as dynamic entry
          // and will be deleted after timeout.
          //
          Status = Ip6FreeNeighborEntry (
                     IpSb,
                     NeighborCache,
                     FALSE,
                     TRUE,
                     EFI_TIMEOUT,
                     NULL,
                     NULL
                     );
          if (EFI_ERROR (Status)) {
            return;
          }
        } else {
          NeighborCache->State = EfiNeighborStale;
          NeighborCache->Ticks = (UINT32) IP6_INFINIT_LIFETIME;
        }
      }

      break;

    case EfiNeighborDelay:
      if ((NeighborCache->Ticks == 0) || (--NeighborCache->Ticks == 0)) {

        NeighborCache->State    = EfiNeighborProbe;
        NeighborCache->Ticks    = IP6_GET_TICKS (IpSb->RetransTimer);
        NeighborCache->Transmit = IP6_MAX_UNICAST_SOLICIT + 1;
        //
        // Send out unicast neighbor solicitation for Neighbor Unreachability Detection
        //
        Status = Ip6SelectSourceAddress (IpSb, &NeighborCache->Neighbor, &Source);
        if (EFI_ERROR (Status)) {
          return;
        }

        Status = Ip6SendNeighborSolicit (
                   IpSb,
                   &Source,
                   &NeighborCache->Neighbor,
                   &NeighborCache->Neighbor,
                   &IpSb->SnpMode.CurrentAddress
                   );
        if (EFI_ERROR (Status)) {
          return;
        }

        NeighborCache->Transmit--;
      }

      break;

    case EfiNeighborProbe:
      if (NeighborCache->Ticks > 0) {
        --NeighborCache->Ticks;
      }

      //
      // Retransmit Neighbor Solicitation messages approximately every
      // RetransTimer milliseconds while awaiting a response.
      //
      if (NeighborCache->Ticks == 0) {
        if (NeighborCache->Transmit > 1) {
          //
          // Send out unicast neighbor solicitation for Neighbor Unreachability
          // Detection. After last neighbor solicitation message has been sent out,
          // wait for RetransTimer and then remove entry if no response is received.
          //
          Status = Ip6SelectSourceAddress (IpSb, &NeighborCache->Neighbor, &Source);
          if (EFI_ERROR (Status)) {
            return;
          }

          Status = Ip6SendNeighborSolicit (
                     IpSb,
                     &Source,
                     &NeighborCache->Neighbor,
                     &NeighborCache->Neighbor,
                     &IpSb->SnpMode.CurrentAddress
                     );
          if (EFI_ERROR (Status)) {
            return;
          }
        }

        //
        // Update the retransmit times.
        //
        if (NeighborCache->Transmit > 0) {
          --NeighborCache->Transmit;
          NeighborCache->Ticks = IP6_GET_TICKS (IpSb->RetransTimer);
        }
      }

      if (NeighborCache->Transmit == 0) {
        //
        // Delete the neighbor entry.
        //
        Status = Ip6FreeNeighborEntry (
                   IpSb,
                   NeighborCache,
                   FALSE,
                   TRUE,
                   EFI_TIMEOUT,
                   NULL,
                   NULL
                   );
        if (EFI_ERROR (Status)) {
          return;
        }
      }

      break;

    default:
      break;
    }
  }
}

/**
  The heartbeat timer of ND module in 1 second. This time routine handles following
  things: 1) maitain default router list; 2) maintain prefix options;
  3) maintain route caches.

  @param[in]  IpSb              The IP6 service binding instance.

**/
VOID
Ip6NdTimerTicking (
  IN IP6_SERVICE            *IpSb
  )
{
  LIST_ENTRY                *Entry;
  LIST_ENTRY                *Next;
  IP6_DEFAULT_ROUTER        *DefaultRouter;
  IP6_PREFIX_LIST_ENTRY     *PrefixOption;
  UINT8                     Index;
  IP6_ROUTE_CACHE_ENTRY     *RouteCache;

  //
  // Decrease the lifetime of default router, if expires remove it from default router list.
  //
  NET_LIST_FOR_EACH_SAFE (Entry, Next, &IpSb->DefaultRouterList) {
    DefaultRouter = NET_LIST_USER_STRUCT (Entry, IP6_DEFAULT_ROUTER, Link);
    if (DefaultRouter->Lifetime != IP6_INF_ROUTER_LIFETIME) {
      if ((DefaultRouter->Lifetime == 0) || (--DefaultRouter->Lifetime == 0)) {
        Ip6DestroyDefaultRouter (IpSb, DefaultRouter);
      }
    }
  }

  //
  // Decrease Valid lifetime and Preferred lifetime of Prefix options and corresponding addresses.
  //
  NET_LIST_FOR_EACH_SAFE (Entry, Next, &IpSb->AutonomousPrefix) {
    PrefixOption = NET_LIST_USER_STRUCT (Entry, IP6_PREFIX_LIST_ENTRY, Link);
    if (PrefixOption->ValidLifetime != (UINT32) IP6_INFINIT_LIFETIME) {
      if ((PrefixOption->ValidLifetime > 0) && (--PrefixOption->ValidLifetime > 0)) {
        if ((PrefixOption->PreferredLifetime != (UINT32) IP6_INFINIT_LIFETIME) &&
            (PrefixOption->PreferredLifetime > 0)
            ) {
          --PrefixOption->PreferredLifetime;
        }
      } else {
        Ip6DestroyPrefixListEntry (IpSb, PrefixOption, FALSE, TRUE);
      }
    }
  }

  NET_LIST_FOR_EACH_SAFE (Entry, Next, &IpSb->OnlinkPrefix) {
    PrefixOption = NET_LIST_USER_STRUCT (Entry, IP6_PREFIX_LIST_ENTRY, Link);
    if (PrefixOption->ValidLifetime != (UINT32) IP6_INFINIT_LIFETIME) {
      if ((PrefixOption->ValidLifetime == 0) || (--PrefixOption->ValidLifetime == 0)) {
        Ip6DestroyPrefixListEntry (IpSb, PrefixOption, TRUE, TRUE);
      }
    }
  }

  //
  // Each bucket of route cache can contain at most IP6_ROUTE_CACHE_MAX entries.
  // Remove the entries at the tail of the bucket. These entries
  // are likely to be used least.
  // Reclaim frequency is set to 1 second.
  //
  for (Index = 0; Index < IP6_ROUTE_CACHE_HASH_SIZE; Index++) {
    while (IpSb->RouteTable->Cache.CacheNum[Index] > IP6_ROUTE_CACHE_MAX) {
      Entry = NetListRemoveTail (&IpSb->RouteTable->Cache.CacheBucket[Index]);
      if (Entry == NULL) {
        break;
      }

      RouteCache = NET_LIST_USER_STRUCT (Entry, IP6_ROUTE_CACHE_ENTRY, Link);
      Ip6FreeRouteCacheEntry (RouteCache);
      ASSERT (IpSb->RouteTable->Cache.CacheNum[Index] > 0);
      IpSb->RouteTable->Cache.CacheNum[Index]--;
    }
  }
}

