/** @file
  Functions implementation related with DHCPv6 for UefiPxeBc Driver.

  (C) Copyright 2014 Hewlett-Packard Development Company, L.P.<BR>
  Copyright (c) 2009 - 2018, 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 "PxeBcImpl.h"

//
// Well-known multi-cast address defined in section-24.1 of rfc-3315
//
//   ALL_DHCP_Relay_Agents_and_Servers address: FF02::1:2
//
EFI_IPv6_ADDRESS   mAllDhcpRelayAndServersAddress = {{0xFF, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2}};

/**
  Parse out a DHCPv6 option by OptTag, and find the position in buffer.

  @param[in]  Buffer        The pointer to the option buffer.
  @param[in]  Length        Length of the option buffer.
  @param[in]  OptTag        The required option tag.

  @retval     NULL          Failed to parse the required option.
  @retval     Others        The postion of the required option in buffer.

**/
EFI_DHCP6_PACKET_OPTION *
PxeBcParseDhcp6Options (
  IN UINT8                       *Buffer,
  IN UINT32                      Length,
  IN UINT16                      OptTag
  )
{
  EFI_DHCP6_PACKET_OPTION        *Option;
  UINT32                         Offset;

  Option  = (EFI_DHCP6_PACKET_OPTION *) Buffer;
  Offset  = 0;

  //
  // OpLen and OpCode here are both stored in network order.
  //
  while (Offset < Length) {

    if (NTOHS (Option->OpCode) == OptTag) {

      return Option;
    }

    Offset += (NTOHS(Option->OpLen) + 4);
    Option  = (EFI_DHCP6_PACKET_OPTION *) (Buffer + Offset);
  }

  return NULL;
}


/**
  Build the options buffer for the DHCPv6 request packet.

  @param[in]  Private             The pointer to PxeBc private data.
  @param[out] OptList             The pointer to the option pointer array.
  @param[in]  Buffer              The pointer to the buffer to contain the option list.

  @return     Index               The count of the built-in options.

**/
UINT32
PxeBcBuildDhcp6Options (
  IN  PXEBC_PRIVATE_DATA           *Private,
  OUT EFI_DHCP6_PACKET_OPTION      **OptList,
  IN  UINT8                        *Buffer
  )
{
  PXEBC_DHCP6_OPTION_ENTRY         OptEnt;
  UINT32                           Index;
  UINT16                           Value;

  Index       = 0;
  OptList[0]  = (EFI_DHCP6_PACKET_OPTION *) Buffer;

  //
  // Append client option request option
  //
  OptList[Index]->OpCode     = HTONS (DHCP6_OPT_ORO);
  OptList[Index]->OpLen      = HTONS (8);
  OptEnt.Oro                 = (PXEBC_DHCP6_OPTION_ORO *) OptList[Index]->Data;
  OptEnt.Oro->OpCode[0]      = HTONS(DHCP6_OPT_BOOT_FILE_URL);
  OptEnt.Oro->OpCode[1]      = HTONS(DHCP6_OPT_BOOT_FILE_PARAM);
  OptEnt.Oro->OpCode[2]      = HTONS(DHCP6_OPT_DNS_SERVERS);
  OptEnt.Oro->OpCode[3]      = HTONS(DHCP6_OPT_VENDOR_CLASS);
  Index++;
  OptList[Index]             = GET_NEXT_DHCP6_OPTION (OptList[Index - 1]);

  //
  // Append client network device interface option
  //
  OptList[Index]->OpCode     = HTONS (DHCP6_OPT_UNDI);
  OptList[Index]->OpLen      = HTONS ((UINT16)3);
  OptEnt.Undi                = (PXEBC_DHCP6_OPTION_UNDI *) OptList[Index]->Data;

  if (Private->Nii != NULL) {
    OptEnt.Undi->Type        = Private->Nii->Type;
    OptEnt.Undi->MajorVer    = Private->Nii->MajorVer;
    OptEnt.Undi->MinorVer    = Private->Nii->MinorVer;
  } else {
    OptEnt.Undi->Type        = DEFAULT_UNDI_TYPE;
    OptEnt.Undi->MajorVer    = DEFAULT_UNDI_MAJOR;
    OptEnt.Undi->MinorVer    = DEFAULT_UNDI_MINOR;
  }

  Index++;
  OptList[Index]             = GET_NEXT_DHCP6_OPTION (OptList[Index - 1]);

  //
  // Append client system architecture option
  //
  OptList[Index]->OpCode     = HTONS (DHCP6_OPT_ARCH);
  OptList[Index]->OpLen      = HTONS ((UINT16) sizeof (PXEBC_DHCP6_OPTION_ARCH));
  OptEnt.Arch                = (PXEBC_DHCP6_OPTION_ARCH *) OptList[Index]->Data;
  Value                      = HTONS (EFI_PXE_CLIENT_SYSTEM_ARCHITECTURE);
  CopyMem (&OptEnt.Arch->Type, &Value, sizeof (UINT16));
  Index++;
  OptList[Index]             = GET_NEXT_DHCP6_OPTION (OptList[Index - 1]);

  //
  // Append vendor class option to store the PXE class identifier.
  //
  OptList[Index]->OpCode       = HTONS (DHCP6_OPT_VENDOR_CLASS);
  OptList[Index]->OpLen        = HTONS ((UINT16) sizeof (PXEBC_DHCP6_OPTION_VENDOR_CLASS));
  OptEnt.VendorClass           = (PXEBC_DHCP6_OPTION_VENDOR_CLASS *) OptList[Index]->Data;
  OptEnt.VendorClass->Vendor   = HTONL (PXEBC_DHCP6_ENTERPRISE_NUM);
  OptEnt.VendorClass->ClassLen = HTONS ((UINT16) sizeof (PXEBC_CLASS_ID));
  CopyMem (
    &OptEnt.VendorClass->ClassId,
    DEFAULT_CLASS_ID_DATA,
    sizeof (PXEBC_CLASS_ID)
    );
  PxeBcUintnToAscDecWithFormat (
    EFI_PXE_CLIENT_SYSTEM_ARCHITECTURE,
    OptEnt.VendorClass->ClassId.ArchitectureType,
    sizeof (OptEnt.VendorClass->ClassId.ArchitectureType)
    );

  if (Private->Nii != NULL) {
    CopyMem (
      OptEnt.VendorClass->ClassId.InterfaceName,
      Private->Nii->StringId,
      sizeof (OptEnt.VendorClass->ClassId.InterfaceName)
      );
    PxeBcUintnToAscDecWithFormat (
      Private->Nii->MajorVer,
      OptEnt.VendorClass->ClassId.UndiMajor,
      sizeof (OptEnt.VendorClass->ClassId.UndiMajor)
      );
    PxeBcUintnToAscDecWithFormat (
      Private->Nii->MinorVer,
      OptEnt.VendorClass->ClassId.UndiMinor,
      sizeof (OptEnt.VendorClass->ClassId.UndiMinor)
      );
  }

  Index++;

  return Index;
}


/**
  Cache the DHCPv6 packet.

  @param[in]  Dst          The pointer to the cache buffer for DHCPv6 packet.
  @param[in]  Src          The pointer to the DHCPv6 packet to be cached.

  @retval     EFI_SUCCESS                Packet is copied.
  @retval     EFI_BUFFER_TOO_SMALL       Cache buffer is not big enough to hold the packet.

**/
EFI_STATUS
PxeBcCacheDhcp6Packet (
  IN EFI_DHCP6_PACKET          *Dst,
  IN EFI_DHCP6_PACKET          *Src
  )
{
  if (Dst->Size < Src->Length) {
    return EFI_BUFFER_TOO_SMALL;
  }

  CopyMem (&Dst->Dhcp6, &Src->Dhcp6, Src->Length);
  Dst->Length = Src->Length;

  return EFI_SUCCESS;
}


/**
  Free all the nodes in the list for boot file.

  @param[in]  Head            The pointer to the head of list.

**/
VOID
PxeBcFreeBootFileOption (
  IN LIST_ENTRY               *Head
  )
{
  LIST_ENTRY                  *Entry;
  LIST_ENTRY                  *NextEntry;
  PXEBC_DHCP6_OPTION_NODE     *Node;

  NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, Head) {
    Node = NET_LIST_USER_STRUCT (Entry, PXEBC_DHCP6_OPTION_NODE, Link);
    RemoveEntryList (Entry);
    FreePool (Node);
  }
}

/**
  Retrieve the boot server address using the EFI_DNS6_PROTOCOL.

  @param[in]  Private             Pointer to PxeBc private data.
  @param[in]  HostName            Pointer to buffer containing hostname.
  @param[out] IpAddress           On output, pointer to buffer containing IPv6 address.

  @retval EFI_SUCCESS             Operation succeeded.
  @retval EFI_OUT_OF_RESOURCES    Failed to allocate needed resources.
  @retval EFI_DEVICE_ERROR        An unexpected network error occurred.
  @retval Others                  Other errors as indicated.
  
**/
EFI_STATUS
PxeBcDns6 (
  IN PXEBC_PRIVATE_DATA           *Private,
  IN     CHAR16                   *HostName,
     OUT EFI_IPv6_ADDRESS         *IpAddress                
  )
{
  EFI_STATUS                      Status;
  EFI_DNS6_PROTOCOL               *Dns6;
  EFI_DNS6_CONFIG_DATA            Dns6ConfigData;
  EFI_DNS6_COMPLETION_TOKEN       Token;
  EFI_HANDLE                      Dns6Handle;
  EFI_IPv6_ADDRESS                *DnsServerList;
  BOOLEAN                         IsDone;
  
  Dns6                = NULL;
  Dns6Handle          = NULL;
  DnsServerList       = Private->DnsServer;
  ZeroMem (&Token, sizeof (EFI_DNS6_COMPLETION_TOKEN));

  //
  // Create a DNSv6 child instance and get the protocol.
  //
  Status = NetLibCreateServiceChild (
             Private->Controller,
             Private->Image,
             &gEfiDns6ServiceBindingProtocolGuid,
             &Dns6Handle
             );
  if (EFI_ERROR (Status)) {
    goto Exit;
  } 
  
  Status = gBS->OpenProtocol (
                  Dns6Handle,
                  &gEfiDns6ProtocolGuid,
                  (VOID **) &Dns6,
                  Private->Image,
                  Private->Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    goto Exit;
  }

  //
  // Configure DNS6 instance for the DNS server address and protocol.
  //
  ZeroMem (&Dns6ConfigData, sizeof (EFI_DNS6_CONFIG_DATA));
  Dns6ConfigData.DnsServerCount = 1;
  Dns6ConfigData.DnsServerList  = DnsServerList;
  Dns6ConfigData.EnableDnsCache = TRUE;
  Dns6ConfigData.Protocol       = EFI_IP_PROTO_UDP;
  IP6_COPY_ADDRESS (&Dns6ConfigData.StationIp, &Private->TmpStationIp.v6);
  Status = Dns6->Configure (
                   Dns6,
                   &Dns6ConfigData
                   );
  if (EFI_ERROR (Status)) {
    goto Exit;
  }

  Token.Status = EFI_NOT_READY;
  IsDone       = FALSE;
  //
  // Create event to set the  IsDone flag when name resolution is finished.
  //
  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  PxeBcCommonNotify,
                  &IsDone,
                  &Token.Event
                  );
  if (EFI_ERROR (Status)) {
    goto Exit;
  }

  //
  // Start asynchronous name resolution.
  //
  Status = Dns6->HostNameToIp (Dns6, HostName, &Token);
  if (EFI_ERROR (Status)) {
    goto Exit;
  }

  while (!IsDone) {
    Dns6->Poll (Dns6);
  }

  //
  // Name resolution is done, check result.
  //
  Status = Token.Status;  
  if (!EFI_ERROR (Status)) {
    if (Token.RspData.H2AData == NULL) {
      Status = EFI_DEVICE_ERROR;
      goto Exit;
    }
    if (Token.RspData.H2AData->IpCount == 0 || Token.RspData.H2AData->IpList == NULL) {
      Status = EFI_DEVICE_ERROR;
      goto Exit;
    }
    //
    // We just return the first IPv6 address from DNS protocol.
    //
    IP6_COPY_ADDRESS (IpAddress, Token.RspData.H2AData->IpList);
    Status = EFI_SUCCESS;
  }
  
