/** @file
  Dhcp6 internal functions implementation.

  (C) Copyright 2014 Hewlett-Packard Development Company, L.P.<BR>
  Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved.<BR>
  Copyright (c) Microsoft Corporation

  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include "Dhcp6Impl.h"

/**
  Enqueue the packet into the retry list in case of timeout.

  @param[in]  Instance        The pointer to the Dhcp6 instance.
  @param[in]  Packet          The pointer to the Dhcp6 packet to retry.
  @param[in]  Elapsed         The pointer to the elapsed time value in the packet.
  @param[in]  RetryCtl        The pointer to the transmission control of the packet.
                              This parameter is optional and may be NULL.

  @retval EFI_SUCCESS           Successfully enqueued the packet into the retry list according
                                to its message type.
  @retval EFI_OUT_OF_RESOURCES  Required system resources could not be allocated.
  @retval EFI_DEVICE_ERROR      An unexpected message type.

**/
EFI_STATUS
Dhcp6EnqueueRetry (
  IN DHCP6_INSTANCE            *Instance,
  IN EFI_DHCP6_PACKET          *Packet,
  IN UINT16                    *Elapsed,
  IN EFI_DHCP6_RETRANSMISSION  *RetryCtl     OPTIONAL
  )
{
  DHCP6_TX_CB  *TxCb;
  DHCP6_IA_CB  *IaCb;

  ASSERT (Packet != NULL);

  IaCb = &Instance->IaCb;
  TxCb = AllocateZeroPool (sizeof (DHCP6_TX_CB));

  if (TxCb == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Save tx packet pointer, and it will be destroyed when reply received.
  //
  TxCb->TxPacket = Packet;
  TxCb->Xid      = Packet->Dhcp6.Header.TransactionId;

  //
  // Save pointer to elapsed-time value so we can update it on retransmits.
  //
  TxCb->Elapsed = Elapsed;

  //
  // Calculate the retransmission according to the message type.
  //
  switch (Packet->Dhcp6.Header.MessageType) {
    case Dhcp6MsgSolicit:
      //
      // Calculate the retransmission threshold value for solicit packet.
      // Use the default value by rfc-3315 if user doesn't configure.
      //
      if (RetryCtl == NULL) {
        TxCb->RetryCtl.Irt = DHCP6_SOL_IRT;
        TxCb->RetryCtl.Mrc = DHCP6_SOL_MRC;
        TxCb->RetryCtl.Mrt = DHCP6_SOL_MRT;
        TxCb->RetryCtl.Mrd = DHCP6_SOL_MRD;
      } else {
        TxCb->RetryCtl.Irt = (RetryCtl->Irt != 0) ? RetryCtl->Irt : DHCP6_SOL_IRT;
        TxCb->RetryCtl.Mrc = (RetryCtl->Mrc != 0) ? RetryCtl->Mrc : DHCP6_SOL_MRC;
        TxCb->RetryCtl.Mrt = (RetryCtl->Mrt != 0) ? RetryCtl->Mrt : DHCP6_SOL_MRT;
        TxCb->RetryCtl.Mrd = (RetryCtl->Mrd != 0) ? RetryCtl->Mrd : DHCP6_SOL_MRD;
      }

      TxCb->RetryExp = Dhcp6CalculateExpireTime (
                         TxCb->RetryCtl.Irt,
                         TRUE,
                         FALSE
                         );
      break;

    case Dhcp6MsgRequest:
      //
      // Calculate the retransmission threshold value for request packet.
      //
      TxCb->RetryCtl.Irt = DHCP6_REQ_IRT;
      TxCb->RetryCtl.Mrc = DHCP6_REQ_MRC;
      TxCb->RetryCtl.Mrt = DHCP6_REQ_MRT;
      TxCb->RetryCtl.Mrd = DHCP6_REQ_MRD;
      TxCb->RetryExp     = Dhcp6CalculateExpireTime (
                             TxCb->RetryCtl.Irt,
                             TRUE,
                             TRUE
                             );
      break;

    case Dhcp6MsgConfirm:
      //
      // Calculate the retransmission threshold value for confirm packet.
      //
      TxCb->RetryCtl.Irt = DHCP6_CNF_IRT;
      TxCb->RetryCtl.Mrc = DHCP6_CNF_MRC;
      TxCb->RetryCtl.Mrt = DHCP6_CNF_MRT;
      TxCb->RetryCtl.Mrd = DHCP6_CNF_MRD;
      TxCb->RetryExp     = Dhcp6CalculateExpireTime (
                             TxCb->RetryCtl.Irt,
                             TRUE,
                             TRUE
                             );
      break;

    case Dhcp6MsgRenew:
      //
      // Calculate the retransmission threshold value for renew packet.
      //
      TxCb->RetryCtl.Irt = DHCP6_REB_IRT;
      TxCb->RetryCtl.Mrc = DHCP6_REB_MRC;
      TxCb->RetryCtl.Mrt = DHCP6_REB_MRT;
      TxCb->RetryCtl.Mrd = IaCb->T2 - IaCb->T1;
      TxCb->RetryExp     = Dhcp6CalculateExpireTime (
                             TxCb->RetryCtl.Irt,
                             TRUE,
                             TRUE
                             );
      break;

    case Dhcp6MsgRebind:
      //
      // Calculate the retransmission threshold value for rebind packet.
      //
      TxCb->RetryCtl.Irt = DHCP6_REN_IRT;
      TxCb->RetryCtl.Mrc = DHCP6_REN_MRC;
      TxCb->RetryCtl.Mrt = DHCP6_REN_MRT;
      TxCb->RetryCtl.Mrd = IaCb->AllExpireTime - IaCb->T2;
      TxCb->RetryExp     = Dhcp6CalculateExpireTime (
                             TxCb->RetryCtl.Irt,
                             TRUE,
                             TRUE
                             );
      break;

    case Dhcp6MsgDecline:
      //
      // Calculate the retransmission threshold value for decline packet.
      //
      TxCb->RetryCtl.Irt = DHCP6_DEC_IRT;
      TxCb->RetryCtl.Mrc = DHCP6_DEC_MRC;
      TxCb->RetryCtl.Mrt = DHCP6_DEC_MRT;
      TxCb->RetryCtl.Mrd = DHCP6_DEC_MRD;
      TxCb->RetryExp     = Dhcp6CalculateExpireTime (
                             TxCb->RetryCtl.Irt,
                             TRUE,
                             TRUE
                             );
      break;

    case Dhcp6MsgRelease:
      //
      // Calculate the retransmission threshold value for release packet.
      //
      TxCb->RetryCtl.Irt = DHCP6_REL_IRT;
      TxCb->RetryCtl.Mrc = DHCP6_REL_MRC;
      TxCb->RetryCtl.Mrt = DHCP6_REL_MRT;
      TxCb->RetryCtl.Mrd = DHCP6_REL_MRD;
      TxCb->RetryExp     = Dhcp6CalculateExpireTime (
                             TxCb->RetryCtl.Irt,
                             TRUE,
                             TRUE
                             );
      break;

    case Dhcp6MsgInfoRequest:
      //
      // Calculate the retransmission threshold value for info-request packet.
      // Use the default value by rfc-3315 if user doesn't configure.
      //
      if (RetryCtl == NULL) {
        TxCb->RetryCtl.Irt = DHCP6_INF_IRT;
        TxCb->RetryCtl.Mrc = DHCP6_INF_MRC;
        TxCb->RetryCtl.Mrt = DHCP6_INF_MRT;
        TxCb->RetryCtl.Mrd = DHCP6_INF_MRD;
      } else {
        TxCb->RetryCtl.Irt = (RetryCtl->Irt != 0) ? RetryCtl->Irt : DHCP6_INF_IRT;
        TxCb->RetryCtl.Mrc = (RetryCtl->Mrc != 0) ? RetryCtl->Mrc : DHCP6_INF_MRC;
        TxCb->RetryCtl.Mrt = (RetryCtl->Mrt != 0) ? RetryCtl->Mrt : DHCP6_INF_MRT;
        TxCb->RetryCtl.Mrd = (RetryCtl->Mrd != 0) ? RetryCtl->Mrd : DHCP6_INF_MRD;
      }

      TxCb->RetryExp = Dhcp6CalculateExpireTime (
                         TxCb->RetryCtl.Irt,
                         TRUE,
                         TRUE
                         );
      break;

    default:
      //
      // Unexpected message type.
      //
      FreePool (TxCb);
      return EFI_DEVICE_ERROR;
  }

  //
  // Insert into the retransmit list of the instance.
  //
  InsertTailList (&Instance->TxList, &TxCb->Link);

  return EFI_SUCCESS;
}

/**
  Dequeue the packet from retry list if reply received or timeout at last.

  @param[in]  Instance        The pointer to the Dhcp6 instance.
  @param[in]  PacketXid       The packet transaction id to match.
  @param[in]  NeedSignal      If TRUE, then an timeout event need be signaled when it is existed.
                              Otherwise, this parameter is ignored.

  @retval EFI_SUCCESS         Successfully dequeued the packet into retry list .
  @retval EFI_NOT_FOUND       There is no xid matched in retry list.

**/
EFI_STATUS
Dhcp6DequeueRetry (
  IN DHCP6_INSTANCE  *Instance,
  IN UINT32          PacketXid,
  IN BOOLEAN         NeedSignal
  )
{
  LIST_ENTRY    *Entry;
  LIST_ENTRY    *NextEntry;
  DHCP6_TX_CB   *TxCb;
  DHCP6_INF_CB  *InfCb;

  //
  // Seek the retransmit node in the retransmit list by packet xid.
  //
  NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Instance->TxList) {
    TxCb = NET_LIST_USER_STRUCT (Entry, DHCP6_TX_CB, Link);
    ASSERT (TxCb->TxPacket);

    if (TxCb->Xid == PacketXid) {
      if (TxCb->TxPacket->Dhcp6.Header.MessageType == Dhcp6MsgInfoRequest) {
        //
        // Seek the info-request node in the info-request list by packet xid.
        //
        NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Instance->InfList) {
          InfCb = NET_LIST_USER_STRUCT (Entry, DHCP6_INF_CB, Link);

          if (InfCb->Xid == PacketXid) {
            //
            // Remove the info-request node, and signal the event if timeout.
            //
            if ((InfCb->TimeoutEvent != NULL) && NeedSignal) {
              gBS->SignalEvent (InfCb->TimeoutEvent);
            }

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

      //
      // Remove the retransmit node.
      //
      RemoveEntryList (&TxCb->Link);
      ASSERT (TxCb->TxPacket);
      FreePool (TxCb->TxPacket);
      FreePool (TxCb);
      return EFI_SUCCESS;
    }
  }

  return EFI_NOT_FOUND;
}

/**
  Clean up the specific nodes in the retry list.

  @param[in]  Instance        The pointer to the Dhcp6 instance.
  @param[in]  Scope           The scope of cleanup nodes.

**/
VOID
Dhcp6CleanupRetry (
  IN DHCP6_INSTANCE  *Instance,
  IN UINT32          Scope
  )
{
  LIST_ENTRY    *Entry;
  LIST_ENTRY    *NextEntry;
  DHCP6_TX_CB   *TxCb;
  DHCP6_INF_CB  *InfCb;

  //
  // Clean up all the stateful messages from the retransmit list.
  //
  if ((Scope == DHCP6_PACKET_STATEFUL) || (Scope == DHCP6_PACKET_ALL)) {
    NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Instance->TxList) {
      TxCb = NET_LIST_USER_STRUCT (Entry, DHCP6_TX_CB, Link);
      ASSERT (TxCb->TxPacket);

      if (TxCb->TxPacket->Dhcp6.Header.MessageType != Dhcp6MsgInfoRequest) {
        RemoveEntryList (&TxCb->Link);
        FreePool (TxCb->TxPacket);
        FreePool (TxCb);
      }
    }
  }

  //
  // Clean up all the stateless messages from the retransmit list.
  //
  if ((Scope == DHCP6_PACKET_STATELESS) || (Scope == DHCP6_PACKET_ALL)) {
    //
    // Clean up all the retransmit list for stateless messages.
    //
    NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Instance->TxList) {
      TxCb = NET_LIST_USER_STRUCT (Entry, DHCP6_TX_CB, Link);
      ASSERT (TxCb->TxPacket);

      if (TxCb->TxPacket->Dhcp6.Header.MessageType == Dhcp6MsgInfoRequest) {
        RemoveEntryList (&TxCb->Link);
        FreePool (TxCb->TxPacket);
        FreePool (TxCb);
      }
    }

    //
    // Clean up all the info-request messages list.
    //
    NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Instance->InfList) {
      InfCb = NET_LIST_USER_STRUCT (Entry, DHCP6_INF_CB, Link);

      if (InfCb->TimeoutEvent != NULL) {
        gBS->SignalEvent (InfCb->TimeoutEvent);
      }

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

/**
  Check whether the TxCb is still a valid control block in the instance's retry list.

  @param[in]  Instance       The pointer to DHCP6_INSTANCE.
  @param[in]  TxCb           The control block for a transmitted message.

  @retval   TRUE      The control block is in Instance's retry list.
  @retval   FALSE     The control block is NOT in Instance's retry list.

**/
BOOLEAN
Dhcp6IsValidTxCb (
  IN  DHCP6_INSTANCE  *Instance,
  IN  DHCP6_TX_CB     *TxCb
  )
{
  LIST_ENTRY  *Entry;

  NET_LIST_FOR_EACH (Entry, &Instance->TxList) {
    if (TxCb == NET_LIST_USER_STRUCT (Entry, DHCP6_TX_CB, Link)) {
      return TRUE;
    }
  }

  return FALSE;
}

/**
  Clean up the session of the instance stateful exchange.

  @param[in, out]  Instance        The pointer to the Dhcp6 instance.
  @param[in]       Status          The return status from udp.

**/
VOID
Dhcp6CleanupSession (
  IN OUT DHCP6_INSTANCE  *Instance,
  IN     EFI_STATUS      Status
  )
{
  UINTN         Index;
  EFI_DHCP6_IA  *Ia;

  ASSERT (Instance->Config);
  ASSERT (Instance->IaCb.Ia);

  //
  // Clean up the retransmit list for stateful messages.
  //
  Dhcp6CleanupRetry (Instance, DHCP6_PACKET_STATEFUL);

  if (Instance->Unicast != NULL) {
    FreePool (Instance->Unicast);
  }

  if (Instance->AdSelect != NULL) {
    FreePool (Instance->AdSelect);
  }

  if (Instance->IaCb.Ia->ReplyPacket != NULL) {
    FreePool (Instance->IaCb.Ia->ReplyPacket);
  }

  //
  // Reinitialize the Ia fields of the instance.
  //
  Instance->UdpSts             = Status;
  Instance->AdSelect           = NULL;
  Instance->AdPref             = 0;
  Instance->Unicast            = NULL;
  Instance->IaCb.T1            = 0;
  Instance->IaCb.T2            = 0;
  Instance->IaCb.AllExpireTime = 0;
  Instance->IaCb.LeaseTime     = 0;

  //
  // Clear start time
  //
  Instance->StartTime = 0;

  Ia              = Instance->IaCb.Ia;
  Ia->State       = Dhcp6Init;
  Ia->ReplyPacket = NULL;

  //
  // Set the addresses as zero lifetime, and then the notify
  // function in Ip6Config will remove these timeout address.
  //
  for (Index = 0; Index < Ia->IaAddressCount; Index++) {
    Ia->IaAddress[Index].PreferredLifetime = 0;
    Ia->IaAddress[Index].ValidLifetime     = 0;
  }

  //
  //
  // Signal the Ia information updated event to informal user.
  //
  if (Instance->Config->IaInfoEvent != NULL) {
    gBS->SignalEvent (Instance->Config->IaInfoEvent);
  }
}

/**
  Callback to user when Dhcp6 transmit/receive occurs.

  @param[in]      Instance        The pointer to the Dhcp6 instance.
  @param[in]      Event           The current Dhcp6 event.
  @param[in, out] Packet          The pointer to the packet sending or received.

  @retval EFI_SUCCESS           The user function returns success.
  @retval EFI_NOT_READY         Direct the caller to continue collecting the offer.
  @retval EFI_ABORTED           The user function ask it to abort.

**/
EFI_STATUS
EFIAPI
Dhcp6CallbackUser (
  IN     DHCP6_INSTANCE    *Instance,
  IN     EFI_DHCP6_EVENT   Event,
  IN OUT EFI_DHCP6_PACKET  **Packet
  )
{
  EFI_STATUS          Status;
  EFI_DHCP6_PACKET    *NewPacket;
  EFI_DHCP6_CALLBACK  Callback;
  VOID                *Context;

  ASSERT (Packet != NULL);
  ASSERT (Instance->Config != NULL);
  ASSERT (Instance->IaCb.Ia != NULL);

  NewPacket = NULL;
  Status    = EFI_SUCCESS;
  Callback  = Instance->Config->Dhcp6Callback;
  Context   = Instance->Config->CallbackContext;

  //
  // Callback to user with the new message if has.
  //
  if (Callback != NULL) {
    Status = Callback (
               &Instance->Dhcp6,
               Context,
               Instance->IaCb.Ia->State,
               Event,
               *Packet,
               &NewPacket
               );
    //
    // Updated the new packet from user to replace the original one.
    //
    if (NewPacket != NULL) {
      ASSERT (*Packet != NULL);
      FreePool (*Packet);
      *Packet = NewPacket;
    }
  }

  return Status;
}

/**
  Update Ia according to the new reply message.

  @param[in, out]  Instance        The pointer to the Dhcp6 instance.
  @param[in]       Packet          The pointer to reply messages.

  @retval EFI_SUCCESS         Updated the Ia information successfully.
  @retval EFI_DEVICE_ERROR    An unexpected error.

**/
EFI_STATUS
Dhcp6UpdateIaInfo (
  IN OUT DHCP6_INSTANCE    *Instance,
  IN     EFI_DHCP6_PACKET  *Packet
  )
{
  EFI_STATUS  Status;
  UINT8       *Option;
  UINT32      OptionLen;
  UINT8       *IaInnerOpt;
  UINT16      IaInnerLen;
  UINT16      StsCode;
  UINT32      T1;
  UINT32      T2;

  T1 = 0;
  T2 = 0;

  ASSERT (Instance->Config != NULL);

  // OptionLen is the length of the Options excluding the DHCP header.
  // Length of the EFI_DHCP6_PACKET from the first byte of the Header field to the last
  // byte of the Option[] field.
  OptionLen = Packet->Length - sizeof (Packet->Dhcp6.Header);

  //
  // If the reply was received in response to a solicit with rapid commit option,
  // request, renew or rebind message, the client updates the information it has
  // recorded about IAs from the IA options contained in the reply message:
  //   1. record the T1 and T2 times
  //   2. add any new addresses in the IA
  //   3. discard any addresses from the IA, that have a valid lifetime of 0
  //   4. update lifetimes for any addresses that already recorded
  //   5. leave unchanged any information about addresses
  //
  // See details in the section-18.1.8 of rfc-3315.
  //
  Option = Dhcp6SeekIaOption (
             Packet->Dhcp6.Option,
             OptionLen,
             &Instance->Config->IaDescriptor
             );
  if (Option == NULL) {
    return EFI_DEVICE_ERROR;
  }

  //
  // Calculate the distance from Packet->Dhcp6.Option to the IA option.
  //
  // Packet->Size and Packet->Length are both UINT32 type, and Packet->Size is
  // the size of the whole packet, including the DHCP header, and Packet->Length
  // is the length of the DHCP message body, excluding the DHCP header.
  //
  // (*Option - Packet->Dhcp6.Option) is the number of bytes from the start of
  // DHCP6 option area to the start of the IA option.
  //
  // Dhcp6SeekInnerOptionSafe() is searching starting from the start of the
  // IA option to the end of the DHCP6 option area, thus subtract the space
  // up until this option
  //
  OptionLen = OptionLen - (UINT32)(Option - Packet->Dhcp6.Option);

  //
  // The format of the IA_NA 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
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //    |          OPTION_IA_NA         |          option-len           |
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //    |                        IAID (4 octets)                        |
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //    |                              T1                               |
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //    |                              T2                               |
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //    |                                                               |
  //    .                         IA_NA-options                         .
  //    .                                                               .
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //
  // The format of the IA_TA 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
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //    |         OPTION_IA_TA          |          option-len           |
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //    |                        IAID (4 octets)                        |
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //    |                                                               |
  //    .                         IA_TA-options                         .
  //    .                                                               .
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //

  //
  // Seek the inner option
  //
  if (EFI_ERROR (
        Dhcp6SeekInnerOptionSafe (
          Instance->Config->IaDescriptor.Type,
          Option,
          OptionLen,
          &IaInnerOpt,
          &IaInnerLen
          )
        ))
  {
    return EFI_DEVICE_ERROR;
  }

  if (Instance->Config->IaDescriptor.Type == Dhcp6OptIana) {
    T1 = NTOHL (ReadUnaligned32 ((UINT32 *)(DHCP6_OFFSET_OF_IA_NA_T1 (Option))));
    T2 = NTOHL (ReadUnaligned32 ((UINT32 *)(DHCP6_OFFSET_OF_IA_NA_T2 (Option))));
    //
    // Refer to RFC3155 Chapter 22.4. If a client receives an IA_NA with T1 greater than T2,
    // and both T1 and T2 are greater than 0, the client discards the IA_NA option and processes
    // the remainder of the message as though the server had not included the invalid IA_NA option.
    //
    if ((T1 > T2) && (T2 > 0)) {
      return EFI_DEVICE_ERROR;
    }
  }

  //
  // The format of the Status Code 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
  //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //  |       OPTION_STATUS_CODE      |         option-len            |
  //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //  |          status-code          |                               |
  //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
  //  .                                                               .
  //  .                        status-message                         .
  //  .                                                               .
  //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //

  //
  // sizeof (option-code + option-len) = 4
  //
  StsCode = Dhcp6StsSuccess;
  Option  = Dhcp6SeekOption (IaInnerOpt, IaInnerLen, Dhcp6OptStatusCode);

  if (Option != NULL) {
    StsCode = NTOHS (ReadUnaligned16 ((UINT16 *)(DHCP6_OFFSET_OF_STATUS_CODE (Option))));
    if (StsCode != Dhcp6StsSuccess) {
      return EFI_DEVICE_ERROR;
    }
  }

  //
  // Generate control block for the Ia.
  //
  Status = Dhcp6GenerateIaCb (
             Instance,
             IaInnerOpt,
             IaInnerLen,
             T1,
             T2
             );

  return Status;
}

/**
  Seeks the Inner Options from a DHCP6 Option

  @param[in]  IaType          The type of the IA option.
  @param[in]  Option          The pointer to the DHCP6 Option.
  @param[in]  OptionLen       The length of the DHCP6 Option.
  @param[out] IaInnerOpt      The pointer to the IA inner option.
  @param[out] IaInnerLen      The length of the IA inner option.

  @retval EFI_SUCCESS         Seek the inner option successfully.
  @retval EFI_DEVICE_ERROR    The OptionLen is invalid. On Error,
                              the pointers are not modified
**/
EFI_STATUS
Dhcp6SeekInnerOptionSafe (
  IN  UINT16  IaType,
  IN  UINT8   *Option,
  IN  UINT32  OptionLen,
  OUT UINT8   **IaInnerOpt,
  OUT UINT16  *IaInnerLen
  )
{
  UINT16  IaInnerLenTmp;
  UINT8   *IaInnerOptTmp;

  if (Option == NULL) {
    ASSERT (Option != NULL);
    return EFI_DEVICE_ERROR;
  }

  if (IaInnerOpt == NULL) {
    ASSERT (IaInnerOpt != NULL);
    return EFI_DEVICE_ERROR;
  }

  if (IaInnerLen == NULL) {
    ASSERT (IaInnerLen != NULL);
    return EFI_DEVICE_ERROR;
  }

  if (IaType == Dhcp6OptIana) {
    //
    // Verify we have a fully formed IA_NA
    //
    if (OptionLen < DHCP6_MIN_SIZE_OF_IA_NA) {
      return EFI_DEVICE_ERROR;
    }

    //
    // Get the IA Inner Option and Length
    //
    IaInnerOptTmp = DHCP6_OFFSET_OF_IA_NA_INNER_OPT (Option);

    //
    // Verify the IaInnerLen is valid.
    //
    IaInnerLenTmp = (UINT16)NTOHS (ReadUnaligned16 ((UINT16 *)DHCP6_OFFSET_OF_OPT_LEN (Option)));
    if (IaInnerLenTmp < DHCP6_SIZE_OF_COMBINED_IAID_T1_T2) {
      return EFI_DEVICE_ERROR;
    }

    IaInnerLenTmp -= DHCP6_SIZE_OF_COMBINED_IAID_T1_T2;
  } else if (IaType == Dhcp6OptIata) {
    //
    // Verify the OptionLen is valid.
    //
    if (OptionLen < DHCP6_MIN_SIZE_OF_IA_TA) {
      return EFI_DEVICE_ERROR;
    }

    IaInnerOptTmp = DHCP6_OFFSET_OF_IA_TA_INNER_OPT (Option);

    //
    // Verify the IaInnerLen is valid.
    //
    IaInnerLenTmp = (UINT16)NTOHS (ReadUnaligned16 ((UINT16 *)(DHCP6_OFFSET_OF_OPT_LEN (Option))));
    if (IaInnerLenTmp < DHCP6_SIZE_OF_IAID) {
      return EFI_DEVICE_ERROR;
    }

    IaInnerLenTmp -= DHCP6_SIZE_OF_IAID;
  } else {
    return EFI_DEVICE_ERROR;
  }

  *IaInnerOpt = IaInnerOptTmp;
  *IaInnerLen = IaInnerLenTmp;

  return EFI_SUCCESS;
}

/**
  Seek StatusCode Option in package. A Status Code option may appear in the
  options field of a DHCP message and/or in the options field of another option.
  See details in section 22.13, RFC3315.

  @param[in]       Instance        The pointer to the Dhcp6 instance.
  @param[in]       Packet          The pointer to reply messages.
  @param[out]      Option          The pointer to status code option.

  @retval EFI_SUCCESS              Seek status code option successfully.
  @retval EFI_DEVICE_ERROR         An unexpected error.

**/
EFI_STATUS
Dhcp6SeekStsOption (
  IN     DHCP6_INSTANCE    *Instance,
  IN     EFI_DHCP6_PACKET  *Packet,
  OUT    UINT8             **Option
  )
{
  UINT8   *IaInnerOpt;
  UINT16  IaInnerLen;
  UINT16  StsCode;
  UINT32  OptionLen;

  // OptionLen is the length of the Options excluding the DHCP header.
  // Length of the EFI_DHCP6_PACKET from the first byte of the Header field to the last
  // byte of the Option[] field.
  OptionLen = Packet->Length - sizeof (Packet->Dhcp6.Header);

  //
  // Seek StatusCode option directly in DHCP message body. That is, search in
  // non-encapsulated option fields.
  //
  *Option = Dhcp6SeekOption (
              Packet->Dhcp6.Option,
              OptionLen,
              Dhcp6OptStatusCode
              );

  if (*Option != NULL) {
    StsCode = NTOHS (ReadUnaligned16 ((UINT16 *)(DHCP6_OFFSET_OF_STATUS_CODE (*Option))));
    if (StsCode != Dhcp6StsSuccess) {
      return EFI_DEVICE_ERROR;
    }
  }

  //
  // Seek in encapsulated options, IA_NA and IA_TA.
  //
  *Option = Dhcp6SeekIaOption (
              Packet->Dhcp6.Option,
              OptionLen,
              &Instance->Config->IaDescriptor
              );
  if (*Option == NULL) {
    return EFI_SUCCESS;
  }

  //
  // Calculate the distance from Packet->Dhcp6.Option to the IA option.
  //
  // Packet->Size and Packet->Length are both UINT32 type, and Packet->Size is
  // the size of the whole packet, including the DHCP header, and Packet->Length
  // is the length of the DHCP message body, excluding the DHCP header.
  //
  // (*Option - Packet->Dhcp6.Option) is the number of bytes from the start of
  // DHCP6 option area to the start of the IA option.
  //
  // Dhcp6SeekInnerOptionSafe() is searching starting from the start of the
  // IA option to the end of the DHCP6 option area, thus subtract the space
  // up until this option
  //
  OptionLen = OptionLen - (UINT32)(*Option - Packet->Dhcp6.Option);

  //
  // Seek the inner option
  //
  if (EFI_ERROR (
        Dhcp6SeekInnerOptionSafe (
          Instance->Config->IaDescriptor.Type,
          *Option,
          OptionLen,
          &IaInnerOpt,
          &IaInnerLen
          )
        ))
  {
    return EFI_DEVICE_ERROR;
  }

  //
  // The format of the Status Code 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
  //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //  |       OPTION_STATUS_CODE      |         option-len            |
  //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //  |          status-code          |                               |
  //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
  //  .                                                               .
  //  .                        status-message                         .
  //  .                                                               .
  //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //

  //
  // sizeof (option-code + option-len) = 4
  //
  *Option = Dhcp6SeekOption (IaInnerOpt, IaInnerLen, Dhcp6OptStatusCode);
  if (*Option != NULL) {
    StsCode = NTOHS (ReadUnaligned16 ((UINT16 *)((DHCP6_OFFSET_OF_STATUS_CODE (*Option)))));
    if (StsCode != Dhcp6StsSuccess) {
      return EFI_DEVICE_ERROR;
    }
  }

  return EFI_SUCCESS;
}

/**
  Transmit Dhcp6 message by udpio.

  @param[in]  Instance        The pointer to the Dhcp6 instance.
  @param[in]  Packet          The pointer to transmit message.
  @param[in]  Elapsed         The pointer to the elapsed time value to fill in.

  @retval EFI_SUCCESS           Successfully transmitted the packet.
  @retval EFI_OUT_OF_RESOURCES  Required system resources could not be allocated.
  @retval Others                Failed to transmit the packet.

**/
EFI_STATUS
Dhcp6TransmitPacket (
  IN DHCP6_INSTANCE    *Instance,
  IN EFI_DHCP6_PACKET  *Packet,
  IN UINT16            *Elapsed
  )
{
  EFI_STATUS     Status;
  NET_BUF        *Wrap;
  NET_FRAGMENT   Frag;
  UDP_END_POINT  EndPt;
  DHCP6_SERVICE  *Service;

  Service = Instance->Service;

  //
  // Wrap it into a netbuf then send it.
  //
  Frag.Bulk = (UINT8 *)&Packet->Dhcp6.Header;
  Frag.Len  = Packet->Length;

  //
  // Do not register free packet here, which will be handled in retry list.
  //
  Wrap = NetbufFromExt (&Frag, 1, 0, 0, Dhcp6DummyExtFree, NULL);

  if (Wrap == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Multicast the Dhcp6 message, unless get the unicast server address by option.
  //
  ZeroMem (&EndPt, sizeof (UDP_END_POINT));

  if (Instance->Unicast != NULL) {
    CopyMem (
      &EndPt.RemoteAddr,
      Instance->Unicast,
      sizeof (EFI_IPv6_ADDRESS)
      );
  } else {
    CopyMem (
      &EndPt.RemoteAddr,
      &mAllDhcpRelayAndServersAddress,
      sizeof (EFI_IPv6_ADDRESS)
      );
  }

  EndPt.RemotePort = DHCP6_PORT_SERVER;
  EndPt.LocalPort  = DHCP6_PORT_CLIENT;

  //
  // Update the elapsed time value.
  //
  if (Elapsed != NULL) {
    SetElapsedTime (Elapsed, Instance);
  }

  //
  // Send out the message by the configured Udp6Io.
  //
  Status = UdpIoSendDatagram (
             Service->UdpIo,
             Wrap,
             &EndPt,
             NULL,
             Dhcp6OnTransmitted,
             NULL
             );

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

  return EFI_SUCCESS;
}

/**
  Create the solicit message and send it.

  @param[in]  Instance        The pointer to the Dhcp6 instance.

  @retval EFI_SUCCESS           Created and sent the solicit message successfully.
  @retval EFI_OUT_OF_RESOURCES  Required system resources could not be allocated.
  @retval Others                Failed to send the solicit message.

**/
EFI_STATUS
Dhcp6SendSolicitMsg   (
  IN DHCP6_INSTANCE  *Instance
  )
{
  EFI_STATUS               Status;
  EFI_DHCP6_PACKET         *Packet;
  EFI_DHCP6_PACKET_OPTION  *UserOpt;
  EFI_DHCP6_DUID           *ClientId;
  DHCP6_SERVICE            *Service;
  UINT8                    *Cursor;
  UINT16                   *Elapsed;
  UINT32                   UserLen;
  UINTN                    Index;
  UINT16                   Length;

  Service  = Instance->Service;
  ClientId = Service->ClientId;
  UserLen  = 0;

  ASSERT (Service->ClientId != NULL);
  ASSERT (Instance->Config != NULL);
  ASSERT (Instance->IaCb.Ia != NULL);

  //
  // Calculate the added length of customized option list.
  //
  for (Index = 0; Index < Instance->Config->OptionCount; Index++) {
    UserLen += (NTOHS (Instance->Config->OptionList[Index]->OpLen) + 4);
  }

  //
  // Create the Dhcp6 packet and initialize common fields.
  //
  Packet = AllocateZeroPool (DHCP6_BASE_PACKET_SIZE + UserLen);
  if (Packet == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_ERROR;
  }

  Packet->Size                       = DHCP6_BASE_PACKET_SIZE + UserLen;
  Packet->Length                     = sizeof (EFI_DHCP6_HEADER);
  Packet->Dhcp6.Header.MessageType   = Dhcp6MsgSolicit;
  Packet->Dhcp6.Header.TransactionId = Service->Xid++;

  //
  // Assembly Dhcp6 options for solicit message.
  //
  Cursor = Packet->Dhcp6.Option;

  Length = HTONS (ClientId->Length);
  Status = Dhcp6AppendOption (
             Packet,
             &Cursor,
             HTONS (Dhcp6OptClientId),
             Length,
             ClientId->Duid
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = Dhcp6AppendETOption (
             Packet,
             &Cursor,
             Instance,
             &Elapsed
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = Dhcp6AppendIaOption (
             Packet,
             &Cursor,
             Instance->IaCb.Ia,
             Instance->IaCb.T1,
             Instance->IaCb.T2,
             Packet->Dhcp6.Header.MessageType
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Append user-defined when configurate Dhcp6 service.
  //
  for (Index = 0; Index < Instance->Config->OptionCount; Index++) {
    UserOpt = Instance->Config->OptionList[Index];
    Status  = Dhcp6AppendOption (
                Packet,
                &Cursor,
                UserOpt->OpCode,
                UserOpt->OpLen,
                UserOpt->Data
                );
    if (EFI_ERROR (Status)) {
      goto ON_ERROR;
    }
  }

  ASSERT (Packet->Size > Packet->Length + 8);

  //
  // Callback to user with the packet to be sent and check the user's feedback.
  //
  Status = Dhcp6CallbackUser (Instance, Dhcp6SendSolicit, &Packet);
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Send solicit packet with the state transition from Dhcp6init to
  // Dhcp6selecting.
  //
  Instance->IaCb.Ia->State = Dhcp6Selecting;
  //
  // Clear initial time for current transaction.
  //
  Instance->StartTime = 0;

  Status = Dhcp6TransmitPacket (Instance, Packet, Elapsed);
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Enqueue the sent packet for the retransmission in case reply timeout.
  //
  return Dhcp6EnqueueRetry (
           Instance,
           Packet,
           Elapsed,
           Instance->Config->SolicitRetransmission
           );

ON_ERROR:

  if (Packet) {
    FreePool (Packet);
  }

  return Status;
}

/**
  Configure some parameter to initiate SolicitMsg.

  @param[in]  Instance          The pointer to the Dhcp6 instance.

  @retval EFI_SUCCESS           Created and sent the solicit message successfully.
  @retval EFI_OUT_OF_RESOURCES  Required system resources could not be allocated.
  @retval Others                Failed to send the solicit message.

**/
EFI_STATUS
Dhcp6InitSolicitMsg   (
  IN DHCP6_INSTANCE  *Instance
  )
{
  Instance->IaCb.T1                 = 0;
  Instance->IaCb.T2                 = 0;
  Instance->IaCb.Ia->IaAddressCount = 0;

  return Dhcp6SendSolicitMsg (Instance);
}

/**
  Create the request message and send it.

  @param[in]  Instance        The pointer to the Dhcp6 instance.

  @retval EFI_SUCCESS           Created and sent the request message successfully.
  @retval EFI_OUT_OF_RESOURCES  Required system resources could not be allocated.
  @retval EFI_DEVICE_ERROR      An unexpected error.
  @retval Others                Failed to send the request message.

**/
EFI_STATUS
Dhcp6SendRequestMsg (
  IN DHCP6_INSTANCE  *Instance
  )
{
  EFI_STATUS               Status;
  EFI_DHCP6_PACKET         *Packet;
  EFI_DHCP6_PACKET_OPTION  *UserOpt;
  EFI_DHCP6_DUID           *ClientId;
  EFI_DHCP6_DUID           *ServerId;
  DHCP6_SERVICE            *Service;
  UINT8                    *Option;
  UINT8                    *Cursor;
  UINT16                   *Elapsed;
  UINT32                   UserLen;
  UINTN                    Index;
  UINT16                   Length;

  ASSERT (Instance->AdSelect != NULL);
  ASSERT (Instance->Config != NULL);
  ASSERT (Instance->IaCb.Ia != NULL);
  ASSERT (Instance->Service != NULL);

  Service  = Instance->Service;
  ClientId = Service->ClientId;

  ASSERT (ClientId != NULL);

  //
  // Get the server Id from the selected advertisement message.
  //
  Option = Dhcp6SeekOption (
             Instance->AdSelect->Dhcp6.Option,
             Instance->AdSelect->Length - sizeof (EFI_DHCP6_HEADER),
             Dhcp6OptServerId
             );
  if (Option == NULL) {
    return EFI_DEVICE_ERROR;
  }

  ServerId = (EFI_DHCP6_DUID *)(Option + 2);

  //
  // Calculate the added length of customized option list.
  //
  UserLen = 0;
  for (Index = 0; Index < Instance->Config->OptionCount; Index++) {
    UserLen += (NTOHS (Instance->Config->OptionList[Index]->OpLen) + 4);
  }

  //
  // Create the Dhcp6 packet and initialize common fields.
  //
  Packet = AllocateZeroPool (DHCP6_BASE_PACKET_SIZE + UserLen);
  if (Packet == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_ERROR;
  }

  Packet->Size                       = DHCP6_BASE_PACKET_SIZE + UserLen;
  Packet->Length                     = sizeof (EFI_DHCP6_HEADER);
  Packet->Dhcp6.Header.MessageType   = Dhcp6MsgRequest;
  Packet->Dhcp6.Header.TransactionId = Service->Xid++;

  //
  // Assembly Dhcp6 options for request message.
  //
  Cursor = Packet->Dhcp6.Option;

  Length = HTONS (ClientId->Length);
  Status = Dhcp6AppendOption (
             Packet,
             &Cursor,
             HTONS (Dhcp6OptClientId),
             Length,
             ClientId->Duid
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = Dhcp6AppendETOption (
             Packet,
             &Cursor,
             Instance,
             &Elapsed
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = Dhcp6AppendOption (
             Packet,
             &Cursor,
             HTONS (Dhcp6OptServerId),
             ServerId->Length,
             ServerId->Duid
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = Dhcp6AppendIaOption (
             Packet,
             &Cursor,
             Instance->IaCb.Ia,
             Instance->IaCb.T1,
             Instance->IaCb.T2,
             Packet->Dhcp6.Header.MessageType
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Append user-defined when configurate Dhcp6 service.
  //
  for (Index = 0; Index < Instance->Config->OptionCount; Index++) {
    UserOpt = Instance->Config->OptionList[Index];
    Status  = Dhcp6AppendOption (
                Packet,
                &Cursor,
                UserOpt->OpCode,
                UserOpt->OpLen,
                UserOpt->Data
                );
    if (EFI_ERROR (Status)) {
      goto ON_ERROR;
    }
  }

  ASSERT (Packet->Size > Packet->Length + 8);

  //
  // Callback to user with the packet to be sent and check the user's feedback.
  //
  Status = Dhcp6CallbackUser (Instance, Dhcp6SendRequest, &Packet);

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

  //
  // Send request packet with the state transition from Dhcp6selecting to
  // Dhcp6requesting.
  //
  Instance->IaCb.Ia->State = Dhcp6Requesting;
  //
  // Clear initial time for current transaction.
  //
  Instance->StartTime = 0;

  Status = Dhcp6TransmitPacket (Instance, Packet, Elapsed);

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

  //
  // Enqueue the sent packet for the retransmission in case reply timeout.
  //
  return Dhcp6EnqueueRetry (Instance, Packet, Elapsed, NULL);

ON_ERROR:

  if (Packet) {
    FreePool (Packet);
  }

  return Status;
}

/**
  Create the decline message and send it.

  @param[in]  Instance        The pointer to the Dhcp6 instance.
  @param[in]  DecIa           The pointer to the decline Ia.

  @retval EFI_SUCCESS           Created and sent the decline message successfully.
  @retval EFI_OUT_OF_RESOURCES  Required system resources could not be allocated.
  @retval EFI_DEVICE_ERROR      An unexpected error.
  @retval Others                Failed to send the decline message.

**/
EFI_STATUS
Dhcp6SendDeclineMsg (
  IN DHCP6_INSTANCE  *Instance,
  IN EFI_DHCP6_IA    *DecIa
  )
{
  EFI_STATUS        Status;
  EFI_DHCP6_PACKET  *Packet;
  EFI_DHCP6_PACKET  *LastReply;
  EFI_DHCP6_DUID    *ClientId;
  EFI_DHCP6_DUID    *ServerId;
  DHCP6_SERVICE     *Service;
  UINT8             *Option;
  UINT8             *Cursor;
  UINT16            *Elapsed;
  UINT16            Length;

  ASSERT (Instance->Config != NULL);
  ASSERT (Instance->IaCb.Ia != NULL);
  ASSERT (Instance->Service != NULL);

  Service   = Instance->Service;
  ClientId  = Service->ClientId;
  LastReply = Instance->IaCb.Ia->ReplyPacket;

  ASSERT (ClientId != NULL);
  ASSERT (LastReply != NULL);

  //
  // Get the server Id from the last reply message.
  //
  Option = Dhcp6SeekOption (
             LastReply->Dhcp6.Option,
             LastReply->Length - sizeof (EFI_DHCP6_HEADER),
             Dhcp6OptServerId
             );
  if (Option == NULL) {
    return EFI_DEVICE_ERROR;
  }

  //
  // EFI_DHCP6_DUID contains a length field of 2 bytes.
  //
  ServerId = (EFI_DHCP6_DUID *)(Option + 2);

  //
  // Create the Dhcp6 packet and initialize common fields.
  //
  Packet = AllocateZeroPool (DHCP6_BASE_PACKET_SIZE);
  if (Packet == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_ERROR;
  }

  Packet->Size                       = DHCP6_BASE_PACKET_SIZE;
  Packet->Length                     = sizeof (EFI_DHCP6_HEADER);
  Packet->Dhcp6.Header.MessageType   = Dhcp6MsgDecline;
  Packet->Dhcp6.Header.TransactionId = Service->Xid++;

  //
  // Assembly Dhcp6 options for rebind/renew message.
  //
  Cursor = Packet->Dhcp6.Option;

  Length = HTONS (ClientId->Length);
  Status = Dhcp6AppendOption (
             Packet,
             &Cursor,
             HTONS (Dhcp6OptClientId),
             Length,
             ClientId->Duid
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = Dhcp6AppendETOption (
             Packet,
             &Cursor,
             Instance,
             &Elapsed
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = Dhcp6AppendOption (
             Packet,
             &Cursor,
             HTONS (Dhcp6OptServerId),
             ServerId->Length,
             ServerId->Duid
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = Dhcp6AppendIaOption (
             Packet,
             &Cursor,
             DecIa,
             0,
             0,
             Packet->Dhcp6.Header.MessageType
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  ASSERT (Packet->Size > Packet->Length + 8);

  //
  // Callback to user with the packet to be sent and check the user's feedback.
  //
  Status = Dhcp6CallbackUser (Instance, Dhcp6SendDecline, &Packet);
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Send decline packet with the state transition from Dhcp6bound to
  // Dhcp6declining.
  //
  Instance->IaCb.Ia->State = Dhcp6Declining;
  //
  // Clear initial time for current transaction.
  //
  Instance->StartTime = 0;

  Status = Dhcp6TransmitPacket (Instance, Packet, Elapsed);
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Enqueue the sent packet for the retransmission in case reply timeout.
  //
  return Dhcp6EnqueueRetry (Instance, Packet, Elapsed, NULL);

ON_ERROR:

  if (Packet) {
    FreePool (Packet);
  }

  return Status;
}

/**
  Create the release message and send it.

  @param[in]  Instance        The pointer to the Dhcp6 instance.
  @param[in]  RelIa           The pointer to the release Ia.

  @retval EFI_SUCCESS           Created and sent the release message successfully.
  @retval EFI_OUT_OF_RESOURCES  Required system resources could not be allocated.
  @retval EFI_DEVICE_ERROR      An unexpected error.
  @retval Others                Failed to send the release message.

**/
EFI_STATUS
Dhcp6SendReleaseMsg (
  IN DHCP6_INSTANCE  *Instance,
  IN EFI_DHCP6_IA    *RelIa
  )
{
  EFI_STATUS        Status;
  EFI_DHCP6_PACKET  *Packet;
  EFI_DHCP6_PACKET  *LastReply;
  EFI_DHCP6_DUID    *ClientId;
  EFI_DHCP6_DUID    *ServerId;
  DHCP6_SERVICE     *Service;
  UINT8             *Option;
  UINT8             *Cursor;
  UINT16            *Elapsed;
  UINT16            Length;

  ASSERT (Instance->Config);
  ASSERT (Instance->IaCb.Ia);

  Service   = Instance->Service;
  ClientId  = Service->ClientId;
  LastReply = Instance->IaCb.Ia->ReplyPacket;

  ASSERT (ClientId);
  ASSERT (LastReply);

  //
  // Get the server Id from the last reply message.
  //
  Option = Dhcp6SeekOption (
             LastReply->Dhcp6.Option,
             LastReply->Length - sizeof (EFI_DHCP6_HEADER),
             Dhcp6OptServerId
             );
  if (Option == NULL) {
    return EFI_DEVICE_ERROR;
  }

  ServerId = (EFI_DHCP6_DUID *)(Option + 2);

  //
  // Create the Dhcp6 packet and initialize common fields.
  //
  Packet = AllocateZeroPool (DHCP6_BASE_PACKET_SIZE);
  if (Packet == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_ERROR;
  }

  Packet->Size                       = DHCP6_BASE_PACKET_SIZE;
  Packet->Length                     = sizeof (EFI_DHCP6_HEADER);
  Packet->Dhcp6.Header.MessageType   = Dhcp6MsgRelease;
  Packet->Dhcp6.Header.TransactionId = Service->Xid++;

  //
  // Assembly Dhcp6 options for rebind/renew message
  //
  Cursor = Packet->Dhcp6.Option;

  Length = HTONS (ClientId->Length);
  Status = Dhcp6AppendOption (
             Packet,
             &Cursor,
             HTONS (Dhcp6OptClientId),
             Length,
             ClientId->Duid
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // ServerId is extracted from packet, it's network order.
  //
  Status = Dhcp6AppendOption (
             Packet,
             &Cursor,
             HTONS (Dhcp6OptServerId),
             ServerId->Length,
             ServerId->Duid
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = Dhcp6AppendETOption (
             Packet,
             &Cursor,
             Instance,
             &Elapsed
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = Dhcp6AppendIaOption (
             Packet,
             &Cursor,
             RelIa,
             0,
             0,
             Packet->Dhcp6.Header.MessageType
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  ASSERT (Packet->Size > Packet->Length + 8);

  //
  // Callback to user with the packet to be sent and check the user's feedback.
  //
  Status = Dhcp6CallbackUser (Instance, Dhcp6SendRelease, &Packet);
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Send release packet with the state transition from Dhcp6bound to
  // Dhcp6releasing.
  //
  Instance->IaCb.Ia->State = Dhcp6Releasing;

  Status = Dhcp6TransmitPacket (Instance, Packet, Elapsed);
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Enqueue the sent packet for the retransmission in case reply timeout.
  //
  return Dhcp6EnqueueRetry (Instance, Packet, Elapsed, NULL);

ON_ERROR:

  if (Packet) {
    FreePool (Packet);
  }

  return Status;
}

/**
  Create the renew/rebind message and send it.

  @param[in]  Instance        The pointer to the Dhcp6 instance.
  @param[in]  RebindRequest   If TRUE, it is a Rebind type message.
                              Otherwise, it is a Renew type message.

  @retval EFI_SUCCESS           Created and sent the renew/rebind message successfully.
  @retval EFI_OUT_OF_RESOURCES  Required system resources could not be allocated.
  @retval EFI_DEVICE_ERROR      An unexpected error.
  @retval Others                Failed to send the renew/rebind message.

**/
EFI_STATUS
Dhcp6SendRenewRebindMsg (
  IN DHCP6_INSTANCE  *Instance,
  IN BOOLEAN         RebindRequest
  )
{
  EFI_STATUS               Status;
  EFI_DHCP6_PACKET         *Packet;
  EFI_DHCP6_PACKET         *LastReply;
  EFI_DHCP6_PACKET_OPTION  *UserOpt;
  EFI_DHCP6_DUID           *ClientId;
  EFI_DHCP6_DUID           *ServerId;
  EFI_DHCP6_STATE          State;
  EFI_DHCP6_EVENT          Event;
  DHCP6_SERVICE            *Service;
  UINT8                    *Option;
  UINT8                    *Cursor;
  UINT16                   *Elapsed;
  UINT32                   UserLen;
  UINTN                    Index;
  UINT16                   Length;

  ASSERT (Instance->Config);
  ASSERT (Instance->IaCb.Ia);

  Service  = Instance->Service;
  ClientId = Service->ClientId;

  ASSERT (ClientId);

  //
  // Calculate the added length of customized option list.
  //
  UserLen = 0;
  for (Index = 0; Index < Instance->Config->OptionCount; Index++) {
    UserLen += (NTOHS (Instance->Config->OptionList[Index]->OpLen) + 4);
  }

  //
  // Create the Dhcp6 packet and initialize common fields.
  //
  Packet = AllocateZeroPool (DHCP6_BASE_PACKET_SIZE + UserLen);
  if (Packet == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_ERROR;
  }

  Packet->Size                       = DHCP6_BASE_PACKET_SIZE + UserLen;
  Packet->Length                     = sizeof (EFI_DHCP6_HEADER);
  Packet->Dhcp6.Header.MessageType   = RebindRequest ? Dhcp6MsgRebind : Dhcp6MsgRenew;
  Packet->Dhcp6.Header.TransactionId = Service->Xid++;

  //
  // Assembly Dhcp6 options for rebind/renew message.
  //
  Cursor = Packet->Dhcp6.Option;

  Length = HTONS (ClientId->Length);
  Status = Dhcp6AppendOption (
             Packet,
             &Cursor,
             HTONS (Dhcp6OptClientId),
             Length,
             ClientId->Duid
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = Dhcp6AppendETOption (
             Packet,
             &Cursor,
             Instance,
             &Elapsed
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = Dhcp6AppendIaOption (
             Packet,
             &Cursor,
             Instance->IaCb.Ia,
             Instance->IaCb.T1,
             Instance->IaCb.T2,
             Packet->Dhcp6.Header.MessageType
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  if (!RebindRequest) {
    //
    // Get the server Id from the last reply message and
    // insert it for rebind request.
    //
    LastReply = Instance->IaCb.Ia->ReplyPacket;
    ASSERT (LastReply);

    Option = Dhcp6SeekOption (
               LastReply->Dhcp6.Option,
               LastReply->Length - sizeof (EFI_DHCP6_HEADER),
               Dhcp6OptServerId
               );
    if (Option == NULL) {
      Status = EFI_DEVICE_ERROR;
      goto ON_ERROR;
    }

    ServerId = (EFI_DHCP6_DUID *)(Option + 2);

    Status = Dhcp6AppendOption (
               Packet,
               &Cursor,
               HTONS (Dhcp6OptServerId),
               ServerId->Length,
               ServerId->Duid
               );
    if (EFI_ERROR (Status)) {
      goto ON_ERROR;
    }
  }

  //
  // Append user-defined when configurate Dhcp6 service.
  //
  for (Index = 0; Index < Instance->Config->OptionCount; Index++) {
    UserOpt = Instance->Config->OptionList[Index];
    Status  = Dhcp6AppendOption (
                Packet,
                &Cursor,
                UserOpt->OpCode,
                UserOpt->OpLen,
                UserOpt->Data
                );
    if (EFI_ERROR (Status)) {
      goto ON_ERROR;
    }
  }

  ASSERT (Packet->Size > Packet->Length + 8);

  //
  // Callback to user with the packet to be sent and check the user's feedback.
  //
  State = (RebindRequest) ? Dhcp6Rebinding : Dhcp6Renewing;
  Event = (RebindRequest) ? Dhcp6EnterRebinding : Dhcp6EnterRenewing;

  Status = Dhcp6CallbackUser (Instance, Event, &Packet);
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Send renew/rebind packet with the state transition from Dhcp6bound to
  // Dhcp6renew/rebind.
  // And sync the lease time when send renew/rebind, in case that user send
  // renew/rebind actively.
  //
  Instance->IaCb.Ia->State = State;
  Instance->IaCb.LeaseTime = (RebindRequest) ? Instance->IaCb.T2 : Instance->IaCb.T1;
  //
  // Clear initial time for current transaction.
  //
  Instance->StartTime = 0;

  Status = Dhcp6TransmitPacket (Instance, Packet, Elapsed);
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Enqueue the sent packet for the retransmission in case reply timeout.
  //
  return Dhcp6EnqueueRetry (Instance, Packet, Elapsed, NULL);

ON_ERROR:

  if (Packet) {
    FreePool (Packet);
  }

  return Status;
}

/**
  Start the information request process.

  @param[in]  Instance          The pointer to the Dhcp6 instance.
  @param[in]  SendClientId      If TRUE, the client identifier option will be included in
                                information request message. Otherwise, the client identifier
                                option will not be included.
  @param[in]  OptionRequest     The pointer to the option request option.
  @param[in]  OptionCount       The number options in the OptionList.
  @param[in]  OptionList        The array pointers to the appended options.
  @param[in]  Retransmission    The pointer to the retransmission control.
  @param[in]  TimeoutEvent      The event of timeout.
  @param[in]  ReplyCallback     The callback function when the reply was received.
  @param[in]  CallbackContext   The pointer to the parameter passed to the callback.

  @retval EFI_SUCCESS           Start the info-request process successfully.
  @retval EFI_OUT_OF_RESOURCES  Required system resources could not be allocated.
  @retval EFI_NO_MAPPING        No source address is available for use.
  @retval Others                Failed to start the info-request process.

**/
EFI_STATUS
Dhcp6StartInfoRequest (
  IN DHCP6_INSTANCE            *Instance,
  IN BOOLEAN                   SendClientId,
  IN EFI_DHCP6_PACKET_OPTION   *OptionRequest,
  IN UINT32                    OptionCount,
  IN EFI_DHCP6_PACKET_OPTION   *OptionList[]    OPTIONAL,
  IN EFI_DHCP6_RETRANSMISSION  *Retransmission,
  IN EFI_EVENT                 TimeoutEvent     OPTIONAL,
  IN EFI_DHCP6_INFO_CALLBACK   ReplyCallback,
  IN VOID                      *CallbackContext OPTIONAL
  )
{
  EFI_STATUS     Status;
  DHCP6_INF_CB   *InfCb;
  DHCP6_SERVICE  *Service;
  EFI_TPL        OldTpl;

  Service = Instance->Service;

  OldTpl           = gBS->RaiseTPL (TPL_CALLBACK);
  Instance->UdpSts = EFI_ALREADY_STARTED;
  //
  // Create and initialize the control block for the info-request.
  //
  InfCb = AllocateZeroPool (sizeof (DHCP6_INF_CB));

  if (InfCb == NULL) {
    gBS->RestoreTPL (OldTpl);
    return EFI_OUT_OF_RESOURCES;
  }

  InfCb->ReplyCallback   = ReplyCallback;
  InfCb->CallbackContext = CallbackContext;
  InfCb->TimeoutEvent    = TimeoutEvent;

  InsertTailList (&Instance->InfList, &InfCb->Link);

  //
  // Send the info-request message to start exchange process.
  //
  Status = Dhcp6SendInfoRequestMsg (
             Instance,
             InfCb,
             SendClientId,
             OptionRequest,
             OptionCount,
             OptionList,
             Retransmission
             );

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

  //
  // Register receive callback for the stateless exchange process.
  //
  Status = UdpIoRecvDatagram (
             Service->UdpIo,
             Dhcp6ReceivePacket,
             Service,
             0
             );

  if (EFI_ERROR (Status) && (Status != EFI_ALREADY_STARTED)) {
    goto ON_ERROR;
  }

  gBS->RestoreTPL (OldTpl);
  return EFI_SUCCESS;

ON_ERROR:
  gBS->RestoreTPL (OldTpl);
  RemoveEntryList (&InfCb->Link);
  FreePool (InfCb);

  return Status;
}

/**
  Create the information request message and send it.

  @param[in]  Instance        The pointer to the Dhcp6 instance.
  @param[in]  InfCb           The pointer to the information request control block.
  @param[in]  SendClientId    If TRUE, the client identifier option will be included in
                              information request message. Otherwise, the client identifier
                              option will not be included.
  @param[in]  OptionRequest   The pointer to the option request option.
  @param[in]  OptionCount     The number options in the OptionList.
  @param[in]  OptionList      The array pointers to the appended options.
  @param[in]  Retransmission  The pointer to the retransmission control.

  @retval EFI_SUCCESS           Created and sent the info-request message successfully.
  @retval EFI_OUT_OF_RESOURCES  Required system resources could not be allocated.
  @retval Others                Failed to send the info-request message.

**/
EFI_STATUS
Dhcp6SendInfoRequestMsg (
  IN DHCP6_INSTANCE            *Instance,
  IN DHCP6_INF_CB              *InfCb,
  IN BOOLEAN                   SendClientId,
  IN EFI_DHCP6_PACKET_OPTION   *OptionRequest,
  IN UINT32                    OptionCount,
  IN EFI_DHCP6_PACKET_OPTION   *OptionList[],
  IN EFI_DHCP6_RETRANSMISSION  *Retransmission
  )
{
  EFI_STATUS               Status;
  EFI_DHCP6_PACKET         *Packet;
  EFI_DHCP6_PACKET_OPTION  *UserOpt;
  EFI_DHCP6_DUID           *ClientId;
  DHCP6_SERVICE            *Service;
  UINT8                    *Cursor;
  UINT16                   *Elapsed;
  UINT32                   UserLen;
  UINTN                    Index;
  UINT16                   Length;

  ASSERT (OptionRequest);

  Service  = Instance->Service;
  ClientId = Service->ClientId;
  UserLen  = NTOHS (OptionRequest->OpLen) + 4;

  ASSERT (ClientId);

  //
  // Calculate the added length of customized option list.
  //
  for (Index = 0; Index < OptionCount; Index++) {
    UserLen += (NTOHS (OptionList[Index]->OpLen) + 4);
  }

  //
  // Create the Dhcp6 packet and initialize common fields.
  //
  Packet = AllocateZeroPool (DHCP6_BASE_PACKET_SIZE + UserLen);
  if (Packet == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_ERROR;
  }

  Packet->Size                       = DHCP6_BASE_PACKET_SIZE + UserLen;
  Packet->Length                     = sizeof (EFI_DHCP6_HEADER);
  Packet->Dhcp6.Header.MessageType   = Dhcp6MsgInfoRequest;
  Packet->Dhcp6.Header.TransactionId = Service->Xid++;

  InfCb->Xid = Packet->Dhcp6.Header.TransactionId;

  //
  // Assembly Dhcp6 options for info-request message.
  //
  Cursor = Packet->Dhcp6.Option;

  if (SendClientId) {
    Length = HTONS (ClientId->Length);
    Status = Dhcp6AppendOption (
               Packet,
               &Cursor,
               HTONS (Dhcp6OptClientId),
               Length,
               ClientId->Duid
               );
    if (EFI_ERROR (Status)) {
      goto ON_ERROR;
    }
  }

  Status = Dhcp6AppendETOption (
             Packet,
             &Cursor,
             Instance,
             &Elapsed
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = Dhcp6AppendOption (
             Packet,
             &Cursor,
             OptionRequest->OpCode,
             OptionRequest->OpLen,
             OptionRequest->Data
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Append user-defined when configurate Dhcp6 service.
  //
  for (Index = 0; Index < OptionCount; Index++) {
    UserOpt = OptionList[Index];
    Status  = Dhcp6AppendOption (
                Packet,
                &Cursor,
                UserOpt->OpCode,
                UserOpt->OpLen,
                UserOpt->Data
                );
    if (EFI_ERROR (Status)) {
      goto ON_ERROR;
    }
  }

  ASSERT (Packet->Size > Packet->Length + 8);

  //
  // Clear initial time for current transaction.
  //
  Instance->StartTime = 0;

  //
  // Send info-request packet with no state.
  //
  Status = Dhcp6TransmitPacket (Instance, Packet, Elapsed);
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Enqueue the sent packet for the retransmission in case reply timeout.
  //
  return Dhcp6EnqueueRetry (Instance, Packet, Elapsed, Retransmission);

ON_ERROR:

  if (Packet) {
    FreePool (Packet);
  }

  return Status;
}

/**
  Create the Confirm message and send it.

  @param[in]  Instance          The pointer to the Dhcp6 instance.

  @retval EFI_SUCCESS           Created and sent the confirm message successfully.
  @retval EFI_OUT_OF_RESOURCES  Required system resources could not be allocated.
  @retval EFI_DEVICE_ERROR      An unexpected error.
  @retval Others                Failed to send the confirm message.

**/
EFI_STATUS
Dhcp6SendConfirmMsg (
  IN DHCP6_INSTANCE  *Instance
  )
{
  UINT8                    *Cursor;
  UINTN                    Index;
  UINT16                   Length;
  UINT32                   UserLen;
  EFI_STATUS               Status;
  DHCP6_SERVICE            *Service;
  EFI_DHCP6_DUID           *ClientId;
  EFI_DHCP6_PACKET         *Packet;
  EFI_DHCP6_PACKET_OPTION  *UserOpt;
  UINT16                   *Elapsed;

  ASSERT (Instance->Config != NULL);
  ASSERT (Instance->IaCb.Ia != NULL);
  ASSERT (Instance->Service != NULL);

  Service  = Instance->Service;
  ClientId = Service->ClientId;
  ASSERT (ClientId != NULL);

  //
  // Calculate the added length of customized option list.
  //
  UserLen = 0;
  for (Index = 0; Index < Instance->Config->OptionCount; Index++) {
    UserLen += (NTOHS (Instance->Config->OptionList[Index]->OpLen) + 4);
  }

  //
  // Create the Dhcp6 packet and initialize common fields.
  //
  Packet = AllocateZeroPool (DHCP6_BASE_PACKET_SIZE + UserLen);
  if (Packet == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_ERROR;
  }

  Packet->Size                       = DHCP6_BASE_PACKET_SIZE + UserLen;
  Packet->Length                     = sizeof (EFI_DHCP6_HEADER);
  Packet->Dhcp6.Header.MessageType   = Dhcp6MsgConfirm;
  Packet->Dhcp6.Header.TransactionId = Service->Xid++;

  //
  // Assembly Dhcp6 options for solicit message.
  //
  Cursor = Packet->Dhcp6.Option;

  Length = HTONS (ClientId->Length);
  Status = Dhcp6AppendOption (
             Packet,
             &Cursor,
             HTONS (Dhcp6OptClientId),
             Length,
             ClientId->Duid
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = Dhcp6AppendETOption (
             Packet,
             &Cursor,
             Instance,
             &Elapsed
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  Status = Dhcp6AppendIaOption (
             Packet,
             &Cursor,
             Instance->IaCb.Ia,
             Instance->IaCb.T1,
             Instance->IaCb.T2,
             Packet->Dhcp6.Header.MessageType
             );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Append user-defined when configurate Dhcp6 service.
  //
  for (Index = 0; Index < Instance->Config->OptionCount; Index++) {
    UserOpt = Instance->Config->OptionList[Index];
    Status  = Dhcp6AppendOption (
                Packet,
                &Cursor,
                UserOpt->OpCode,
                UserOpt->OpLen,
                UserOpt->Data
                );
    if (EFI_ERROR (Status)) {
      goto ON_ERROR;
    }
  }

  ASSERT (Packet->Size > Packet->Length + 8);

  //
  // Callback to user with the packet to be sent and check the user's feedback.
  //
  Status = Dhcp6CallbackUser (Instance, Dhcp6SendConfirm, &Packet);
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Send confirm packet with the state transition from Dhcp6Bound to
  // Dhcp6Confirming.
  //
  Instance->IaCb.Ia->State = Dhcp6Confirming;
  //
  // Clear initial time for current transaction.
  //
  Instance->StartTime = 0;

  Status = Dhcp6TransmitPacket (Instance, Packet, Elapsed);
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Enqueue the sent packet for the retransmission in case reply timeout.
  //
  return Dhcp6EnqueueRetry (Instance, Packet, Elapsed, NULL);

ON_ERROR:

  if (Packet) {
    FreePool (Packet);
  }

  return Status;
}

/**
  Handle with the Dhcp6 reply message.

  @param[in]  Instance        The pointer to Dhcp6 instance.
  @param[in]  Packet          The pointer to the Dhcp6 reply message.

  @retval EFI_SUCCESS           Processed the reply message successfully.
  @retval EFI_OUT_OF_RESOURCES  Required system resources could not be allocated.
  @retval EFI_DEVICE_ERROR      An unexpected error.
  @retval Others                Failed to process the reply message.

**/
EFI_STATUS
Dhcp6HandleReplyMsg (
  IN DHCP6_INSTANCE    *Instance,
  IN EFI_DHCP6_PACKET  *Packet
  )
{
  EFI_STATUS  Status;
  UINT8       *Option;
  UINT16      StsCode;

  ASSERT (Instance->Config != NULL);
  ASSERT (Instance->IaCb.Ia != NULL);
  ASSERT (Packet != NULL);

  Status = EFI_SUCCESS;

  if (Packet->Dhcp6.Header.MessageType != Dhcp6MsgReply) {
    return EFI_DEVICE_ERROR;
  }

  //
  // If the client subsequently receives a valid reply message that includes a
  // rapid commit option since send a solicit with rapid commit option before,
  // preocess the reply message and discard any reply messages received in
  // response to the request message.
  // See details in the section-17.1.4 of rfc-3315.
  //
  Option = Dhcp6SeekOption (
             Packet->Dhcp6.Option,
             Packet->Length - sizeof (EFI_DHCP6_HEADER),
             Dhcp6OptRapidCommit
             );

  if (((Option != NULL) && !Instance->Config->RapidCommit) || ((Option == NULL) && Instance->Config->RapidCommit)) {
    return EFI_DEVICE_ERROR;
  }

  //
  // As to a valid reply packet in response to a request/renew/rebind packet,
  // ignore the packet if not contains the Ia option
  //
  if ((Instance->IaCb.Ia->State == Dhcp6Requesting) ||
      (Instance->IaCb.Ia->State == Dhcp6Renewing) ||
      (Instance->IaCb.Ia->State == Dhcp6Rebinding)
      )
  {
    Option = Dhcp6SeekIaOption (
               Packet->Dhcp6.Option,
               Packet->Length,
               &Instance->Config->IaDescriptor
               );
    if (Option == NULL) {
      return EFI_SUCCESS;
    }
  }

  //
  // Callback to user with the received packet and check the user's feedback.
  //
  Status = Dhcp6CallbackUser (Instance, Dhcp6RcvdReply, &Packet);

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

  //
  // When receive a valid reply packet in response to a decline/release packet,
  // the client considers the decline/release event completed regardless of the
  // status code.
  //
  if ((Instance->IaCb.Ia->State == Dhcp6Declining) || (Instance->IaCb.Ia->State == Dhcp6Releasing)) {
    if (Instance->IaCb.Ia->IaAddressCount != 0) {
      Instance->IaCb.Ia->State = Dhcp6Bound;
    } else {
      ASSERT (Instance->IaCb.Ia->ReplyPacket);
      FreePool (Instance->IaCb.Ia->ReplyPacket);
      Instance->IaCb.Ia->ReplyPacket = NULL;
      Instance->IaCb.Ia->State       = Dhcp6Init;
    }

    //
    // For sync, set the success flag out of polling in decline/release.
    //
    Instance->UdpSts = EFI_SUCCESS;

    //
    // For async, signal the Ia event to inform Ia information update.
    //
    if (Instance->Config->IaInfoEvent != NULL) {
      gBS->SignalEvent (Instance->Config->IaInfoEvent);
    }

    //
    // Reset start time for next exchange.
    //
    Instance->StartTime = 0;

    Status = EFI_SUCCESS;
    goto ON_EXIT;
  }

  //
  // Upon the receipt of a valid reply packet in response to a solicit, request,
  // confirm, renew and rebind, the behavior depends on the status code option.
  // See the details in the section-18.1.8 of rfc-3315.
  //
  Option = NULL;
  Status = Dhcp6SeekStsOption (
             Instance,
             Packet,
             &Option
             );

  if (!EFI_ERROR (Status)) {
    //
    // No status code or no error status code means succeed to reply.
    //
    Status = Dhcp6UpdateIaInfo (Instance, Packet);
    if (!EFI_ERROR (Status)) {
      //
      // Reset start time for next exchange.
      //
      Instance->StartTime = 0;

      //
      // Set bound state and store the reply packet.
      //
      if (Instance->IaCb.Ia->ReplyPacket != NULL) {
        FreePool (Instance->IaCb.Ia->ReplyPacket);
      }

      Instance->IaCb.Ia->ReplyPacket = AllocateZeroPool (Packet->Size);

      if (Instance->IaCb.Ia->ReplyPacket == NULL) {
        Status = EFI_OUT_OF_RESOURCES;
        goto ON_EXIT;
      }

      CopyMem (Instance->IaCb.Ia->ReplyPacket, Packet, Packet->Size);

      Instance->IaCb.Ia->State = Dhcp6Bound;

      //
      // For sync, set the success flag out of polling in start/renewrebind.
      //
      Instance->UdpSts = EFI_SUCCESS;

      //
      // Maybe this is a new round DHCP process due to some reason, such as NotOnLink
      // ReplyMsg for ConfirmMsg should trigger new round to acquire new address. In that
      // case, clear old address.ValidLifetime and append to new address. Therefore, DHCP
      // consumers can be notified to flush old address.
      //
      Dhcp6AppendCacheIa (Instance);

      //
      // For async, signal the Ia event to inform Ia information update.
      //
      if (Instance->Config->IaInfoEvent != NULL) {
        gBS->SignalEvent (Instance->Config->IaInfoEvent);
      }
    } else if (Status == EFI_NOT_FOUND) {
      //
      // Refer to RFC3315 Chapter 18.1.8, for each IA in the original Renew or Rebind message,
      // the client sends a Renew or Rebind if the IA is not in the Reply message.
      // Return EFI_SUCCESS so we can continue to restart the Renew/Rebind process.
      //
      return EFI_SUCCESS;
    }

    goto ON_EXIT;
  } else if (Option != NULL) {
    //
    // Any error status code option is found.
    //
    StsCode = NTOHS (ReadUnaligned16 ((UINT16 *)((DHCP6_OFFSET_OF_STATUS_CODE (Option)))));
    switch (StsCode) {
      case Dhcp6StsUnspecFail:
        //
        // It indicates the server is unable to process the message due to an
        // unspecified failure condition, so just retry if possible.
        //
        break;

      case Dhcp6StsUseMulticast:
        //
        // It indicates the server receives a message via unicast from a client
        // to which the server has not sent a unicast option, so retry it by
        // multi-cast address.
        //
        if (Instance->Unicast != NULL) {
          FreePool (Instance->Unicast);
          Instance->Unicast = NULL;
        }

        break;

      case Dhcp6StsNotOnLink:
        if (Instance->IaCb.Ia->State == Dhcp6Confirming) {
          //
          // Before initiate new round DHCP, cache the current IA.
          //
          Status = Dhcp6CacheIa (Instance);
          if (EFI_ERROR (Status)) {
            return Status;
          }

          //
          // Restart S.A.R.R process to acquire new address.
          //
          Status = Dhcp6InitSolicitMsg (Instance);
          if (EFI_ERROR (Status)) {
            return Status;
          }
        }

        break;

      case Dhcp6StsNoBinding:
        if ((Instance->IaCb.Ia->State == Dhcp6Renewing) || (Instance->IaCb.Ia->State == Dhcp6Rebinding)) {
          //
          // Refer to RFC3315 Chapter 18.1.8, for each IA in the original Renew or Rebind message, the client
          // sends a Request message if the IA contained a Status Code option with the NoBinding status.
          //
          Status = Dhcp6SendRequestMsg (Instance);
          if (EFI_ERROR (Status)) {
            return Status;
          }
        }

        break;

      default:
        //
        // The other status code, just restart solicitation.
        //
        break;
    }
  }

  return EFI_SUCCESS;

ON_EXIT:

  if (!EFI_ERROR (Status)) {
    Status = Dhcp6DequeueRetry (
               Instance,
               Packet->Dhcp6.Header.TransactionId,
               FALSE
               );
  }

  return Status;
}

/**
  Select the appointed Dhcp6 advertisement message.

  @param[in]  Instance        The pointer to the Dhcp6 instance.
  @param[in]  AdSelect        The pointer to the selected Dhcp6 advertisement message.

  @retval EFI_SUCCESS           Selected the right advertisement message successfully.
  @retval EFI_OUT_OF_RESOURCES  Required system resources could not be allocated.
  @retval Others                Failed to select the advertise message.

**/
EFI_STATUS
Dhcp6SelectAdvertiseMsg (
  IN DHCP6_INSTANCE    *Instance,
  IN EFI_DHCP6_PACKET  *AdSelect
  )
{
  EFI_STATUS  Status;
  UINT8       *Option;

  ASSERT (AdSelect != NULL);

  //
  // Callback to user with the selected advertisement packet, and the user
  // might overwrite it.
  //
  Status = Dhcp6CallbackUser (Instance, Dhcp6SelectAdvertise, &AdSelect);

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

  Instance->AdSelect = AdSelect;

  //
  // Dequeue the sent packet for the retransmission since advertisement selected.
  //
  Status = Dhcp6DequeueRetry (
             Instance,
             AdSelect->Dhcp6.Header.TransactionId,
             FALSE
             );

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

  //
  // Check whether there is server unicast option in the selected advertise
  // packet, and update it.
  //
  Option = Dhcp6SeekOption (
             AdSelect->Dhcp6.Option,
             AdSelect->Length - sizeof (EFI_DHCP6_HEADER),
             Dhcp6OptServerUnicast
             );

  if (Option != NULL) {
    Instance->Unicast = AllocateZeroPool (sizeof (EFI_IPv6_ADDRESS));

    if (Instance->Unicast == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    CopyMem (Instance->Unicast, DHCP6_OFFSET_OF_OPT_DATA (Option), sizeof (EFI_IPv6_ADDRESS));
  }

  //
  // Update the information of the Ia by the selected advertisement message.
  //
  Status = Dhcp6UpdateIaInfo (Instance, AdSelect);

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

  //
  // Send the request message to continue the S.A.R.R. process.
  //
  return Dhcp6SendRequestMsg (Instance);
}

/**
  Handle with the Dhcp6 advertisement message.

  @param[in]  Instance        The pointer to the Dhcp6 instance.
  @param[in]  Packet          The pointer to the Dhcp6 advertisement message.

  @retval EFI_SUCCESS           Processed the advertisement message successfully.
  @retval EFI_OUT_OF_RESOURCES  Required system resources could not be allocated.
  @retval EFI_DEVICE_ERROR      An unexpected error.
  @retval Others                Failed to process the advertise message.

**/
EFI_STATUS
Dhcp6HandleAdvertiseMsg (
  IN DHCP6_INSTANCE    *Instance,
  IN EFI_DHCP6_PACKET  *Packet
  )
{
  EFI_STATUS  Status;
  UINT8       *Option;
  BOOLEAN     Timeout;

  ASSERT (Instance->Config);
  ASSERT (Instance->IaCb.Ia);

  Timeout = FALSE;

  //
  // If the client does receives a valid reply message that includes a rapid
  // commit option since a solicit with rapid commit option sent before, select
  // this reply message. Or else, process the advertise messages as normal.
  // See details in the section-17.1.4 of rfc-3315.
  //
  Option = Dhcp6SeekOption (
             Packet->Dhcp6.Option,
             Packet->Length - sizeof (EFI_DHCP6_HEADER),
             Dhcp6OptRapidCommit
             );

  if ((Option != NULL) && Instance->Config->RapidCommit && (Packet->Dhcp6.Header.MessageType == Dhcp6MsgReply)) {
    return Dhcp6HandleReplyMsg (Instance, Packet);
  }

  if (Packet->Dhcp6.Header.MessageType != Dhcp6MsgAdvertise) {
    return EFI_DEVICE_ERROR;
  }

  //
  // Client must ignore any advertise message that includes a status code option
  // containing the value noaddrsavail, with the exception that the client may
  // display the associated status message to the user.
  // See the details in the section-17.1.3 of rfc-3315.
  //
  Status = Dhcp6SeekStsOption (
             Instance,
             Packet,
             &Option
             );
  if (EFI_ERROR (Status)) {
    return EFI_DEVICE_ERROR;
  }

  //
  // Callback to user with the received packet and check the user's feedback.
  //
  Status = Dhcp6CallbackUser (Instance, Dhcp6RcvdAdvertise, &Packet);

  if (!EFI_ERROR (Status)) {
    //
    // Success means user choose the current advertisement packet.
    //
    if (Instance->AdSelect != NULL) {
      FreePool (Instance->AdSelect);
    }

    //
    // Store the selected advertisement packet and set a flag.
    //
    Instance->AdSelect = AllocateZeroPool (Packet->Size);

    if (Instance->AdSelect == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    CopyMem (Instance->AdSelect, Packet, Packet->Size);

    Instance->AdPref = 0xff;
  } else if (Status == EFI_NOT_READY) {
    //
    // Not_ready means user wants to continue to receive more advertise packets.
    //
    if ((Instance->AdPref == 0xff) && (Instance->AdSelect == NULL)) {
      //
      // It's a tricky point. The timer routine set adpref as 0xff if the first
      // rt timeout and no advertisement received, which means any advertisement
      // received will be selected after the first rt.
      //
      Timeout = TRUE;
    }

    //
    // Check whether the current packet has a 255 preference option or not.
    // Take non-preference option as 0 value.
    //
    Option = Dhcp6SeekOption (
               Packet->Dhcp6.Option,
               Packet->Length - 4,
               Dhcp6OptPreference
               );

    if ((Instance->AdSelect == NULL) || ((Option != NULL) && (*(Option + 4) > Instance->AdPref))) {
      //
      // No advertisements received before or preference is more than other
      // advertisements received before. Then store the new packet and the
      // preference value.
      //
      if (Instance->AdSelect != NULL) {
        FreePool (Instance->AdSelect);
      }

      Instance->AdSelect = AllocateZeroPool (Packet->Size);

      if (Instance->AdSelect == NULL) {
        return EFI_OUT_OF_RESOURCES;
      }

      CopyMem (Instance->AdSelect, Packet, Packet->Size);

      if (Option != NULL) {
        Instance->AdPref = *(DHCP6_OFFSET_OF_OPT_DATA (Option));
      }
    } else {
      //
      // Non-preference and other advertisements received before or current
      // preference is less than other advertisements received before.
      // Leave the packet alone.
    }
  } else {
    //
    // Other error status means termination.
    //
    return Status;
  }

  //
  // Client must collect advertise messages as more as possible until the first
  // RT has elapsed, or get a highest preference 255 advertise.
  // See details in the section-17.1.2 of rfc-3315.
  //
  if ((Instance->AdPref == 0xff) || Timeout) {
    Status = Dhcp6SelectAdvertiseMsg (Instance, Instance->AdSelect);
  }

  return Status;
}

/**
  The Dhcp6 stateful exchange process routine.

  @param[in]  Instance        The pointer to the Dhcp6 instance.
  @param[in]  Packet          The pointer to the received Dhcp6 message.

**/
VOID
Dhcp6HandleStateful (
  IN DHCP6_INSTANCE    *Instance,
  IN EFI_DHCP6_PACKET  *Packet
  )
{
  EFI_STATUS      Status;
  EFI_DHCP6_DUID  *ClientId;
  DHCP6_SERVICE   *Service;
  UINT8           *Option;

  Service  = Instance->Service;
  ClientId = Service->ClientId;
  Status   = EFI_SUCCESS;

  if (Instance->Config == NULL) {
    goto ON_CONTINUE;
  }

  ASSERT (ClientId);
  ASSERT (Instance->Config);
  ASSERT (Instance->IaCb.Ia);

  //
  // Discard the packet if not advertisement or reply packet.
  //
  if ((Packet->Dhcp6.Header.MessageType != Dhcp6MsgAdvertise) && (Packet->Dhcp6.Header.MessageType != Dhcp6MsgReply)) {
    goto ON_CONTINUE;
  }

  //
  // Check whether include client Id or not.
  //
  Option = Dhcp6SeekOption (
             Packet->Dhcp6.Option,
             Packet->Length - DHCP6_SIZE_OF_COMBINED_CODE_AND_LEN,
             Dhcp6OptClientId
             );

  if ((Option == NULL) || (CompareMem (DHCP6_OFFSET_OF_OPT_DATA (Option), ClientId->Duid, ClientId->Length) != 0)) {
    goto ON_CONTINUE;
  }

  //
  // Check whether include server Id or not.
  //
  Option = Dhcp6SeekOption (
             Packet->Dhcp6.Option,
             Packet->Length - DHCP6_SIZE_OF_COMBINED_CODE_AND_LEN,
             Dhcp6OptServerId
             );

  if (Option == NULL) {
    goto ON_CONTINUE;
  }

  switch (Instance->IaCb.Ia->State) {
    case Dhcp6Selecting:
      //
      // Handle the advertisement message when in the Dhcp6Selecting state.
      // Do not need check return status, if failed, just continue to the next.
      //
      Dhcp6HandleAdvertiseMsg (Instance, Packet);
      break;

    case Dhcp6Requesting:
    case Dhcp6Confirming:
    case Dhcp6Renewing:
    case Dhcp6Rebinding:
    case Dhcp6Releasing:
    case Dhcp6Declining:
      //
      // Handle the reply message when in the Dhcp6Requesting,  Dhcp6Renewing
      // Dhcp6Rebinding, Dhcp6Releasing and Dhcp6Declining state.
      // If failed here, it should reset the current session.
      //
      Status = Dhcp6HandleReplyMsg (Instance, Packet);
      if (EFI_ERROR (Status)) {
        goto ON_EXIT;
      }

      break;
    default:
      //
      // Other state has not supported yet.
      //
      break;
  }

ON_CONTINUE:
  //
  // Continue to receive the following Dhcp6 message.
  //
  Status = UdpIoRecvDatagram (
             Service->UdpIo,
             Dhcp6ReceivePacket,
             Service,
             0
             );
ON_EXIT:
  if (EFI_ERROR (Status)) {
    Dhcp6CleanupSession (Instance, Status);
  }
}

/**
  The Dhcp6 stateless exchange process routine.

  @param[in]  Instance        The pointer to the Dhcp6 instance.
  @param[in]  Packet          The pointer to the received Dhcp6 message.

**/
VOID
Dhcp6HandleStateless (
  IN DHCP6_INSTANCE    *Instance,
  IN EFI_DHCP6_PACKET  *Packet
  )
{
  EFI_STATUS     Status;
  DHCP6_SERVICE  *Service;
  DHCP6_INF_CB   *InfCb;
  UINT8          *Option;
  BOOLEAN        IsMatched;

  Service   = Instance->Service;
  Status    = EFI_SUCCESS;
  IsMatched = FALSE;
  InfCb     = NULL;

  if (Packet->Dhcp6.Header.MessageType != Dhcp6MsgReply) {
    goto ON_EXIT;
  }

  //
  // Check whether it's a desired Info-request message by Xid.
  //
  while (!IsListEmpty (&Instance->InfList)) {
    InfCb = NET_LIST_HEAD (&Instance->InfList, DHCP6_INF_CB, Link);
    if (InfCb->Xid == Packet->Dhcp6.Header.TransactionId) {
      IsMatched = TRUE;
      break;
    }
  }

  if (!IsMatched) {
    goto ON_EXIT;
  }

  //
  // Check whether include server Id or not.
  //
  Option = Dhcp6SeekOption (
             Packet->Dhcp6.Option,
             Packet->Length - sizeof (EFI_DHCP6_HEADER),
             Dhcp6OptServerId
             );

  if (Option == NULL) {
    goto ON_EXIT;
  }

  //
  // Callback to user with the received packet and check the user's feedback.
  //
  Status = InfCb->ReplyCallback (
                    &Instance->Dhcp6,
                    InfCb->CallbackContext,
                    Packet
                    );

  if (Status == EFI_NOT_READY) {
    //
    // Success or aborted will both stop this info-request exchange process,
    // but not ready means user wants to continue to receive reply.
    //
    goto ON_EXIT;
  }

  //
  // Dequeue the sent packet from the txlist if the xid matched, and ignore
  // if no xid matched.
  //
  Dhcp6DequeueRetry (
    Instance,
    Packet->Dhcp6.Header.TransactionId,
    FALSE
    );

  //
  // For sync, set the status out of polling for info-request.
  //
  Instance->UdpSts = Status;

ON_EXIT:

  Status = UdpIoRecvDatagram (
             Service->UdpIo,
             Dhcp6ReceivePacket,
             Service,
             0
             );

  if (EFI_ERROR (Status)) {
    Dhcp6CleanupRetry (Instance, DHCP6_PACKET_STATELESS);
  }
}

/**
  The receive callback function for Dhcp6 exchange process.

  @param[in]  Udp6Wrap        The pointer to the received net buffer.
  @param[in]  EndPoint        The pointer to the udp end point.
  @param[in]  IoStatus        The return status from udp io.
  @param[in]  Context         The opaque parameter to the function.

**/
VOID
EFIAPI
Dhcp6ReceivePacket (
  IN NET_BUF        *Udp6Wrap,
  IN UDP_END_POINT  *EndPoint,
  IN EFI_STATUS     IoStatus,
  IN VOID           *Context
  )
{
  EFI_DHCP6_HEADER  *Head;
  EFI_DHCP6_PACKET  *Packet;
  DHCP6_SERVICE     *Service;
  DHCP6_INSTANCE    *Instance;
  DHCP6_TX_CB       *TxCb;
  UINT32            Size;
  BOOLEAN           IsDispatched;
  BOOLEAN           IsStateless;
  LIST_ENTRY        *Entry1;
  LIST_ENTRY        *Next1;
  LIST_ENTRY        *Entry2;
  LIST_ENTRY        *Next2;
  EFI_STATUS        Status;

  ASSERT (Udp6Wrap != NULL);
  ASSERT (Context != NULL);

  Service      = (DHCP6_SERVICE *)Context;
  Instance     = NULL;
  Packet       = NULL;
  IsDispatched = FALSE;
  IsStateless  = FALSE;

  if (EFI_ERROR (IoStatus)) {
    return;
  }

  if (Udp6Wrap->TotalSize < sizeof (EFI_DHCP6_HEADER)) {
    goto ON_CONTINUE;
  }

  //
  // Copy the net buffer received from upd6 to a Dhcp6 packet.
  //
  Size   = sizeof (EFI_DHCP6_PACKET) + Udp6Wrap->TotalSize;
  Packet = (EFI_DHCP6_PACKET *)AllocateZeroPool (Size);

  if (Packet == NULL) {
    goto ON_CONTINUE;
  }

  Packet->Size   = Size;
  Head           = &Packet->Dhcp6.Header;
  Packet->Length = NetbufCopy (Udp6Wrap, 0, Udp6Wrap->TotalSize, (UINT8 *)Head);

  if (Packet->Length == 0) {
    goto ON_CONTINUE;
  }

  //
  // Dispatch packet to right instance by transaction id.
  //
  NET_LIST_FOR_EACH_SAFE (Entry1, Next1, &Service->Child) {
    Instance = NET_LIST_USER_STRUCT (Entry1, DHCP6_INSTANCE, Link);

    NET_LIST_FOR_EACH_SAFE (Entry2, Next2, &Instance->TxList) {
      TxCb = NET_LIST_USER_STRUCT (Entry2, DHCP6_TX_CB, Link);

      if (Packet->Dhcp6.Header.TransactionId == TxCb->Xid) {
        //
        // Find the corresponding packet in tx list, and check it whether belongs
        // to stateful exchange process.
        //
        if (TxCb->TxPacket->Dhcp6.Header.MessageType == Dhcp6MsgInfoRequest) {
          IsStateless = TRUE;
        }

        IsDispatched = TRUE;
        break;
      }
    }

    if (IsDispatched) {
      break;
    }
  }

  //
  // Skip this packet if not dispatched to any instance.
  //
  if (!IsDispatched) {
    goto ON_CONTINUE;
  }

  //
  // Dispatch the received packet ot the right instance.
  //
  if (IsStateless) {
    Dhcp6HandleStateless (Instance, Packet);
  } else {
    Dhcp6HandleStateful (Instance, Packet);
  }

ON_CONTINUE:

  if (!IsDispatched) {
    Status = UdpIoRecvDatagram (
               Service->UdpIo,
               Dhcp6ReceivePacket,
               Service,
               0
               );
    if (EFI_ERROR (Status)) {
      NET_LIST_FOR_EACH_SAFE (Entry1, Next1, &Service->Child) {
        Instance = NET_LIST_USER_STRUCT (Entry1, DHCP6_INSTANCE, Link);
        Dhcp6CleanupRetry (Instance, DHCP6_PACKET_ALL);
      }
    }
  }

  NetbufFree (Udp6Wrap);

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

/**
  Detect Link movement for specified network device.

  This routine will try to invoke Snp->GetStatus() to get the media status.
  If media present status switches from unpresent to present, a link movement
  is detected. Note that the underlying UNDI driver may not support reporting
  media status from GET_STATUS command. If that, fail to detect link movement.

  @param[in]  Instance       The pointer to DHCP6_INSTANCE.

  @retval     TRUE           A link movement is detected.
  @retval     FALSE          A link movement is not detected.

**/
BOOLEAN
Dhcp6LinkMovDetect (
  IN  DHCP6_INSTANCE  *Instance
  )
{
  UINT32                       InterruptStatus;
  BOOLEAN                      MediaPresent;
  EFI_STATUS                   Status;
  EFI_SIMPLE_NETWORK_PROTOCOL  *Snp;

  ASSERT (Instance != NULL);
  Snp          = Instance->Service->Snp;
  MediaPresent = Instance->MediaPresent;

  //
  // Check whether SNP support media detection
  //
  if (!Snp->Mode->MediaPresentSupported) {
    return FALSE;
  }

  //
  // Invoke Snp->GetStatus() to refresh MediaPresent field in SNP mode data
  //
  Status = Snp->GetStatus (Snp, &InterruptStatus, NULL);
  if (EFI_ERROR (Status)) {
    return FALSE;
  }

  Instance->MediaPresent = Snp->Mode->MediaPresent;
  //
  // Media transimit Unpresent to Present means new link movement is detected.
  //
  if (!MediaPresent && Instance->MediaPresent) {
    return TRUE;
  }

  return FALSE;
}

/**
  The timer routine of the Dhcp6 instance for each second.

  @param[in]  Event           The timer event.
  @param[in]  Context         The opaque parameter to the function.

**/
VOID
EFIAPI
Dhcp6OnTimerTick (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  LIST_ENTRY      *Entry;
  LIST_ENTRY      *NextEntry;
  DHCP6_INSTANCE  *Instance;
  DHCP6_TX_CB     *TxCb;
  DHCP6_IA_CB     *IaCb;
  UINT32          LossTime;
  EFI_STATUS      Status;

  ASSERT (Context != NULL);

  Instance = (DHCP6_INSTANCE *)Context;

  //
  // 1. Loop the tx list, count live time of every tx packet to check whether
  //    need re-transmit or not.
  //
  NET_LIST_FOR_EACH_SAFE (Entry, NextEntry, &Instance->TxList) {
    TxCb = NET_LIST_USER_STRUCT (Entry, DHCP6_TX_CB, Link);

    TxCb->TickTime++;

    if (TxCb->TickTime > TxCb->RetryExp) {
      //
      // Handle the first rt in the transmission of solicit specially.
      //
      if (((TxCb->RetryCnt == 0) || TxCb->SolicitRetry) && (TxCb->TxPacket->Dhcp6.Header.MessageType == Dhcp6MsgSolicit)) {
        if (Instance->AdSelect == NULL) {
          //
          // Set adpref as 0xff here to indicate select any advertisement
          // afterwards.
          //
          Instance->AdPref = 0xff;
        } else {
          //
          // Select the advertisement received before.
          //
          Status = Dhcp6SelectAdvertiseMsg (Instance, Instance->AdSelect);
          if (Status == EFI_ABORTED) {
            goto ON_CLOSE;
          } else if (EFI_ERROR (Status)) {
            TxCb->RetryCnt++;
          }

          return;
        }
      }

      //
      // Increase the retry count for the packet and add up the total loss time.
      //
      TxCb->RetryCnt++;
      TxCb->RetryLos += TxCb->RetryExp;

      //
      // Check whether overflow the max retry count limit for this packet
      //
      if ((TxCb->RetryCtl.Mrc != 0) && (TxCb->RetryCtl.Mrc < TxCb->RetryCnt)) {
        Status = EFI_NO_RESPONSE;
        goto ON_CLOSE;
      }

      //
      // Check whether overflow the max retry duration for this packet
      //
      if ((TxCb->RetryCtl.Mrd != 0) && (TxCb->RetryCtl.Mrd <= TxCb->RetryLos)) {
        Status = EFI_NO_RESPONSE;
        goto ON_CLOSE;
      }

      //
      // Re-calculate retry expire timeout for the next time.
      //
      // Firstly, Check the new calculated time whether overflow the max retry
      // expire time.
      //
      TxCb->RetryExp = Dhcp6CalculateExpireTime (
                         TxCb->RetryExp,
                         FALSE,
                         TRUE
                         );

      if ((TxCb->RetryCtl.Mrt != 0) && (TxCb->RetryCtl.Mrt < TxCb->RetryExp)) {
        TxCb->RetryExp = Dhcp6CalculateExpireTime (
                           TxCb->RetryCtl.Mrt,
                           TRUE,
                           TRUE
                           );
      }

      //
      // Secondly, Check the new calculated time whether overflow the max retry
      // duration time.
      //
      LossTime = TxCb->RetryLos + TxCb->RetryExp;
      if ((TxCb->RetryCtl.Mrd != 0) && (TxCb->RetryCtl.Mrd < LossTime)) {
        TxCb->RetryExp = TxCb->RetryCtl.Mrd - TxCb->RetryLos;
      }

      //
      // Reset the tick time for the next retransmission
      //
      TxCb->TickTime = 0;

      //
      // Retransmit the last sent packet again.
      //
      Dhcp6TransmitPacket (Instance, TxCb->TxPacket, TxCb->Elapsed);
      TxCb->SolicitRetry = FALSE;
      if (TxCb->TxPacket->Dhcp6.Header.MessageType == Dhcp6MsgSolicit) {
        TxCb->SolicitRetry = TRUE;
      }
    }
  }

  //
  // 2. Check the configured Ia, count lease time of every valid Ia to check
  // whether need to renew or rebind this Ia.
  //
  IaCb = &Instance->IaCb;

  if ((Instance->Config == NULL) || (IaCb->Ia == NULL)) {
    return;
  }

  if ((IaCb->Ia->State == Dhcp6Bound) || (IaCb->Ia->State == Dhcp6Renewing) || (IaCb->Ia->State == Dhcp6Rebinding)) {
    IaCb->LeaseTime++;

    if ((IaCb->LeaseTime > IaCb->T2) && (IaCb->Ia->State == Dhcp6Bound)) {
      //
      // Exceed t2, send rebind packet to extend the Ia lease.
      //
      Dhcp6SendRenewRebindMsg (Instance, TRUE);
    } else if ((IaCb->LeaseTime > IaCb->T1) && (IaCb->Ia->State == Dhcp6Bound)) {
      //
      // Exceed t1, send renew packet to extend the Ia lease.
      //
      Dhcp6SendRenewRebindMsg (Instance, FALSE);
    }
  }

  //
  // 3. In any situation when a client may have moved to a new link, the
  //    client MUST initiate a Confirm/Reply message exchange.
  //
  if (Dhcp6LinkMovDetect (Instance) && (IaCb->Ia->State == Dhcp6Bound)) {
    Dhcp6SendConfirmMsg (Instance);
  }

  return;

ON_CLOSE:

  if (Dhcp6IsValidTxCb (Instance, TxCb) &&
      (TxCb->TxPacket != NULL) &&
      ((TxCb->TxPacket->Dhcp6.Header.MessageType == Dhcp6MsgInfoRequest) ||
       (TxCb->TxPacket->Dhcp6.Header.MessageType == Dhcp6MsgRenew) ||
       (TxCb->TxPacket->Dhcp6.Header.MessageType == Dhcp6MsgConfirm))
      )
  {
    //
    // The failure of renew/Confirm will still switch to the bound state.
    //
    if ((TxCb->TxPacket->Dhcp6.Header.MessageType == Dhcp6MsgRenew) ||
        (TxCb->TxPacket->Dhcp6.Header.MessageType == Dhcp6MsgConfirm))
    {
      ASSERT (Instance->IaCb.Ia);
      Instance->IaCb.Ia->State = Dhcp6Bound;
    }

    //
    // The failure of info-request will return no response.
    //
    if (TxCb->TxPacket->Dhcp6.Header.MessageType == Dhcp6MsgInfoRequest) {
      Instance->UdpSts = EFI_NO_RESPONSE;
    }

    Dhcp6DequeueRetry (
      Instance,
      TxCb->Xid,
      TRUE
      );
  } else {
    //
    // The failure of the others will terminate current state machine if timeout.
    //
    Dhcp6CleanupSession (Instance, Status);
  }
}
