/** @file
  Dhcp6 internal functions implementation.

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

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

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

**/

#include "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 destoryed 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 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.
    //
    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);
    }
  }
}


/**
  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;
  EFI_DHCP6_STATE             State;
  UINT8                       *Option;
  UINT8                       *IaInnerOpt;
  UINT16                      IaInnerLen;
  UINT16                      StsCode;
  UINT32                      T1;
  UINT32                      T2;

  ASSERT (Instance->Config != NULL);
  //
  // If the reply was received in reponse 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 alread recorded
  //   5. leave unchanged any information about addresses
  //
  // See details in the section-18.1.8 of rfc-3315.
  //
  State  = Dhcp6Init;
  Option = Dhcp6SeekIaOption (
             Packet->Dhcp6.Option,
             Packet->Length - sizeof (EFI_DHCP6_HEADER),
             &Instance->Config->IaDescriptor
             );
  if (Option == NULL) {
    return EFI_DEVICE_ERROR;
  }

  //
  // 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                         .
  //    .                                                               .
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //

  //
  // sizeof (option-code + option-len + IaId)           = 8
  // sizeof (option-code + option-len + IaId + T1)      = 12
  // sizeof (option-code + option-len + IaId + T1 + T2) = 16
  //
  // The inner options still start with 2 bytes option-code and 2 bytes option-len.
  //
  if (Instance->Config->IaDescriptor.Type == Dhcp6OptIana) {
    T1 = NTOHL (ReadUnaligned32 ((UINT32 *) (Option + 8)));
    T2 = NTOHL (ReadUnaligned32 ((UINT32 *) (Option + 12)));
    IaInnerOpt = Option + 16;
    IaInnerLen = (UINT16) (NTOHS (ReadUnaligned16 ((UINT16 *) (Option + 2))) - 12);
  } else {
    T1 = 0;
    T2 = 0;
    IaInnerOpt = Option + 8;
    IaInnerLen = (UINT16) (NTOHS (ReadUnaligned16 ((UINT16 *) (Option + 2))) - 4);
  }

  //
  // 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 *) (Option + 4)));
    if (StsCode != Dhcp6StsSuccess) {
      return EFI_DEVICE_ERROR;
    }
  }

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

  return Status;
}



/**
  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;

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

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

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

  //
  // 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                         .
  //    .                                                               .
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //

  //
  // sizeof (option-code + option-len + IaId)           = 8
  // sizeof (option-code + option-len + IaId + T1)      = 12
  // sizeof (option-code + option-len + IaId + T1 + T2) = 16
  //
  // The inner options still start with 2 bytes option-code and 2 bytes option-len.
  //
  if (Instance->Config->IaDescriptor.Type == Dhcp6OptIana) {
    IaInnerOpt = *Option + 16;
    IaInnerLen = (UINT16) (NTOHS (ReadUnaligned16 ((UINT16 *) (*Option + 2))) - 12);
  } else {
    IaInnerOpt = *Option + 8;
    IaInnerLen = (UINT16) (NTOHS (ReadUnaligned16 ((UINT16 *) (*Option + 2))) - 4);
  }

  //
  // 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 *) (*Option + 4)));
    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 commone fields.
  //
  Packet = AllocateZeroPool (DHCP6_BASE_PACKET_SIZE + UserLen);
  if (Packet == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  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);
  Cursor = Dhcp6AppendOption (
             Cursor,
             HTONS (Dhcp6OptClientId),
             Length,
             ClientId->Duid
             );

  Cursor = Dhcp6AppendETOption (
             Cursor,
             Instance,
             &Elapsed
             );

  Cursor = Dhcp6AppendIaOption (
             Cursor,
             Instance->IaCb.Ia,
             Instance->IaCb.T1,
             Instance->IaCb.T2
             );

  //
  // Append user-defined when configurate Dhcp6 service.
  //
  for (Index = 0; Index < Instance->Config->OptionCount; Index++) {

    UserOpt = Instance->Config->OptionList[Index];
    Cursor  = Dhcp6AppendOption(
                Cursor,
                UserOpt->OpCode,
                UserOpt->OpLen,
                UserOpt->Data
                );
  }

  //
  // Determine the size/length of packet.
  //
  Packet->Length += (UINT32) (Cursor - Packet->Dhcp6.Option);
  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)) {
    FreePool (Packet);
    return Status;
  }

  //
  // Send solicit packet with the state transition from Dhcp6init to
  // Dhcp6selecting.
  //
  Instance->IaCb.Ia->State = Dhcp6Selecting;

  Status = Dhcp6TransmitPacket (Instance, Packet, Elapsed);

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

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

/**
  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 - 4,
             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 commone fields.
  //
  Packet = AllocateZeroPool (DHCP6_BASE_PACKET_SIZE + UserLen);
  if (Packet == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  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);
  Cursor = Dhcp6AppendOption (
             Cursor,
             HTONS (Dhcp6OptClientId),
             Length,
             ClientId->Duid
             );

  Cursor = Dhcp6AppendETOption (
             Cursor,
             Instance,
             &Elapsed
             );

  Cursor = Dhcp6AppendOption (
             Cursor,
             HTONS (Dhcp6OptServerId),
             ServerId->Length,
             ServerId->Duid
             );

  Cursor = Dhcp6AppendIaOption (
             Cursor,
             Instance->IaCb.Ia,
             Instance->IaCb.T1,
             Instance->IaCb.T2
             );

  //
  // Append user-defined when configurate Dhcp6 service.
  //
  for (Index = 0; Index < Instance->Config->OptionCount; Index++) {

    UserOpt = Instance->Config->OptionList[Index];
    Cursor  = Dhcp6AppendOption(
                Cursor,
                UserOpt->OpCode,
                UserOpt->OpLen,
                UserOpt->Data
                );
  }

  //
  // Determine the size/length of packet.
  //
  Packet->Length += (UINT32) (Cursor - Packet->Dhcp6.Option);
  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)) {
    FreePool (Packet);
    return Status;
  }

  //
  // Send request packet with the state transition from Dhcp6selecting to
  // Dhcp6requesting.
  //
  Instance->IaCb.Ia->State = Dhcp6Requesting;

  Status = Dhcp6TransmitPacket (Instance, Packet, Elapsed);

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

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


/**
  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 - 4,
             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 commone fields.
  //
  Packet = AllocateZeroPool (DHCP6_BASE_PACKET_SIZE);
  if (Packet == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  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);
  Cursor = Dhcp6AppendOption (
             Cursor,
             HTONS (Dhcp6OptClientId),
             Length,
             ClientId->Duid
             );

  Cursor = Dhcp6AppendETOption (
             Cursor,
             Instance,
             &Elapsed
             );

  Cursor = Dhcp6AppendOption (
             Cursor,
             HTONS (Dhcp6OptServerId),
             ServerId->Length,
             ServerId->Duid
             );

  Cursor = Dhcp6AppendIaOption (Cursor, DecIa, 0, 0);

  //
  // Determine the size/length of packet.
  //
  Packet->Length += (UINT32) (Cursor - Packet->Dhcp6.Option);
  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)) {
    FreePool (Packet);
    return Status;
  }

  //
  // Send decline packet with the state transition from Dhcp6bound to
  // Dhcp6declining.
  //
  Instance->IaCb.Ia->State = Dhcp6Declining;

  Status = Dhcp6TransmitPacket (Instance, Packet, Elapsed);

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

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


/**
  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 - 4,
             Dhcp6OptServerId
             );
  if (Option == NULL) {
    return EFI_DEVICE_ERROR;
  }

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

  //
  // Create the Dhcp6 packet and initialize commone fields.
  //
  Packet = AllocateZeroPool (DHCP6_BASE_PACKET_SIZE);
  if (Packet == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  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);
  Cursor = Dhcp6AppendOption (
             Cursor,
             HTONS (Dhcp6OptClientId),
             Length,
             ClientId->Duid
             );

  //
  // ServerId is extracted from packet, it's network order.
  //
  Cursor = Dhcp6AppendOption (
             Cursor,
             HTONS (Dhcp6OptServerId),
             ServerId->Length,
             ServerId->Duid
             );

  Cursor = Dhcp6AppendETOption (
             Cursor,
             Instance,
             &Elapsed
             );

  Cursor = Dhcp6AppendIaOption (Cursor, RelIa, 0, 0);

  //
  // Determine the size/length of packet
  //
  Packet->Length += (UINT32) (Cursor - Packet->Dhcp6.Option);
  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)) {
    FreePool (Packet);
    return Status;
  }

  //
  // 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)) {
    FreePool (Packet);
    return Status;
  }

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


/**
  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 commone fields.
  //
  Packet = AllocateZeroPool (DHCP6_BASE_PACKET_SIZE + UserLen);
  if (Packet == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  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);
  Cursor = Dhcp6AppendOption (
             Cursor,
             HTONS (Dhcp6OptClientId),
             Length,
             ClientId->Duid
             );

  Cursor = Dhcp6AppendETOption (
             Cursor,
             Instance,
             &Elapsed
             );

  Cursor = Dhcp6AppendIaOption (
             Cursor,
             Instance->IaCb.Ia,
             Instance->IaCb.T1,
             Instance->IaCb.T2
             );

  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 - 4,
               Dhcp6OptServerId
               );
    if (Option == NULL) {
      FreePool (Packet);
      return EFI_DEVICE_ERROR;
    }

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

    Cursor = Dhcp6AppendOption (
               Cursor,
               HTONS (Dhcp6OptServerId),
               ServerId->Length,
               ServerId->Duid
               );
  }

  //
  // Append user-defined when configurate Dhcp6 service.
  //
  for (Index = 0; Index < Instance->Config->OptionCount; Index++) {

    UserOpt = Instance->Config->OptionList[Index];
    Cursor  = Dhcp6AppendOption(
                Cursor,
                UserOpt->OpCode,
                UserOpt->OpLen,
                UserOpt->Data
                );
  }

  //
  // Determine the size/length of packet.
  //
  Packet->Length += (UINT32) (Cursor - Packet->Dhcp6.Option);
  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)) {
    FreePool (Packet);
    return Status;
  }

  //
  // 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;

  Status = Dhcp6TransmitPacket (Instance, Packet, Elapsed);

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

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

/**
  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 commone fields.
  //
  Packet = AllocateZeroPool (DHCP6_BASE_PACKET_SIZE + UserLen);
  if (Packet == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  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);
    Cursor = Dhcp6AppendOption (
               Cursor,
               HTONS (Dhcp6OptClientId),
               Length,
               ClientId->Duid
               );
  }

  Cursor = Dhcp6AppendETOption (
             Cursor,
             Instance,
             &Elapsed
             );

  Cursor = Dhcp6AppendOption (
             Cursor,
             OptionRequest->OpCode,
             OptionRequest->OpLen,
             OptionRequest->Data
             );

  //
  // Append user-defined when configurate Dhcp6 service.
  //
  for (Index = 0; Index < OptionCount; Index++) {

    UserOpt = OptionList[Index];
    Cursor  = Dhcp6AppendOption(
                Cursor,
                UserOpt->OpCode,
                UserOpt->OpLen,
                UserOpt->Data
                );
  }

  //
  // Determine the size/length of packet.
  //
  Packet->Length += (UINT32) (Cursor - Packet->Dhcp6.Option);
  ASSERT (Packet->Size > Packet->Length + 8);

  //
  // Send info-request packet with no state.
  //
  Status = Dhcp6TransmitPacket (Instance, Packet, Elapsed);

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

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


/**
  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) {
    return EFI_OUT_OF_RESOURCES;
  }

  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);
  Cursor = Dhcp6AppendOption (
             Cursor,
             HTONS (Dhcp6OptClientId),
             Length,
             ClientId->Duid
             );

  Cursor = Dhcp6AppendETOption (
             Cursor,
             Instance,
             &Elapsed
             );

  Cursor = Dhcp6AppendIaOption (
             Cursor,
             Instance->IaCb.Ia,
             Instance->IaCb.T1,
             Instance->IaCb.T2
             );

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

  //
  // Determine the size/length of packet.
  //
  Packet->Length += (UINT32) (Cursor - Packet->Dhcp6.Option);
  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)) {
    FreePool (Packet);
    return Status;
  }

  //
  // Send confirm packet with the state transition from Dhcp6Bound to
  // Dhcp6Confirming.
  //
  Instance->IaCb.Ia->State = Dhcp6Confirming;

  Status = Dhcp6TransmitPacket (Instance, Packet, Elapsed);

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

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



/**
  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);

  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 - 4,
             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_DEVICE_ERROR;
    }
  }

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

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

  //
  // Dequeue the sent packet from retransmit list since reply received.
  //
  Status = Dhcp6DequeueRetry (
             Instance,
             Packet->Dhcp6.Header.TransactionId,
             FALSE
             );

  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 infomation update.
    //
    if (Instance->Config->IaInfoEvent != NULL) {
      gBS->SignalEvent (Instance->Config->IaInfoEvent);
    }

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

    return EFI_SUCCESS;
  }

  //
  // 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)) {
    //
    // Reset start time for next exchange.
    //
    Instance->StartTime       = 0;

    //
    // No status code or no error status code means succeed to reply.
    //
    Status = Dhcp6UpdateIaInfo (Instance, Packet);

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

    //
    // 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) {
      return EFI_OUT_OF_RESOURCES;
    }

    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 triger 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 infomation update.
    //
    if (Instance->Config->IaInfoEvent != NULL) {
      gBS->SignalEvent (Instance->Config->IaInfoEvent);
    }
  } else if (Option != NULL) {
    //
    // Any error status code option is found.
    //
    StsCode = NTOHS (ReadUnaligned16 ((UINT16 *) (Option + 4)));
    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;

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

  return EFI_SUCCESS;
}


/**
  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 - 4,
             Dhcp6OptServerUnicast
             );

  if (Option != NULL) {

    Instance->Unicast = AllocateZeroPool (sizeof(EFI_IPv6_ADDRESS));

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

    CopyMem (Instance->Unicast, Option + 4, 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;
  UINT16                      StsCode;
  BOOLEAN                     Timeout;

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

  Timeout = FALSE;
  StsCode = Dhcp6StsSuccess;

  //
  // If the client does receives a valid reply message that includes a rapid
  // commit option since a solicit with rapid commit optioin 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 - 4,
             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.
  //
  Option = Dhcp6SeekOption(
             Packet->Dhcp6.Option,
             Packet->Length - 4,
             Dhcp6OptStatusCode
             );

  if (Option != NULL) {
    StsCode = NTOHS (ReadUnaligned16 ((UINT16 *) (Option + 4)));
    if (StsCode != Dhcp6StsSuccess) {
      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 = *(Option + 4);
      }
    } 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->InDestory || 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 - 4,
             Dhcp6OptClientId
             );

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

  //
  // Check whether include server Id or not.
  //
  Option = Dhcp6SeekOption(
             Packet->Dhcp6.Option,
             Packet->Length - 4,
             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 (Instance->InDestory) {
    goto ON_EXIT;
  }

  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 - 4,
             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;

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

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

  if (EFI_ERROR (IoStatus)) {
    return ;
  }

  //
  // 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:

  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 (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) {
        goto ON_CLOSE;
      }

      //
      // Check whether overflow the max retry duration for this packet
      //
      if (TxCb->RetryCtl.Mrd != 0 && TxCb->RetryCtl.Mrd <= TxCb->RetryLos) {
        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 (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, EFI_NO_RESPONSE);
  }
}