Exit:
  FreePool (HostName);

  if (Token.Event != NULL) {
    gBS->CloseEvent (Token.Event);
  }
  if (Token.RspData.H2AData != NULL) {
    if (Token.RspData.H2AData->IpList != NULL) {
      FreePool (Token.RspData.H2AData->IpList);
    }
    FreePool (Token.RspData.H2AData);
  }

  if (Dns6 != NULL) {
    Dns6->Configure (Dns6, NULL);
    
    gBS->CloseProtocol (
           Dns6Handle,
           &gEfiDns6ProtocolGuid,
           Private->Image,
           Private->Controller
           );
  }

  if (Dns6Handle != NULL) {
    NetLibDestroyServiceChild (
      Private->Controller,
      Private->Image,
      &gEfiDns6ServiceBindingProtocolGuid,
      Dns6Handle
      );
  }

  if (DnsServerList != NULL) {
    FreePool (DnsServerList);
  }
  
  return Status;  
}

/**
  Parse the Boot File URL option.

  @param[in]      Private      Pointer to PxeBc private data.
  @param[out]     FileName     The pointer to the boot file name.
  @param[in, out] SrvAddr      The pointer to the boot server address.
  @param[in]      BootFile     The pointer to the boot file URL option data.
  @param[in]      Length       The length of the boot file URL option data.

  @retval EFI_ABORTED     User cancel operation.
  @retval EFI_SUCCESS     Selected the boot menu successfully.
  @retval EFI_NOT_READY   Read the input key from the keybroad has not finish.

**/
EFI_STATUS
PxeBcExtractBootFileUrl (
  IN PXEBC_PRIVATE_DATA      *Private,
     OUT UINT8               **FileName,
  IN OUT EFI_IPv6_ADDRESS    *SrvAddr,
  IN     CHAR8               *BootFile,
  IN     UINT16              Length
  )
{
  UINT16                     PrefixLen;
  CHAR8                      *BootFileNamePtr;
  CHAR8                      *BootFileName;
  UINT16                     BootFileNameLen;
  CHAR8                      *TmpStr;
  CHAR8                      TmpChar;
  CHAR8                      *ServerAddressOption;
  CHAR8                      *ServerAddress;
  CHAR8                      *ModeStr;
  CHAR16                     *HostName;
  BOOLEAN                    IpExpressedUrl;
  UINTN                      Len;
  EFI_STATUS                 Status;

  IpExpressedUrl = TRUE;
  //
  // The format of the Boot File URL option is:
  //
  //  0                   1                   2                   3
  //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  // |       OPT_BOOTFILE_URL        |            option-len         |
  // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  // |                                                               |
  // .                  bootfile-url  (variable length)              .
  // |                                                               |
  // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //

  //
  // Based upon RFC 5970 and UEFI 2.6, bootfile-url format can be
  // tftp://[SERVER_ADDRESS]/BOOTFILE_NAME or tftp://domain_name/BOOTFILE_NAME
  // As an example where the BOOTFILE_NAME is the EFI loader and
  // SERVER_ADDRESS is the ASCII encoding of an IPV6 address.
  //
  PrefixLen = (UINT16) AsciiStrLen (PXEBC_DHCP6_BOOT_FILE_URL_PREFIX);

  if (Length <= PrefixLen ||
      CompareMem (BootFile, PXEBC_DHCP6_BOOT_FILE_URL_PREFIX, PrefixLen) != 0) {
    return EFI_NOT_FOUND;
  }

  BootFile = BootFile + PrefixLen;
  Length   = (UINT16) (Length - PrefixLen);

  TmpStr = (CHAR8 *) AllocateZeroPool (Length + 1);
  if (TmpStr == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  CopyMem (TmpStr, BootFile, Length);
  TmpStr[Length] = '\0';

  //
  // Get the part of SERVER_ADDRESS string.
  //
  ServerAddressOption = TmpStr;
  if (*ServerAddressOption == PXEBC_ADDR_START_DELIMITER) {
    ServerAddressOption ++;
    ServerAddress = ServerAddressOption;
    while (*ServerAddress != '\0' && *ServerAddress != PXEBC_ADDR_END_DELIMITER) {
      ServerAddress++;
    }
    
    if (*ServerAddress != PXEBC_ADDR_END_DELIMITER) {
      FreePool (TmpStr);
      return EFI_INVALID_PARAMETER;
    }
    
    *ServerAddress = '\0';
    
    //
    // Convert the string of server address to Ipv6 address format and store it.
    //
    Status = NetLibAsciiStrToIp6 (ServerAddressOption, SrvAddr);
    if (EFI_ERROR (Status)) {
      FreePool (TmpStr);
      return Status;
    }

  } else {
    IpExpressedUrl = FALSE;
    ServerAddress = ServerAddressOption;
    while (*ServerAddress != '\0' && *ServerAddress != PXEBC_TFTP_URL_SEPARATOR) {
      ServerAddress++;
    }

    if (*ServerAddress != PXEBC_TFTP_URL_SEPARATOR) {
      FreePool (TmpStr);
      return EFI_INVALID_PARAMETER;
    }
    *ServerAddress = '\0';

    Len = AsciiStrSize (ServerAddressOption);
    HostName = AllocateZeroPool (Len * sizeof (CHAR16));
    if (HostName == NULL) {
      FreePool (TmpStr);
      return EFI_OUT_OF_RESOURCES;
    }
    AsciiStrToUnicodeStrS (
      ServerAddressOption,
      HostName,
      Len
      );

    //
    // Perform DNS resolution.
    //
    Status = PxeBcDns6 (Private,HostName, SrvAddr);
    if (EFI_ERROR (Status)) {
      FreePool (TmpStr);
      return Status;
    }
  }

  //
  // Get the part of BOOTFILE_NAME string.
  //
  BootFileNamePtr = (CHAR8*)((UINTN)ServerAddress + 1);
  if (IpExpressedUrl) {
    if (*BootFileNamePtr != PXEBC_TFTP_URL_SEPARATOR) {
      FreePool (TmpStr);
      return EFI_INVALID_PARAMETER;
    }
    ++BootFileNamePtr;
  }

  BootFileNameLen = (UINT16)(Length - (UINT16) ((UINTN)BootFileNamePtr - (UINTN)TmpStr) + 1);
  if (BootFileNameLen != 0 || FileName != NULL) {
    //
    // Remove trailing mode=octet if present and ignore.  All other modes are
    // invalid for netboot6, so reject them.
    //
    ModeStr = AsciiStrStr (BootFileNamePtr, ";mode=octet");
    if (ModeStr != NULL && *(ModeStr + AsciiStrLen (";mode=octet")) == '\0') {
      *ModeStr = '\0';
    } else if (AsciiStrStr (BootFileNamePtr, ";mode=") != NULL) {
      FreePool (TmpStr);
      return EFI_INVALID_PARAMETER;
    }

    //
    // Extract boot file name from URL.
    //
    BootFileName = (CHAR8 *) AllocateZeroPool (BootFileNameLen);
    if (BootFileName == NULL) {
      FreePool (TmpStr);
      return EFI_OUT_OF_RESOURCES;
    }
    *FileName = (UINT8*) BootFileName;

    //
    // Decode percent-encoding in boot file name.
    //
    while (*BootFileNamePtr != '\0') {
      if (*BootFileNamePtr == '%') {
        TmpChar = *(BootFileNamePtr+ 3);
        *(BootFileNamePtr+ 3) = '\0';
        *BootFileName = (UINT8) AsciiStrHexToUintn ((CHAR8*)(BootFileNamePtr + 1));
        BootFileName++;
        *(BootFileNamePtr+ 3) = TmpChar;
        BootFileNamePtr += 3;
      } else {
        *BootFileName = *BootFileNamePtr;
        BootFileName++;
        BootFileNamePtr++;
      }
    }
    *BootFileName = '\0';
  }

  FreePool (TmpStr);

  return EFI_SUCCESS;
}


/**
  Parse the Boot File Parameter option.

  @param[in]  BootFilePara      The pointer to boot file parameter option data.
  @param[out] BootFileSize      The pointer to the parsed boot file size.

  @retval EFI_SUCCESS     Successfully obtained the boot file size from parameter option.
  @retval EFI_NOT_FOUND   Failed to extract the boot file size from parameter option.

**/
EFI_STATUS
PxeBcExtractBootFileParam (
  IN  CHAR8                  *BootFilePara,
  OUT UINT16                 *BootFileSize
  )
{
  UINT16                     Length;
  UINT8                      Index;
  UINT8                      Digit;
  UINT32                     Size;

  CopyMem (&Length, BootFilePara, sizeof (UINT16));
  Length = NTOHS (Length);

  //
  // The BootFile Size should be 1~5 byte ASCII strings
  //
  if (Length < 1 || Length > 5) {
    return EFI_NOT_FOUND;
  }

  //
  // Extract the value of BootFile Size.
  //
  BootFilePara = BootFilePara + sizeof (UINT16);
  Size         = 0;
  for (Index = 0; Index < Length; Index++) {
    if (EFI_ERROR (PxeBcUniHexToUint8 (&Digit, *(BootFilePara + Index)))) {
      return EFI_NOT_FOUND;
    }

    Size = (Size + Digit) * 10;
  }

  Size = Size / 10;
  if (Size > PXEBC_DHCP6_MAX_BOOT_FILE_SIZE) {
    return EFI_NOT_FOUND;
  }

  *BootFileSize = (UINT16) Size;
  return EFI_SUCCESS;
}


/**
  Parse the cached DHCPv6 packet, including all the options.

  @param[in]  Cache6           The pointer to a cached DHCPv6 packet.

  @retval     EFI_SUCCESS      Parsed the DHCPv6 packet successfully.
  @retval     EFI_DEVICE_ERROR Failed to parse and invalid the packet.

**/
EFI_STATUS
PxeBcParseDhcp6Packet (
  IN PXEBC_DHCP6_PACKET_CACHE  *Cache6
  )
{
  EFI_DHCP6_PACKET             *Offer;
  EFI_DHCP6_PACKET_OPTION      **Options;
  EFI_DHCP6_PACKET_OPTION      *Option;
  PXEBC_OFFER_TYPE             OfferType;
  BOOLEAN                      IsProxyOffer;
  BOOLEAN                      IsPxeOffer;
  UINT32                       Offset;
  UINT32                       Length;
  UINT32                       EnterpriseNum;

  IsProxyOffer = TRUE;
  IsPxeOffer   = FALSE;
  Offer        = &Cache6->Packet.Offer;
  Options      = Cache6->OptList;

  ZeroMem (Cache6->OptList, sizeof (Cache6->OptList));

  Option  = (EFI_DHCP6_PACKET_OPTION *) (Offer->Dhcp6.Option);
  Offset  = 0;
  Length  = GET_DHCP6_OPTION_SIZE (Offer);

  //
  // OpLen and OpCode here are both stored in network order, since they are from original packet.
  //
  while (Offset < Length) {

    if (NTOHS (Option->OpCode) == DHCP6_OPT_IA_NA) {
      Options[PXEBC_DHCP6_IDX_IA_NA] = Option;
    } else if (NTOHS (Option->OpCode) == DHCP6_OPT_BOOT_FILE_URL) {
      //
      // The server sends this option to inform the client about an URL to a boot file.
      //
      Options[PXEBC_DHCP6_IDX_BOOT_FILE_URL] = Option;
    } else if (NTOHS (Option->OpCode) == DHCP6_OPT_BOOT_FILE_PARAM) {
      Options[PXEBC_DHCP6_IDX_BOOT_FILE_PARAM] = Option;
    } else if (NTOHS (Option->OpCode) == DHCP6_OPT_VENDOR_CLASS) {
      Options[PXEBC_DHCP6_IDX_VENDOR_CLASS] = Option;
    } else if (NTOHS (Option->OpCode) == DHCP6_OPT_DNS_SERVERS) {
      Options[PXEBC_DHCP6_IDX_DNS_SERVER] = Option;
    }

    Offset += (NTOHS (Option->OpLen) + 4);
    Option  = (EFI_DHCP6_PACKET_OPTION *) (Offer->Dhcp6.Option + Offset);
  }

  //
  // The offer with assigned client address is NOT a proxy offer.
  // An ia_na option, embeded with valid ia_addr option and a status_code of success.
  //
  Option = Options[PXEBC_DHCP6_IDX_IA_NA];
  if (Option != NULL) {
    Option = PxeBcParseDhcp6Options (
               Option->Data + 12,
               NTOHS (Option->OpLen),
               DHCP6_OPT_STATUS_CODE
               );
    if ((Option != NULL && Option->Data[0] == 0) || (Option == NULL)) {
      IsProxyOffer = FALSE;
    }
  }

  //
  // The offer with "PXEClient" is a pxe offer.
  //
  Option        = Options[PXEBC_DHCP6_IDX_VENDOR_CLASS];
  EnterpriseNum = HTONL(PXEBC_DHCP6_ENTERPRISE_NUM);

  if (Option != NULL &&
      NTOHS(Option->OpLen) >= 13 &&
      CompareMem (Option->Data, &EnterpriseNum, sizeof (UINT32)) == 0 &&
      CompareMem (&Option->Data[6], DEFAULT_CLASS_ID_DATA, 9) == 0) {
    IsPxeOffer = TRUE;
  }

  //
  // Determine offer type of the dhcp6 packet.
  //
  if (IsPxeOffer) {
    //
    // It's a binl offer only with PXEClient.
    //
    OfferType = IsProxyOffer ? PxeOfferTypeProxyBinl : PxeOfferTypeDhcpBinl;
  } else {
    //
    // It's a dhcp only offer, which is a pure dhcp6 offer packet.
    //
    OfferType = PxeOfferTypeDhcpOnly;
  }

  Cache6->OfferType = OfferType;

  return EFI_SUCCESS;
}


/**
  Cache the DHCPv6 ack packet, and parse it on demand.

  @param[in]  Private             The pointer to PxeBc private data.
  @param[in]  Ack                 The pointer to the DHCPv6 ack packet.
  @param[in]  Verified            If TRUE, parse the ACK packet and store info into mode data.

  @retval     EFI_SUCCESS                Cache and parse the packet successfully.
  @retval     EFI_BUFFER_TOO_SMALL       Cache buffer is not big enough to hold the packet.

**/
EFI_STATUS
PxeBcCopyDhcp6Ack (
  IN PXEBC_PRIVATE_DATA   *Private,
  IN EFI_DHCP6_PACKET     *Ack,
  IN BOOLEAN              Verified
  )
{
  EFI_PXE_BASE_CODE_MODE  *Mode;
  EFI_STATUS              Status;

  Mode = Private->PxeBc.Mode;

  Status = PxeBcCacheDhcp6Packet (&Private->DhcpAck.Dhcp6.Packet.Ack, Ack);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (Verified) {
    //
    // Parse the ack packet and store it into mode data if needed.
    //
    PxeBcParseDhcp6Packet (&Private->DhcpAck.Dhcp6);
    CopyMem (&Mode->DhcpAck.Dhcpv6, &Ack->Dhcp6, Ack->Length);
    Mode->DhcpAckReceived = TRUE;
  }

  return EFI_SUCCESS;
}


/**
  Cache the DHCPv6 proxy offer packet according to the received order.

  @param[in]  Private               The pointer to PxeBc private data.
  @param[in]  OfferIndex            The received order of offer packets.

  @retval     EFI_SUCCESS                Cache and parse the packet successfully.
  @retval     EFI_BUFFER_TOO_SMALL       Cache buffer is not big enough to hold the packet.

**/
EFI_STATUS
PxeBcCopyDhcp6Proxy (
  IN PXEBC_PRIVATE_DATA     *Private,
  IN UINT32                 OfferIndex
  )
{
  EFI_PXE_BASE_CODE_MODE    *Mode;
  EFI_DHCP6_PACKET          *Offer;
  EFI_STATUS              Status;

  ASSERT (OfferIndex < Private->OfferNum);
  ASSERT (OfferIndex < PXEBC_OFFER_MAX_NUM);

  Mode  = Private->PxeBc.Mode;
  Offer = &Private->OfferBuffer[OfferIndex].Dhcp6.Packet.Offer;

  //
  // Cache the proxy offer packet and parse it.
  //
  Status = PxeBcCacheDhcp6Packet (&Private->ProxyOffer.Dhcp6.Packet.Offer, Offer);
  if (EFI_ERROR(Status)) {
    return Status;
  }
  PxeBcParseDhcp6Packet (&Private->ProxyOffer.Dhcp6);

  //
  // Store this packet into mode data.
  //
  CopyMem (&Mode->ProxyOffer.Dhcpv6, &Offer->Dhcp6, Offer->Length);
  Mode->ProxyOfferReceived = TRUE;

  return EFI_SUCCESS;
}

/**
  Seek the address of the first byte of the option header.

  @param[in]  Buf           The pointer to the buffer.
  @param[in]  SeekLen       The length to seek.
  @param[in]  OptType       The option type.

  @retval     NULL          If it failed to seek the option.
  @retval     others        The position to the option.

**/
UINT8 *
PxeBcDhcp6SeekOption (
  IN UINT8           *Buf,
  IN UINT32          SeekLen,
  IN UINT16          OptType
  )
{
  UINT8              *Cursor;
  UINT8              *Option;
  UINT16             DataLen;
  UINT16             OpCode;

  Option = NULL;
  Cursor = Buf;

  while (Cursor < Buf + SeekLen) {
    OpCode = ReadUnaligned16 ((UINT16 *) Cursor);
    if (OpCode == HTONS (OptType)) {
      Option = Cursor;
      break;
    }
    DataLen = NTOHS (ReadUnaligned16 ((UINT16 *) (Cursor + 2)));
    Cursor += (DataLen + 4);
  }

  return Option;
}


/**
  Build and send out the request packet for the bootfile, and parse the reply.

  @param[in]  Private               The pointer to PxeBc private data.
  @param[in]  Index                 PxeBc option boot item type.

  @retval     EFI_SUCCESS           Successfully discovered the boot file.
  @retval     EFI_OUT_OF_RESOURCES  Failed to allocate resources.
  @retval     EFI_NOT_FOUND         Can't get the PXE reply packet.
  @retval     Others                Failed to discover the boot file.

**/
EFI_STATUS
PxeBcRequestBootService (
  IN  PXEBC_PRIVATE_DATA              *Private,
  IN  UINT32                          Index
  )
{
  EFI_PXE_BASE_CODE_UDP_PORT          SrcPort;
  EFI_PXE_BASE_CODE_UDP_PORT          DestPort;
  EFI_PXE_BASE_CODE_PROTOCOL          *PxeBc;
  EFI_PXE_BASE_CODE_DHCPV6_PACKET     *Discover;
  UINTN                               DiscoverLen;
  EFI_DHCP6_PACKET                    *Request;
  UINTN                               RequestLen;
  EFI_DHCP6_PACKET                    *Reply;
  UINT8                               *RequestOpt;
  UINT8                               *DiscoverOpt;
  UINTN                               ReadSize;
  UINT16                              OpFlags;
  UINT16                              OpCode;
  UINT16                              OpLen;
  EFI_STATUS                          Status;
  EFI_DHCP6_PACKET                    *IndexOffer;
  UINT8                               *Option;

  PxeBc       = &Private->PxeBc;
  Request     = Private->Dhcp6Request;
  IndexOffer  = &Private->OfferBuffer[Index].Dhcp6.Packet.Offer;
  SrcPort     = PXEBC_BS_DISCOVER_PORT;
  DestPort    = PXEBC_BS_DISCOVER_PORT;
  OpFlags     = 0;

  if (Request == NULL) {
    return EFI_DEVICE_ERROR;
  }

  Discover = AllocateZeroPool (sizeof (EFI_PXE_BASE_CODE_DHCPV6_PACKET));
  if (Discover == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Build the request packet by the cached request packet before.
  //
  Discover->TransactionId = IndexOffer->Dhcp6.Header.TransactionId;
  Discover->MessageType   = Request->Dhcp6.Header.MessageType;
  RequestOpt              = Request->Dhcp6.Option;
  DiscoverOpt             = Discover->DhcpOptions;
  DiscoverLen             = sizeof (EFI_DHCP6_HEADER);
  RequestLen              = DiscoverLen;

  //
  // Find Server ID Option from ProxyOffer.
  //
  if (Private->OfferBuffer[Index].Dhcp6.OfferType == PxeOfferTypeProxyBinl) {  
    Option = PxeBcDhcp6SeekOption (
               IndexOffer->Dhcp6.Option,
               IndexOffer->Length - 4,
               DHCP6_OPT_SERVER_ID
               );
    if (Option == NULL) {
      return EFI_NOT_FOUND;
    }
  
    //
    // Add Server ID Option.
    //
    OpLen = NTOHS (((EFI_DHCP6_PACKET_OPTION *) Option)->OpLen);
    CopyMem (DiscoverOpt, Option, OpLen + 4);
    DiscoverOpt += (OpLen + 4);
    DiscoverLen += (OpLen + 4);
  }

  while (RequestLen < Request->Length) {
    OpCode = NTOHS (((EFI_DHCP6_PACKET_OPTION *) RequestOpt)->OpCode);
    OpLen  = NTOHS (((EFI_DHCP6_PACKET_OPTION *) RequestOpt)->OpLen);
    if (OpCode != EFI_DHCP6_IA_TYPE_NA &&
        OpCode != EFI_DHCP6_IA_TYPE_TA &&
        OpCode != DHCP6_OPT_SERVER_ID
        ) {
      //
      // Copy all the options except IA option and Server ID
      //
      CopyMem (DiscoverOpt, RequestOpt, OpLen + 4);
      DiscoverOpt += (OpLen + 4);
      DiscoverLen += (OpLen + 4);
    }
    RequestOpt += (OpLen + 4);
    RequestLen += (OpLen + 4);
  }

  //
  // Update Elapsed option in the package 
  //
  Option = PxeBcDhcp6SeekOption (
             Discover->DhcpOptions,
             (UINT32)(RequestLen - 4),
             DHCP6_OPT_ELAPSED_TIME
             );
  if (Option != NULL) {
    CalcElapsedTime (Private);
    WriteUnaligned16 ((UINT16*)(Option + 4), HTONS((UINT16) Private->ElapsedTime));
  }  

  Status = PxeBc->UdpWrite (
                    PxeBc,
                    OpFlags,
                    &Private->ServerIp,
                    &DestPort,
                    NULL,
                    &Private->StationIp,
                    &SrcPort,
                    NULL,
                    NULL,
                    &DiscoverLen,
                    (VOID *) Discover
                    );

  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Cache the right PXE reply packet here, set valid flag later.
  // Especially for PXE discover packet, store it into mode data here.
  //
  Reply = &Private->ProxyOffer.Dhcp6.Packet.Offer;
  ReadSize = (UINTN) Reply->Size;

  //
  // Start Udp6Read instance
  //
  Status = Private->Udp6Read->Configure (Private->Udp6Read, &Private->Udp6CfgData);
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }
    
  Status = PxeBc->UdpRead (
                    PxeBc,
                    EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_IP | EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_DEST_IP,
                    NULL,
                    &SrcPort,
                    &Private->ServerIp,
                    &DestPort,
                    NULL,
                    NULL,
                    &ReadSize,
                    (VOID *) &Reply->Dhcp6
                    );
  //
  // Stop Udp6Read instance
  //
  Private->Udp6Read->Configure (Private->Udp6Read, NULL);

  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Update length
  //
  Reply->Length = (UINT32) ReadSize;

  return EFI_SUCCESS;
  
ON_ERROR:
  if (Discover != NULL) {
    FreePool (Discover);
  }

  return Status;
}


/**
  Retry to request bootfile name by the BINL offer.

  @param[in]  Private              The pointer to PxeBc private data.
  @param[in]  Index                The received order of offer packets.

  @retval     EFI_SUCCESS          Successfully retried a request for the bootfile name.
  @retval     EFI_DEVICE_ERROR     Failed to retry the bootfile name.

**/
EFI_STATUS
PxeBcRetryDhcp6Binl (
  IN PXEBC_PRIVATE_DATA  *Private,
  IN UINT32              Index
  )
{
  EFI_PXE_BASE_CODE_MODE    *Mode;
  PXEBC_DHCP6_PACKET_CACHE  *Offer;
  PXEBC_DHCP6_PACKET_CACHE  *Cache6;
  EFI_STATUS                Status;

  ASSERT (Index < PXEBC_OFFER_MAX_NUM);
  ASSERT (Private->OfferBuffer[Index].Dhcp6.OfferType == PxeOfferTypeDhcpBinl ||
          Private->OfferBuffer[Index].Dhcp6.OfferType == PxeOfferTypeProxyBinl);

  Mode                  = Private->PxeBc.Mode;
  Private->IsDoDiscover = FALSE;
  Offer                 = &Private->OfferBuffer[Index].Dhcp6;
  if (Offer->OptList[PXEBC_DHCP6_IDX_BOOT_FILE_URL] == NULL) {
    //
    // There is no BootFileUrl option in dhcp6 offer, so use servers multi-cast address instead.
    //
    CopyMem (
      &Private->ServerIp.v6,
      &mAllDhcpRelayAndServersAddress,
      sizeof (EFI_IPv6_ADDRESS)
      );
  } else {
    ASSERT (Offer->OptList[PXEBC_DHCP6_IDX_BOOT_FILE_URL] != NULL);
    //
    // Parse out the next server address from the last offer, and store it
    //
    Status = PxeBcExtractBootFileUrl (
               Private,
               &Private->BootFileName,
               &Private->ServerIp.v6,
               (CHAR8 *) (Offer->OptList[PXEBC_DHCP6_IDX_BOOT_FILE_URL]->Data),
               NTOHS (Offer->OptList[PXEBC_DHCP6_IDX_BOOT_FILE_URL]->OpLen)
               );
    if (EFI_ERROR (Status)) {
      return Status;
    }
  }

  //
  // Retry Dhcp6Binl again for the bootfile, and the reply cached into Private->ProxyOffer.
  //
  Status = PxeBcRequestBootService (Private, Index);

  if (EFI_ERROR (Status)) {
    return Status;
  }

  Cache6 = &Private->ProxyOffer.Dhcp6;
  Status = PxeBcParseDhcp6Packet (Cache6);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (Cache6->OfferType != PxeOfferTypeProxyPxe10 &&
      Cache6->OfferType != PxeOfferTypeProxyWfm11a &&
      Cache6->OptList[PXEBC_DHCP6_IDX_BOOT_FILE_URL] == NULL) {
    //
    // This BINL ack doesn't have discovery option set or multicast option set
    // or bootfile name specified.
    //
    return EFI_DEVICE_ERROR;
  }

  Mode->ProxyOfferReceived = TRUE;
  CopyMem (
    &Mode->ProxyOffer.Dhcpv6,
    &Cache6->Packet.Offer.Dhcp6,
    Cache6->Packet.Offer.Length
    );

  return EFI_SUCCESS;
}


/**
  Cache all the received DHCPv6 offers, and set OfferIndex and OfferCount.

  @param[in]  Private               The pointer to PXEBC_PRIVATE_DATA.
  @param[in]  RcvdOffer             The pointer to the received offer packet.

  @retval     EFI_SUCCESS      Cache and parse the packet successfully.
  @retval     Others           Operation failed.
**/
EFI_STATUS
PxeBcCacheDhcp6Offer (
  IN PXEBC_PRIVATE_DATA     *Private,
  IN EFI_DHCP6_PACKET       *RcvdOffer
  )
{
  PXEBC_DHCP6_PACKET_CACHE  *Cache6;
  EFI_DHCP6_PACKET          *Offer;
  PXEBC_OFFER_TYPE          OfferType;
  EFI_STATUS                Status;

  Cache6 = &Private->OfferBuffer[Private->OfferNum].Dhcp6;
  Offer  = &Cache6->Packet.Offer;

  //
  // Cache the content of DHCPv6 packet firstly.
  //
  Status = PxeBcCacheDhcp6Packet (Offer, RcvdOffer);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Validate the DHCPv6 packet, and parse the options and offer type.
  //
  if (EFI_ERROR (PxeBcParseDhcp6Packet (Cache6))) {
    return EFI_ABORTED;
  }

  //
  // Determine whether cache the current offer by type, and record OfferIndex and OfferCount.
  //
  OfferType = Cache6->OfferType;
  ASSERT (OfferType < PxeOfferTypeMax);
  ASSERT (Private->OfferCount[OfferType] < PXEBC_OFFER_MAX_NUM);

  if (IS_PROXY_OFFER (OfferType)) {
    //
    // It's a proxy offer without yiaddr, including PXE10, WFM11a or BINL offer.
    //
    Private->IsProxyRecved = TRUE;

    if (OfferType == PxeOfferTypeProxyBinl) {
      //
      // Cache all proxy BINL offers.
      //
      Private->OfferIndex[OfferType][Private->OfferCount[OfferType]] = Private->OfferNum;
      Private->OfferCount[OfferType]++;
    } else if ((OfferType == PxeOfferTypeProxyPxe10 || OfferType == PxeOfferTypeProxyWfm11a) && 
                 Private->OfferCount[OfferType] < 1) {
      //
      // Only cache the first PXE10/WFM11a offer, and discard the others.
      //
      Private->OfferIndex[OfferType][0] = Private->OfferNum;
      Private->OfferCount[OfferType]    = 1;
    } else {
      return EFI_ABORTED;
    }
  } else {
    //
    // It's a DHCPv6 offer with yiaddr, and cache them all.
    //
    Private->OfferIndex[OfferType][Private->OfferCount[OfferType]] = Private->OfferNum;
    Private->OfferCount[OfferType]++;
  }

  Private->OfferNum++;

  return EFI_SUCCESS;
}


/**
  Select an DHCPv6 offer, and record SelectIndex and SelectProxyType.

  @param[in]  Private             The pointer to PXEBC_PRIVATE_DATA.

**/
VOID
PxeBcSelectDhcp6Offer (
  IN PXEBC_PRIVATE_DATA     *Private
  )
{
  UINT32                Index;
  UINT32                OfferIndex;
  PXEBC_OFFER_TYPE      OfferType;

  Private->SelectIndex = 0;

  if (Private->IsOfferSorted) {
    //
    // Select offer by default policy.
    //
    if (Private->OfferCount[PxeOfferTypeDhcpPxe10] > 0) {
      //
      // 1. DhcpPxe10 offer
      //
      Private->SelectIndex = Private->OfferIndex[PxeOfferTypeDhcpPxe10][0] + 1;

    } else if (Private->OfferCount[PxeOfferTypeDhcpWfm11a] > 0) {
      //
      // 2. DhcpWfm11a offer
      //
      Private->SelectIndex = Private->OfferIndex[PxeOfferTypeDhcpWfm11a][0] + 1;

    } else if (Private->OfferCount[PxeOfferTypeDhcpOnly] > 0 &&
               Private->OfferCount[PxeOfferTypeProxyPxe10] > 0) {
      //
      // 3. DhcpOnly offer and ProxyPxe10 offer.
      //
      Private->SelectIndex     = Private->OfferIndex[PxeOfferTypeDhcpOnly][0] + 1;
      Private->SelectProxyType = PxeOfferTypeProxyPxe10;

    } else if (Private->OfferCount[PxeOfferTypeDhcpOnly] > 0 &&
               Private->OfferCount[PxeOfferTypeProxyWfm11a] > 0) {
      //
      // 4. DhcpOnly offer and ProxyWfm11a offer.
      //
      Private->SelectIndex     = Private->OfferIndex[PxeOfferTypeDhcpOnly][0] + 1;
      Private->SelectProxyType = PxeOfferTypeProxyWfm11a;

    } else if (Private->OfferCount[PxeOfferTypeDhcpBinl] > 0) {
      //
      // 5. DhcpBinl offer.
      //
      Private->SelectIndex = Private->OfferIndex[PxeOfferTypeDhcpBinl][0] + 1;

    } else if (Private->OfferCount[PxeOfferTypeDhcpOnly] > 0 &&
               Private->OfferCount[PxeOfferTypeProxyBinl] > 0) {
      //
      // 6. DhcpOnly offer and ProxyBinl offer.
      //
      Private->SelectIndex     = Private->OfferIndex[PxeOfferTypeDhcpOnly][0] + 1;
      Private->SelectProxyType = PxeOfferTypeProxyBinl;

    } else {
      //
      // 7. DhcpOnly offer with bootfilename.
      //
      for (Index = 0; Index < Private->OfferCount[PxeOfferTypeDhcpOnly]; Index++) {
        OfferIndex = Private->OfferIndex[PxeOfferTypeDhcpOnly][Index];
        if (Private->OfferBuffer[OfferIndex].Dhcp6.OptList[PXEBC_DHCP6_IDX_BOOT_FILE_URL] != NULL) {
          Private->SelectIndex = OfferIndex + 1;
          break;
        }
      }
    }
  } else {
    //
    // Select offer by received order.
    //
    for (Index = 0; Index < Private->OfferNum; Index++) {

      OfferType = Private->OfferBuffer[Index].Dhcp6.OfferType;

      if (IS_PROXY_OFFER (OfferType)) {
        //
        // Skip proxy offers
        //
        continue;
      }

      if (!Private->IsProxyRecved &&
          OfferType == PxeOfferTypeDhcpOnly &&
          Private->OfferBuffer[Index].Dhcp6.OptList[PXEBC_DHCP6_IDX_BOOT_FILE_URL] == NULL) {
        //
        // Skip if DhcpOnly offer without any other proxy offers or bootfilename.
        //
        continue;
      }

      Private->SelectIndex = Index + 1;
      break;
    }
  }
}


/**
  Handle the DHCPv6 offer packet.

  @param[in]  Private             The pointer to PXEBC_PRIVATE_DATA.

  @retval     EFI_SUCCESS           Handled the DHCPv6 offer packet successfully.
  @retval     EFI_NO_RESPONSE       No response to the following request packet.
  @retval     EFI_OUT_OF_RESOURCES  Failed to allocate resources.
  @retval     EFI_BUFFER_TOO_SMALL  Can't cache the offer pacet.

**/
EFI_STATUS
PxeBcHandleDhcp6Offer (
  IN PXEBC_PRIVATE_DATA            *Private
  )
{
  PXEBC_DHCP6_PACKET_CACHE         *Cache6;
  EFI_STATUS                       Status;
  PXEBC_OFFER_TYPE                 OfferType;
  UINT32                           ProxyIndex;
  UINT32                           SelectIndex;
  UINT32                           Index;

  ASSERT (Private->SelectIndex > 0);
  SelectIndex = (UINT32) (Private->SelectIndex - 1);
  ASSERT (SelectIndex < PXEBC_OFFER_MAX_NUM);
  Cache6      = &Private->OfferBuffer[SelectIndex].Dhcp6;
  Status      = EFI_SUCCESS;

  //
  // First try to cache DNS server address if DHCP6 offer provides.
  //
  if (Cache6->OptList[PXEBC_DHCP6_IDX_DNS_SERVER] != NULL) {
    Private->DnsServer = AllocateZeroPool (NTOHS (Cache6->OptList[PXEBC_DHCP6_IDX_DNS_SERVER]->OpLen));
    if (Private->DnsServer == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }
    CopyMem (Private->DnsServer, Cache6->OptList[PXEBC_DHCP6_IDX_DNS_SERVER]->Data, sizeof (EFI_IPv6_ADDRESS));
  }

  if (Cache6->OfferType == PxeOfferTypeDhcpBinl) {
    //
    // DhcpBinl offer is selected, so need try to request bootfilename by this offer.
    //
    if (EFI_ERROR (PxeBcRetryDhcp6Binl (Private, SelectIndex))) {
      Status = EFI_NO_RESPONSE;
    }
  } else if (Cache6->OfferType == PxeOfferTypeDhcpOnly) {

    if (Private->IsProxyRecved) {
      //
      // DhcpOnly offer is selected, so need try to request bootfilename.
      //
      ProxyIndex = 0;
      if (Private->IsOfferSorted) {
        //
        // The proxy offer should be determined if select by default policy.
        // IsOfferSorted means all offers are labeled by OfferIndex.
        //
        ASSERT (Private->OfferCount[Private->SelectProxyType] > 0);

        if (Private->SelectProxyType == PxeOfferTypeProxyBinl) {
          //
          // Try all the cached ProxyBinl offer one by one to request bootfilename.
          //
          for (Index = 0; Index < Private->OfferCount[Private->SelectProxyType]; Index++) {

            ProxyIndex = Private->OfferIndex[Private->SelectProxyType][Index];
            if (!EFI_ERROR (PxeBcRetryDhcp6Binl (Private, ProxyIndex))) {
              break;
            }
          }
          if (Index == Private->OfferCount[Private->SelectProxyType]) {
            Status = EFI_NO_RESPONSE;
          }
        } else {
          //
          // For other proxy offers (pxe10 or wfm11a), only one is buffered.
          //
          ProxyIndex = Private->OfferIndex[Private->SelectProxyType][0];
        }
      } else {
        //
        // The proxy offer should not be determined if select by received order.
        //
        Status = EFI_NO_RESPONSE;

        for (Index = 0; Index < Private->OfferNum; Index++) {

          OfferType = Private->OfferBuffer[Index].Dhcp6.OfferType;

          if (!IS_PROXY_OFFER (OfferType)) {
            //
            // Skip non proxy dhcp offers.
            //
            continue;
          }

          if (OfferType == PxeOfferTypeProxyBinl) {
            //
            // Try all the cached ProxyBinl offer one by one to request bootfilename.
            //
            if (EFI_ERROR (PxeBcRetryDhcp6Binl (Private, Index))) {
              continue;
            }
          }

          Private->SelectProxyType = OfferType;
          ProxyIndex               = Index;
          Status                   = EFI_SUCCESS;
          break;
        }
      }

      if (!EFI_ERROR (Status) && Private->SelectProxyType != PxeOfferTypeProxyBinl) {
        //
        // Success to try to request by a ProxyPxe10 or ProxyWfm11a offer, copy and parse it.
        //
        Status = PxeBcCopyDhcp6Proxy (Private, ProxyIndex);
      }
    } else {
      //
      //  Othewise, the bootfilename must be included in DhcpOnly offer.
      //
      ASSERT (Cache6->OptList[PXEBC_DHCP6_IDX_BOOT_FILE_URL] != NULL);
    }
  }

  if (!EFI_ERROR (Status)) {
    //
    // All PXE boot information is ready by now.
    //
    Status = PxeBcCopyDhcp6Ack (Private, &Private->DhcpAck.Dhcp6.Packet.Ack, TRUE);
    Private->PxeBc.Mode->DhcpDiscoverValid = TRUE;
  }

  return Status;
}


/**
  Unregister the address by Ip6Config protocol.

  @param[in]  Private             The pointer to PXEBC_PRIVATE_DATA.

**/
VOID
PxeBcUnregisterIp6Address (
  IN PXEBC_PRIVATE_DATA           *Private
  )
{
  if (Private->Ip6Policy != PXEBC_IP6_POLICY_MAX) {
    //
    // PXE driver change the policy of IP6 driver, it's a chance to recover.
    // Keep the point and there is no enough requirements to do recovery.
    //
  }
}

/**
  Check whether IP driver could route the message which will be sent to ServerIp address.
  
  This function will check the IP6 route table every 1 seconds until specified timeout is expired, if a valid
  route is found in IP6 route table, the address will be filed in GatewayAddr and return.

  @param[in]  Private             The pointer to PXEBC_PRIVATE_DATA.
  @param[in]  TimeOutInSecond     Timeout value in seconds.
  @param[out] GatewayAddr         Pointer to store the gateway IP address.

  @retval     EFI_SUCCESS         Found a valid gateway address successfully.
  @retval     EFI_TIMEOUT         The operation is time out.
  @retval     Other               Unexpect error happened.
  
**/
EFI_STATUS
PxeBcCheckRouteTable (
  IN  PXEBC_PRIVATE_DATA            *Private,
  IN  UINTN                         TimeOutInSecond,
  OUT EFI_IPv6_ADDRESS              *GatewayAddr
  )
{
  EFI_STATUS                       Status;
  EFI_IP6_PROTOCOL                 *Ip6;
  EFI_IP6_MODE_DATA                Ip6ModeData;
  UINTN                            Index;
  EFI_EVENT                        TimeOutEvt;
  UINTN                            RetryCount;
  BOOLEAN                          GatewayIsFound;

  ASSERT (GatewayAddr != NULL);
  ASSERT (Private != NULL);

  Ip6            = Private->Ip6;
  GatewayIsFound = FALSE;
  RetryCount     = 0;
  TimeOutEvt     = NULL;
  ZeroMem (GatewayAddr, sizeof (EFI_IPv6_ADDRESS));

  while (TRUE) {
    Status = Ip6->GetModeData (Ip6, &Ip6ModeData, NULL, NULL);
    if (EFI_ERROR (Status)) {
      goto ON_EXIT;
    }

    //
    // Find out the gateway address which can route the message which send to ServerIp.
    //
    for (Index = 0; Index < Ip6ModeData.RouteCount; Index++) {
      if (NetIp6IsNetEqual (&Private->ServerIp.v6, &Ip6ModeData.RouteTable[Index].Destination, Ip6ModeData.RouteTable[Index].PrefixLength)) {
        IP6_COPY_ADDRESS (GatewayAddr, &Ip6ModeData.RouteTable[Index].Gateway);
        GatewayIsFound = TRUE;
        break;
      }
    }

    if (Ip6ModeData.AddressList != NULL) {
      FreePool (Ip6ModeData.AddressList);
    }
    if (Ip6ModeData.GroupTable != NULL) {
      FreePool (Ip6ModeData.GroupTable);
    }
    if (Ip6ModeData.RouteTable != NULL) {
      FreePool (Ip6ModeData.RouteTable);
    }
    if (Ip6ModeData.NeighborCache != NULL) {
      FreePool (Ip6ModeData.NeighborCache);
    }
    if (Ip6ModeData.PrefixTable != NULL) {
      FreePool (Ip6ModeData.PrefixTable);
    }
    if (Ip6ModeData.IcmpTypeList != NULL) {
      FreePool (Ip6ModeData.IcmpTypeList);
    }
    
    if (GatewayIsFound || RetryCount == TimeOutInSecond) {
      break;
    }
    
    RetryCount++;
    
    //
    // Delay 1 second then recheck it again.
    //
    if (TimeOutEvt == NULL) {
      Status = gBS->CreateEvent (
                      EVT_TIMER,
                      TPL_CALLBACK,
                      NULL,
                      NULL,
                      &TimeOutEvt
                      );
      if (EFI_ERROR (Status)) {
        goto ON_EXIT;
      }
    }

    Status = gBS->SetTimer (TimeOutEvt, TimerRelative, TICKS_PER_SECOND);
    if (EFI_ERROR (Status)) {
      goto ON_EXIT;
    }
    while (EFI_ERROR (gBS->CheckEvent (TimeOutEvt))) {
      Ip6->Poll (Ip6);
    }
  }
  
ON_EXIT:
  if (TimeOutEvt != NULL) {
    gBS->CloseEvent (TimeOutEvt);
  }
  
  if (GatewayIsFound) {
    Status = EFI_SUCCESS;
  } else if (RetryCount == TimeOutInSecond) {
    Status = EFI_TIMEOUT;
  }

  return Status; 
}

/**
  Register the ready station address and gateway by Ip6Config protocol.

  @param[in]  Private             The pointer to PXEBC_PRIVATE_DATA.
  @param[in]  Address             The pointer to the ready address.

  @retval     EFI_SUCCESS         Registered the address succesfully.
  @retval     Others              Failed to register the address.

**/
EFI_STATUS
PxeBcRegisterIp6Address (
  IN PXEBC_PRIVATE_DATA            *Private,
  IN EFI_IPv6_ADDRESS              *Address
  )
{
  EFI_IP6_PROTOCOL                 *Ip6;
  EFI_IP6_CONFIG_PROTOCOL          *Ip6Cfg;
  EFI_IP6_CONFIG_POLICY            Policy;
  EFI_IP6_CONFIG_MANUAL_ADDRESS    CfgAddr;
  EFI_IPv6_ADDRESS                 GatewayAddr;
  UINTN                            DataSize;
  EFI_EVENT                        MappedEvt;
  EFI_STATUS                       Status;
  BOOLEAN                          NoGateway;
  EFI_IPv6_ADDRESS                 *Ip6Addr;
  UINTN                            Index;

  Status     = EFI_SUCCESS;
  MappedEvt  = NULL;
  Ip6Addr    = NULL;
  DataSize   = sizeof (EFI_IP6_CONFIG_POLICY);
  Ip6Cfg     = Private->Ip6Cfg;
  Ip6        = Private->Ip6;
  NoGateway  = FALSE;

  ZeroMem (&CfgAddr, sizeof (EFI_IP6_CONFIG_MANUAL_ADDRESS));
  CopyMem (&CfgAddr.Address, Address, sizeof (EFI_IPv6_ADDRESS));

  Status = Ip6->Configure (Ip6, &Private->Ip6CfgData);
  if (EFI_ERROR (Status)) {
    goto ON_EXIT;
  }

  //
  // Retrieve the gateway address from IP6 route table.
  //
  Status = PxeBcCheckRouteTable (Private, PXEBC_IP6_ROUTE_TABLE_TIMEOUT, &GatewayAddr);
  if (EFI_ERROR (Status)) {
    NoGateway = TRUE;
  }
  
  //
  // There is no channel between IP6 and PXE driver about address setting,
  // so it has to set the new address by Ip6ConfigProtocol manually.
  //
  Policy = Ip6ConfigPolicyManual;
  Status = Ip6Cfg->SetData (
                     Ip6Cfg,
                     Ip6ConfigDataTypePolicy,
                     sizeof(EFI_IP6_CONFIG_POLICY),
                     &Policy
                     );
  if (EFI_ERROR (Status)) {
    //
    // There is no need to recover later.
    //
    Private->Ip6Policy = PXEBC_IP6_POLICY_MAX;
    goto ON_EXIT;
  }

  //
  // Create a notify event to set address flag when DAD if IP6 driver succeeded.
  //
  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  PxeBcCommonNotify,
                  &Private->IsAddressOk,
                  &MappedEvt
                  );
  if (EFI_ERROR (Status)) {
    goto ON_EXIT;
  }

  Private->IsAddressOk = FALSE;
  Status = Ip6Cfg->RegisterDataNotify (
                     Ip6Cfg,
                     Ip6ConfigDataTypeManualAddress,
                     MappedEvt
                     );
  if (EFI_ERROR(Status)) {
    goto ON_EXIT;
  }

  Status = Ip6Cfg->SetData (
                     Ip6Cfg,
                     Ip6ConfigDataTypeManualAddress,
                     sizeof(EFI_IP6_CONFIG_MANUAL_ADDRESS),
                     &CfgAddr
                     );
  if (EFI_ERROR(Status) && Status != EFI_NOT_READY) {
    goto ON_EXIT;
  } else if (Status == EFI_NOT_READY) {
    //
    // Poll the network until the asynchronous process is finished.
    //
    while (!Private->IsAddressOk) {
      Ip6->Poll (Ip6);
    }
    //
    // Check whether the IP6 address setting is successed.
    //
    DataSize = 0;
    Status = Ip6Cfg->GetData (
                       Ip6Cfg,
                       Ip6ConfigDataTypeManualAddress,
                       &DataSize,
                       NULL
                       );
    if (Status != EFI_BUFFER_TOO_SMALL || DataSize == 0) {
      Status = EFI_DEVICE_ERROR;
      goto ON_EXIT;
    }

    Ip6Addr = AllocatePool (DataSize);
    if (Ip6Addr == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }
    Status = Ip6Cfg->GetData (
                       Ip6Cfg,
                       Ip6ConfigDataTypeManualAddress,
                       &DataSize,
                       (VOID*) Ip6Addr
                       );
    if (EFI_ERROR (Status)) {
      Status = EFI_DEVICE_ERROR;
      goto ON_EXIT;
    }

    for (Index = 0; Index < DataSize / sizeof (EFI_IPv6_ADDRESS); Index++) {
      if (CompareMem (Ip6Addr + Index, Address, sizeof (EFI_IPv6_ADDRESS)) == 0) {
        break;
      }
    }
    if (Index == DataSize / sizeof (EFI_IPv6_ADDRESS)) {
      Status = EFI_ABORTED;
      goto ON_EXIT;
    }
  }
  
  //
  // Set the default gateway address back if needed.
  //
  if (!NoGateway && !NetIp6IsUnspecifiedAddr (&GatewayAddr)) {
    Status = Ip6Cfg->SetData (
                       Ip6Cfg,
                       Ip6ConfigDataTypeGateway,
                       sizeof (EFI_IPv6_ADDRESS),
                       &GatewayAddr
                       );
    if (EFI_ERROR (Status)) {
      goto ON_EXIT;
    }
  }

ON_EXIT:
  if (MappedEvt != NULL) {
    Ip6Cfg->UnregisterDataNotify (
              Ip6Cfg,
              Ip6ConfigDataTypeManualAddress,
              MappedEvt
              );
    gBS->CloseEvent (MappedEvt);
  }
  if (Ip6Addr != NULL) {
    FreePool (Ip6Addr);
  }
  return Status;
}

/**
  Set the IP6 policy to Automatic.

  @param[in]  Private             The pointer to PXEBC_PRIVATE_DATA.

  @retval     EFI_SUCCESS         Switch the IP policy succesfully.
  @retval     Others              Unexpect error happened.

**/
EFI_STATUS
PxeBcSetIp6Policy (
  IN PXEBC_PRIVATE_DATA            *Private
  )
{
  EFI_IP6_CONFIG_POLICY            Policy;
  EFI_STATUS                       Status;
  EFI_IP6_CONFIG_PROTOCOL          *Ip6Cfg;
  UINTN                            DataSize;

  Ip6Cfg      = Private->Ip6Cfg;
  DataSize    = sizeof (EFI_IP6_CONFIG_POLICY);

  //
  // Get and store the current policy of IP6 driver.
  //
  Status = Ip6Cfg->GetData (
                     Ip6Cfg,
                     Ip6ConfigDataTypePolicy,
                     &DataSize,
                     &Private->Ip6Policy
                     );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (Private->Ip6Policy == Ip6ConfigPolicyManual) {
    Policy = Ip6ConfigPolicyAutomatic;
    Status = Ip6Cfg->SetData (
                       Ip6Cfg,
                       Ip6ConfigDataTypePolicy,
                       sizeof(EFI_IP6_CONFIG_POLICY),
                       &Policy
                       );
    if (EFI_ERROR (Status)) {
      //
      // There is no need to recover later.
      //
      Private->Ip6Policy = PXEBC_IP6_POLICY_MAX;
    }
  }

  return Status;
}

/**
  This function will register the station IP address and flush IP instance to start using the new IP address.
  
  @param[in]  Private             The pointer to PXEBC_PRIVATE_DATA.

  @retval     EFI_SUCCESS         The new IP address has been configured successfully.
  @retval     Others              Failed to configure the address.

**/
EFI_STATUS
PxeBcSetIp6Address (
  IN  PXEBC_PRIVATE_DATA              *Private
  )
{
  EFI_STATUS                  Status;
  EFI_DHCP6_PROTOCOL          *Dhcp6;
    
  Dhcp6 = Private->Dhcp6;

  CopyMem (&Private->StationIp.v6, &Private->TmpStationIp.v6, sizeof (EFI_IPv6_ADDRESS));
  CopyMem (&Private->PxeBc.Mode->StationIp.v6, &Private->StationIp.v6, sizeof (EFI_IPv6_ADDRESS));

  Status = PxeBcRegisterIp6Address (Private, &Private->StationIp.v6);
  if (EFI_ERROR (Status)) {
    Dhcp6->Stop (Dhcp6);
    return Status;
  }

  Status = PxeBcFlushStationIp (Private, &Private->StationIp, NULL);
  if (EFI_ERROR (Status)) {
    PxeBcUnregisterIp6Address (Private);
    Dhcp6->Stop (Dhcp6);
    return Status;
  }

  AsciiPrint ("\n  Station IP address is ");
  PxeBcShowIp6Addr (&Private->StationIp.v6);

  return EFI_SUCCESS;
}

/**
  EFI_DHCP6_CALLBACK is provided by the consumer of the EFI DHCPv6 Protocol driver
  to intercept events that occurred in the configuration process.

  @param[in]  This              The pointer to the EFI DHCPv6 Protocol.
  @param[in]  Context           The pointer to the context set by EFI_DHCP6_PROTOCOL.Configure().
  @param[in]  CurrentState      The current operational state of the EFI DHCPv Protocol driver.
  @param[in]  Dhcp6Event        The event that occurs in the current state, which usually means a
                                state transition.
  @param[in]  Packet            The DHCPv6 packet that is going to be sent or was already received.
  @param[out] NewPacket         The packet that is used to replace the Packet above.

  @retval EFI_SUCCESS           Told the EFI DHCPv6 Protocol driver to continue the DHCP process.
  @retval EFI_NOT_READY         Only used in the Dhcp6Selecting state. The EFI DHCPv6 Protocol
                                driver will continue to wait for more packets.
  @retval EFI_ABORTED           Told the EFI DHCPv6 Protocol driver to abort the current process.

**/
EFI_STATUS
EFIAPI
PxeBcDhcp6CallBack (
  IN  EFI_DHCP6_PROTOCOL           *This,
  IN  VOID                         *Context,
  IN  EFI_DHCP6_STATE              CurrentState,
  IN  EFI_DHCP6_EVENT              Dhcp6Event,
  IN  EFI_DHCP6_PACKET             *Packet,
  OUT EFI_DHCP6_PACKET             **NewPacket     OPTIONAL
  )
{
  PXEBC_PRIVATE_DATA                  *Private;
  EFI_PXE_BASE_CODE_MODE              *Mode;
  EFI_PXE_BASE_CODE_CALLBACK_PROTOCOL *Callback;
  EFI_DHCP6_PACKET                    *SelectAd;
  EFI_STATUS                          Status;
  BOOLEAN                             Received;

  if ((Dhcp6Event != Dhcp6RcvdAdvertise) &&
      (Dhcp6Event != Dhcp6SelectAdvertise) &&
      (Dhcp6Event != Dhcp6SendSolicit) &&
      (Dhcp6Event != Dhcp6SendRequest) &&
      (Dhcp6Event != Dhcp6RcvdReply)) {
    return EFI_SUCCESS;
  }

  ASSERT (Packet != NULL);

  Private   = (PXEBC_PRIVATE_DATA *) Context;
  Mode      = Private->PxeBc.Mode;
  Callback  = Private->PxeBcCallback;

  //
  // Callback to user when any traffic ocurred if has.
  //
  if (Dhcp6Event != Dhcp6SelectAdvertise && Callback != NULL) {
    Received = (BOOLEAN) (Dhcp6Event == Dhcp6RcvdAdvertise || Dhcp6Event == Dhcp6RcvdReply);
    Status = Callback->Callback (
                         Callback,
                         Private->Function,
                         Received,
                         Packet->Length,
                         (EFI_PXE_BASE_CODE_PACKET *) &Packet->Dhcp6
                         );
    if (Status != EFI_PXE_BASE_CODE_CALLBACK_STATUS_CONTINUE) {
      return EFI_ABORTED;
    }
  }

  Status = EFI_SUCCESS;

  switch (Dhcp6Event) {

  case Dhcp6SendSolicit:
    if (Packet->Length > PXEBC_DHCP6_PACKET_MAX_SIZE) {
      //
      // If the to be sent packet exceeds the maximum length, abort the DHCP process.
      //
      Status = EFI_ABORTED;
      break;
    }
    
    //
    // Record the first Solicate msg time
    //
    if (Private->SolicitTimes == 0) {
      CalcElapsedTime (Private);
      Private->SolicitTimes++;
    }
    //
    // Cache the dhcp discover packet to mode data directly.
    //
    CopyMem (&Mode->DhcpDiscover.Dhcpv4, &Packet->Dhcp6, Packet->Length);
    break;

  case Dhcp6RcvdAdvertise:
    Status = EFI_NOT_READY;
    if (Packet->Length > PXEBC_DHCP6_PACKET_MAX_SIZE) {
      //
      // Ignore the incoming packets which exceed the maximum length.
      //
      break;
    }
    if (Private->OfferNum < PXEBC_OFFER_MAX_NUM) {
      //
      // Cache the dhcp offers to OfferBuffer[] for select later, and record
      // the OfferIndex and OfferCount.
      //
      PxeBcCacheDhcp6Offer (Private, Packet);
    }
    break;

  case Dhcp6SendRequest:
    if (Packet->Length > PXEBC_DHCP6_PACKET_MAX_SIZE) {
      //
      // If the to be sent packet exceeds the maximum length, abort the DHCP process.
      //
      Status = EFI_ABORTED;
      break;
    }
    
    //
    // Store the request packet as seed packet for discover.
    //
    if (Private->Dhcp6Request != NULL) {
      FreePool (Private->Dhcp6Request);
    }
    Private->Dhcp6Request = AllocateZeroPool (Packet->Size);
    if (Private->Dhcp6Request != NULL) {
      CopyMem (Private->Dhcp6Request, Packet, Packet->Size);
    }
    break;

  case Dhcp6SelectAdvertise:
    //
    // Select offer by the default policy or by order, and record the SelectIndex
    // and SelectProxyType.
    //
    PxeBcSelectDhcp6Offer (Private);

    if (Private->SelectIndex == 0) {
      Status = EFI_ABORTED;
    } else {
      ASSERT (NewPacket != NULL);
      SelectAd   = &Private->OfferBuffer[Private->SelectIndex - 1].Dhcp6.Packet.Offer;
      *NewPacket = AllocateZeroPool (SelectAd->Size);
      ASSERT (*NewPacket != NULL);
      if (*NewPacket == NULL) {
        return EFI_ABORTED;
      }
      CopyMem (*NewPacket, SelectAd, SelectAd->Size);
    }
    break;

  case Dhcp6RcvdReply:
    //
    // Cache the dhcp ack to Private->Dhcp6Ack, but it's not the final ack in mode data
    // without verification.
    //
    ASSERT (Private->SelectIndex != 0);
    Status = PxeBcCopyDhcp6Ack (Private, Packet, FALSE);
    if (EFI_ERROR (Status)) {
      Status = EFI_ABORTED;
    }
    break;

  default:
    ASSERT (0);
  }

  return Status;
}


/**
  Build and send out the request packet for the bootfile, and parse the reply.

  @param[in]  Private               The pointer to PxeBc private data.
  @param[in]  Type                  PxeBc option boot item type.
  @param[in]  Layer                 The pointer to option boot item layer.
  @param[in]  UseBis                Use BIS or not.
  @param[in]  DestIp                The pointer to the server address.

  @retval     EFI_SUCCESS           Successfully discovered the boot file.
  @retval     EFI_OUT_OF_RESOURCES  Failed to allocate resources.
  @retval     EFI_NOT_FOUND         Can't get the PXE reply packet.
  @retval     Others                Failed to discover the boot file.

**/
EFI_STATUS
PxeBcDhcp6Discover (
  IN  PXEBC_PRIVATE_DATA              *Private,
  IN  UINT16                          Type,
  IN  UINT16                          *Layer,
  IN  BOOLEAN                         UseBis,
  IN  EFI_IP_ADDRESS                  *DestIp
  )
{
  EFI_PXE_BASE_CODE_UDP_PORT          SrcPort;
  EFI_PXE_BASE_CODE_UDP_PORT          DestPort;
  EFI_PXE_BASE_CODE_MODE              *Mode;
  EFI_PXE_BASE_CODE_PROTOCOL          *PxeBc;
  EFI_PXE_BASE_CODE_DHCPV6_PACKET     *Discover;
  UINTN                               DiscoverLen;
  EFI_DHCP6_PACKET                    *Request;
  UINTN                               RequestLen;
  EFI_DHCP6_PACKET                    *Reply;
  UINT8                               *RequestOpt;
  UINT8                               *DiscoverOpt;
  UINTN                               ReadSize;
  UINT16                              OpCode;
  UINT16                              OpLen;
  UINT32                              Xid;
  EFI_STATUS                          Status;

  PxeBc       = &Private->PxeBc;
  Mode        = PxeBc->Mode;
  Request     = Private->Dhcp6Request;
  SrcPort     = PXEBC_BS_DISCOVER_PORT;
  DestPort    = PXEBC_BS_DISCOVER_PORT;

  if (!UseBis && Layer != NULL) {
    *Layer &= EFI_PXE_BASE_CODE_BOOT_LAYER_MASK;
  }

  if (Request == NULL) {
    return EFI_DEVICE_ERROR;
  }

  Discover = AllocateZeroPool (sizeof (EFI_PXE_BASE_CODE_DHCPV6_PACKET));
  if (Discover == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Build the discover packet by the cached request packet before.
  //
  Xid                     = NET_RANDOM (NetRandomInitSeed ());
  Discover->TransactionId = HTONL (Xid);
  Discover->MessageType   = Request->Dhcp6.Header.MessageType;
  RequestOpt              = Request->Dhcp6.Option;
  DiscoverOpt             = Discover->DhcpOptions;
  DiscoverLen             = sizeof (EFI_DHCP6_HEADER);
  RequestLen              = DiscoverLen;

  while (RequestLen < Request->Length) {
    OpCode = NTOHS (((EFI_DHCP6_PACKET_OPTION *) RequestOpt)->OpCode);
    OpLen  = NTOHS (((EFI_DHCP6_PACKET_OPTION *) RequestOpt)->OpLen);
    if (OpCode != EFI_DHCP6_IA_TYPE_NA &&
        OpCode != EFI_DHCP6_IA_TYPE_TA) {
      //
      // Copy all the options except IA option.
      //
      CopyMem (DiscoverOpt, RequestOpt, OpLen + 4);
      DiscoverOpt += (OpLen + 4);
      DiscoverLen += (OpLen + 4);
    }
    RequestOpt += (OpLen + 4);
    RequestLen += (OpLen + 4);
  }

  Status = PxeBc->UdpWrite (
                    PxeBc,
                    0,
                    &Private->ServerIp,
                    &DestPort,
                    NULL,
                    &Private->StationIp,
                    &SrcPort,
                    NULL,
                    NULL,
                    &DiscoverLen,
                    (VOID *) Discover
                    );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Cache the right PXE reply packet here, set valid flag later.
  // Especially for PXE discover packet, store it into mode data here.
  //
  if (Private->IsDoDiscover) {
    CopyMem (&Mode->PxeDiscover.Dhcpv6, Discover, DiscoverLen);
    Reply = &Private->PxeReply.Dhcp6.Packet.Ack;
  } else {
    Reply = &Private->ProxyOffer.Dhcp6.Packet.Offer;
  }
  ReadSize = (UINTN) Reply->Size;

  //
  // Start Udp6Read instance
  //
  Status = Private->Udp6Read->Configure (Private->Udp6Read, &Private->Udp6CfgData);
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }
  
  Status = PxeBc->UdpRead (
                    PxeBc,
                    EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_DEST_IP,
                    NULL,
                    &SrcPort,
                    &Private->ServerIp,
                    &DestPort,
                    NULL,
                    NULL,
                    &ReadSize,
                    (VOID *) &Reply->Dhcp6
                    );
  //
  // Stop Udp6Read instance
  //
  Private->Udp6Read->Configure (Private->Udp6Read, NULL);
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  return EFI_SUCCESS;

ON_ERROR:
  if (Discover != NULL) {
    FreePool (Discover);
  }

  return Status;
}


/**
  Start the DHCPv6 S.A.R.R. process to acquire the IPv6 address and other PXE boot information.

  @param[in]  Private           The pointer to PxeBc private data.
  @param[in]  Dhcp6             The pointer to the EFI_DHCP6_PROTOCOL

  @retval EFI_SUCCESS           The S.A.R.R. process successfully finished.
  @retval Others                Failed to finish the S.A.R.R. process.

**/
EFI_STATUS
PxeBcDhcp6Sarr (
  IN PXEBC_PRIVATE_DATA            *Private,
  IN EFI_DHCP6_PROTOCOL            *Dhcp6
  )
{
  EFI_PXE_BASE_CODE_MODE           *PxeMode;
  EFI_DHCP6_CONFIG_DATA            Config;
  EFI_DHCP6_MODE_DATA              Mode;
  EFI_DHCP6_RETRANSMISSION         *Retransmit;
  EFI_DHCP6_PACKET_OPTION          *OptList[PXEBC_DHCP6_OPTION_MAX_NUM];
  UINT8                            Buffer[PXEBC_DHCP6_OPTION_MAX_SIZE];
  UINT32                           OptCount;
  EFI_STATUS                       Status;
  EFI_IP6_CONFIG_PROTOCOL          *Ip6Cfg;
  EFI_STATUS                       TimerStatus;
  EFI_EVENT                        Timer;
  UINT64                           GetMappingTimeOut;
  UINTN                            DataSize;
  EFI_IP6_CONFIG_DUP_ADDR_DETECT_TRANSMITS    DadXmits;

  Status     = EFI_SUCCESS;
  PxeMode    = Private->PxeBc.Mode;
  Ip6Cfg     = Private->Ip6Cfg;
  Timer      = NULL;

  //
  // Build option list for the request packet.
  //
  OptCount   = PxeBcBuildDhcp6Options (Private, OptList, Buffer);
  ASSERT (OptCount> 0);

  Retransmit = AllocateZeroPool (sizeof (EFI_DHCP6_RETRANSMISSION));
  if (Retransmit == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  ZeroMem (&Mode, sizeof (EFI_DHCP6_MODE_DATA));
  ZeroMem (&Config, sizeof (EFI_DHCP6_CONFIG_DATA));

  Config.OptionCount           = OptCount;
  Config.OptionList            = OptList;
  Config.Dhcp6Callback         = PxeBcDhcp6CallBack;
  Config.CallbackContext       = Private;
  Config.IaInfoEvent           = NULL;
  Config.RapidCommit           = FALSE;
  Config.ReconfigureAccept     = FALSE;
  Config.IaDescriptor.IaId     = Private->IaId;
  Config.IaDescriptor.Type     = EFI_DHCP6_IA_TYPE_NA;
  Config.SolicitRetransmission = Retransmit;
  Retransmit->Irt              = 4;
  Retransmit->Mrc              = 4;
  Retransmit->Mrt              = 32;
  Retransmit->Mrd              = 60;

  //
  // Configure the DHCPv6 instance for PXE boot.
  //
  Status = Dhcp6->Configure (Dhcp6, &Config);
  FreePool (Retransmit);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Initialize the record fields for DHCPv6 offer in private data.
  //
  Private->IsProxyRecved = FALSE;
  Private->OfferNum      = 0;
  Private->SelectIndex   = 0;
  ZeroMem (Private->OfferCount, sizeof (Private->OfferCount));
  ZeroMem (Private->OfferIndex, sizeof (Private->OfferIndex));


  //
  // Start DHCPv6 S.A.R.R. process to acquire IPv6 address.
  //
  Status = Dhcp6->Start (Dhcp6);
  if (Status == EFI_NO_MAPPING) {
    //
    // IP6 Linklocal address is not available for use, so stop current Dhcp process
    // and wait for duplicate address detection to finish.
    //
    Dhcp6->Stop (Dhcp6);

    //
    // Get Duplicate Address Detection Transmits count.
    //
    DataSize = sizeof (EFI_IP6_CONFIG_DUP_ADDR_DETECT_TRANSMITS);
    Status = Ip6Cfg->GetData (
                       Ip6Cfg,
                       Ip6ConfigDataTypeDupAddrDetectTransmits,
                       &DataSize,
                       &DadXmits
                       );
    if (EFI_ERROR (Status)) {
      Dhcp6->Configure (Dhcp6, NULL);
      return Status;
    }

    Status = gBS->CreateEvent (EVT_TIMER, TPL_CALLBACK, NULL, NULL, &Timer);
    if (EFI_ERROR (Status)) {
      Dhcp6->Configure (Dhcp6, NULL);
      return Status;
    }

    GetMappingTimeOut = TICKS_PER_SECOND * DadXmits.DupAddrDetectTransmits + PXEBC_DAD_ADDITIONAL_DELAY;
    Status = gBS->SetTimer (Timer, TimerRelative, GetMappingTimeOut);
    if (EFI_ERROR (Status)) {
      gBS->CloseEvent (Timer);
      Dhcp6->Configure (Dhcp6, NULL);
      return Status;
    }

    do {
      
      TimerStatus = gBS->CheckEvent (Timer);
      if (!EFI_ERROR (TimerStatus)) {
        Status = Dhcp6->Start (Dhcp6);
      }
    } while (TimerStatus == EFI_NOT_READY);
    
    gBS->CloseEvent (Timer);
  }
  if (EFI_ERROR (Status)) {
    if (Status == EFI_ICMP_ERROR) {
      PxeMode->IcmpErrorReceived = TRUE;
    }
    Dhcp6->Configure (Dhcp6, NULL);
    return Status;
  }

  //
  // Get the acquired IPv6 address and store them.
  //
  Status = Dhcp6->GetModeData (Dhcp6, &Mode, NULL);
  if (EFI_ERROR (Status)) {
    Dhcp6->Stop (Dhcp6);
    return Status;
  }

  ASSERT ((Mode.Ia != NULL) && (Mode.Ia->State == Dhcp6Bound));
  //
  // DHCP6 doesn't have an option to specify the router address on the subnet, the only way to get the
  // router address in IP6 is the router discovery mechanism (the RS and RA, which only be handled when
  // the IP policy is Automatic). So we just hold the station IP address here and leave the IP policy as
  // Automatic, until we get the server IP address. This could let IP6 driver finish the router discovery 
  // to find a valid router address.
  //
  CopyMem (&Private->TmpStationIp.v6, &Mode.Ia->IaAddress[0].IpAddress, sizeof (EFI_IPv6_ADDRESS));
  if (Mode.ClientId != NULL) {
    FreePool (Mode.ClientId);
  }
  if (Mode.Ia != NULL) {
    FreePool (Mode.Ia);
  }
  //
  // Check the selected offer whether BINL retry is needed.
  //
  Status = PxeBcHandleDhcp6Offer (Private);
  if (EFI_ERROR (Status)) {
    Dhcp6->Stop (Dhcp6);
    return Status;
  }
  
  return EFI_SUCCESS;
}
