/** @file
  Dhcp6 support functions implementation.

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

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

**/

#include "Dhcp6Impl.h"


/**
  Generate client Duid in the format of Duid-llt.

  @param[in]  Mode          The pointer to the mode of SNP.

  @retval     NULL          If it failed to generate a client Id.
  @retval     others        The pointer to the new client id.

**/
EFI_DHCP6_DUID *
Dhcp6GenerateClientId (
  IN EFI_SIMPLE_NETWORK_MODE   *Mode
  )
{
  EFI_STATUS                Status;
  EFI_DHCP6_DUID            *Duid;
  EFI_TIME                  Time;
  UINT32                    Stamp;
  EFI_GUID                  Uuid;


  //
  // Attempt to get client Id from variable to keep it constant.
  // See details in section-9 of rfc-3315.
  //
  GetVariable2 (L"ClientId", &gEfiDhcp6ServiceBindingProtocolGuid, (VOID**)&Duid, NULL);
  if (Duid != NULL) {
    return Duid;
  }

  //
  //  The format of client identifier option:
  //
  //     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_CLIENTID        |          option-len           |
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //    .                                                               .
  //    .                              DUID                             .
  //    .                        (variable length)                      .
  //    .                                                               .
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //

  //
  // If System UUID is found from SMBIOS Table, use DUID-UUID type.
  //
  if ((PcdGet8 (PcdDhcp6UidType) == Dhcp6DuidTypeUuid) && !EFI_ERROR (NetLibGetSystemGuid (&Uuid)) && !CompareGuid (&Uuid, &gZeroGuid)) {
    //
    //
    //  The format of DUID-UUID:
    //
    //    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
    //   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //   |          DUID-Type (4)        |    UUID (128 bits)            |
    //   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
    //   |                                                               |
    //   |                                                               |
    //   |                                -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //   |                                |
    //   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-

    //
    // sizeof (option-len + Duid-type + UUID-size) = 20 bytes
    //
    Duid = AllocateZeroPool (2 + 2 + sizeof (EFI_GUID));
    if (Duid == NULL) {
      return NULL;
    }

    //
    // sizeof (Duid-type + UUID-size) = 18 bytes
    //
    Duid->Length = (UINT16) (18);

    //
    // Set the Duid-type and copy UUID.
    //
    WriteUnaligned16 ((UINT16 *) (Duid->Duid), HTONS (Dhcp6DuidTypeUuid));

    CopyMem (Duid->Duid + 2, &Uuid, sizeof(EFI_GUID));

  } else {

    //
    //
    //  The format of DUID-LLT:
    //
    //     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
    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //    |          Duid type (1)        |    hardware type (16 bits)    |
    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //    |                        time (32 bits)                         |
    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //    .                                                               .
    //    .             link-layer address (variable length)              .
    //    .                                                               .
    //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //

    //
    // Generate a time stamp of the seconds from 2000/1/1, assume 30day/month.
    //
    gRT->GetTime (&Time, NULL);
    Stamp = (UINT32)
      (
        ((((UINT32)(Time.Year - 2000) * 360 + (Time.Month - 1) * 30 + (Time.Day - 1)) * 24 + Time.Hour) * 60 + Time.Minute) *
        60 +
        Time.Second
      );

    //
    // sizeof (option-len + Duid-type + hardware-type + time) = 10 bytes
    //
    Duid = AllocateZeroPool (10 + Mode->HwAddressSize);
    if (Duid == NULL) {
      return NULL;
    }

    //
    // sizeof (Duid-type + hardware-type + time) = 8 bytes
    //
    Duid->Length = (UINT16) (Mode->HwAddressSize + 8);

    //
    // Set the Duid-type, hardware-type, time and copy the hardware address.
    //
    WriteUnaligned16 ((UINT16 *) ((UINT8 *) Duid + OFFSET_OF (EFI_DHCP6_DUID, Duid)), HTONS (Dhcp6DuidTypeLlt));
    WriteUnaligned16 ((UINT16 *) ((UINT8 *) Duid + OFFSET_OF (EFI_DHCP6_DUID, Duid) + 2), HTONS (NET_IFTYPE_ETHERNET));
    WriteUnaligned32 ((UINT32 *) ((UINT8 *) Duid + OFFSET_OF (EFI_DHCP6_DUID, Duid) + 4), HTONL (Stamp));

    CopyMem (Duid->Duid + 8, &Mode->CurrentAddress, Mode->HwAddressSize);
  }

  Status = gRT->SetVariable (
                  L"ClientId",
                  &gEfiDhcp6ServiceBindingProtocolGuid,
                  (EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS),
                  Duid->Length + 2,
                  (VOID *) Duid
                  );
  if (EFI_ERROR (Status)) {
    FreePool (Duid);
    return NULL;
  }

  return Duid;
}


/**
  Copy the Dhcp6 configure data.

  @param[in]  DstCfg        The pointer to the destination configure data.
  @param[in]  SorCfg        The pointer to the source configure data.

  @retval EFI_SUCCESS           Copy the content from SorCfg from DstCfg successfully.
  @retval EFI_OUT_OF_RESOURCES  Required system resources could not be allocated.

**/
EFI_STATUS
Dhcp6CopyConfigData (
  IN EFI_DHCP6_CONFIG_DATA      *DstCfg,
  IN EFI_DHCP6_CONFIG_DATA      *SorCfg
  )
{
  UINTN                     Index;
  UINTN                     OptionListSize;
  UINTN                     OptionSize;

  CopyMem (DstCfg, SorCfg, sizeof (EFI_DHCP6_CONFIG_DATA));

  //
  // Allocate another buffer for solicitretransmission, and copy it.
  //
  if (SorCfg->SolicitRetransmission != NULL) {

    DstCfg->SolicitRetransmission = AllocateZeroPool (sizeof (EFI_DHCP6_RETRANSMISSION));

    if (DstCfg->SolicitRetransmission == NULL) {
      //
      // Error will be handled out of this function.
      //
      return EFI_OUT_OF_RESOURCES;
    }

    CopyMem (
      DstCfg->SolicitRetransmission,
      SorCfg->SolicitRetransmission,
      sizeof (EFI_DHCP6_RETRANSMISSION)
      );
  }

  if (SorCfg->OptionList != NULL && SorCfg->OptionCount != 0) {

    OptionListSize     = SorCfg->OptionCount * sizeof (EFI_DHCP6_PACKET_OPTION *);
    DstCfg->OptionList = AllocateZeroPool (OptionListSize);

    if (DstCfg->OptionList == NULL) {
      //
      // Error will be handled out of this function.
      //
      return EFI_OUT_OF_RESOURCES;
    }

    for (Index = 0; Index < SorCfg->OptionCount; Index++) {

      OptionSize                = NTOHS (SorCfg->OptionList[Index]->OpLen) + 4;
      DstCfg->OptionList[Index] = AllocateZeroPool (OptionSize);

      if (DstCfg->OptionList[Index] == NULL) {
        //
        // Error will be handled out of this function.
        //
        return EFI_OUT_OF_RESOURCES;
      }

      CopyMem (
        DstCfg->OptionList[Index],
        SorCfg->OptionList[Index],
        OptionSize
        );
    }
  }

  return EFI_SUCCESS;
}


/**
  Clean up the configure data.

  @param[in, out]  CfgData       The pointer to the configure data.

**/
VOID
Dhcp6CleanupConfigData (
  IN OUT EFI_DHCP6_CONFIG_DATA       *CfgData
  )
{
  UINTN                          Index;

  ASSERT (CfgData != NULL);
  //
  // Clean up all fields in config data including the reference buffers, but do
  // not free the config data buffer itself.
  //
  if (CfgData->OptionList != NULL) {
    for (Index = 0; Index < CfgData->OptionCount; Index++) {
      if (CfgData->OptionList[Index] != NULL) {
        FreePool (CfgData->OptionList[Index]);
      }
    }
    FreePool (CfgData->OptionList);
  }

  if (CfgData->SolicitRetransmission != NULL) {
    FreePool (CfgData->SolicitRetransmission);
  }

  ZeroMem (CfgData, sizeof (EFI_DHCP6_CONFIG_DATA));
}


/**
  Clean up the mode data.

  @param[in, out]  ModeData      The pointer to the mode data.

**/
VOID
Dhcp6CleanupModeData (
  IN OUT EFI_DHCP6_MODE_DATA        *ModeData
  )
{
  ASSERT (ModeData != NULL);
  //
  // Clean up all fields in mode data including the reference buffers, but do
  // not free the mode data buffer itself.
  //
  if (ModeData->ClientId != NULL) {
    FreePool (ModeData->ClientId);
  }

  if (ModeData->Ia != NULL) {

    if (ModeData->Ia->ReplyPacket != NULL) {
      FreePool (ModeData->Ia->ReplyPacket);
    }
    FreePool (ModeData->Ia);
  }

  ZeroMem (ModeData, sizeof (EFI_DHCP6_MODE_DATA));
}


/**
  Calculate the expire time by the algorithm defined in rfc.

  @param[in]  Base          The base value of the time.
  @param[in]  IsFirstRt     If TRUE, it is the first time to calculate expire time.
  @param[in]  NeedSigned    If TRUE, the the signed factor is needed.

  @return     Expire        The calculated result for the new expire time.

**/
UINT32
Dhcp6CalculateExpireTime (
  IN UINT32                 Base,
  IN BOOLEAN                IsFirstRt,
  IN BOOLEAN                NeedSigned
  )
{
  EFI_TIME                  Time;
  BOOLEAN                   Signed;
  UINT32                    Seed;
  UINT32                    Expire;

  //
  // Take the 10bits of microsecond in system time as a uniform distribution.
  // Take the 10th bit as a flag to determine it's signed or not.
  //
  gRT->GetTime (&Time, NULL);
  Seed   = ((Time.Nanosecond >> 10) & DHCP6_10_BIT_MASK);
  Signed = (BOOLEAN) ((((Time.Nanosecond >> 9) & 0x01) != 0) ? TRUE : FALSE);
  Signed = (BOOLEAN) (NeedSigned ? Signed : FALSE);

  //
  // Calculate expire by the following algo:
  //   1. base + base * (-0.1 ~ 0) for the first solicit
  //   2. base + base * (-0.1 ~ 0.1) for the first other messages
  //   3. 2 * base + base * (-0.1 ~ 0.1) for the subsequent all messages
  //   4. base + base * (-0.1 ~ 0) for the more than mrt timeout
  //
  // The (Seed / 0x3ff / 10) is used to a random range (0, 0.1).
  //
  if (IsFirstRt && Signed) {

    Expire = Base - (UINT32) (Base * Seed / DHCP6_10_BIT_MASK / 10);

  } else if (IsFirstRt && !Signed) {

    Expire = Base + (UINT32) (Base * Seed / DHCP6_10_BIT_MASK / 10);

  } else if (!IsFirstRt && Signed) {

    Expire = 2 * Base - (UINT32) (Base * Seed / DHCP6_10_BIT_MASK / 10);

  } else {

    Expire = 2 * Base + (UINT32) (Base * Seed / DHCP6_10_BIT_MASK / 10);
  }

  Expire = (Expire != 0) ? Expire : 1;

  return Expire;
}


/**
  Calculate the lease time by the algorithm defined in rfc.

  @param[in]  IaCb          The pointer to the Ia control block.

**/
VOID
Dhcp6CalculateLeaseTime (
  IN DHCP6_IA_CB              *IaCb
  )
{
  UINT32                      MinLt;
  UINT32                      MaxLt;
  UINTN                       Index;

  ASSERT (IaCb->Ia->IaAddressCount > 0);

  MinLt    = (UINT32) (-1);
  MaxLt    = 0;

  //
  // Calculate minlt as min of all valid life time, and maxlt as max of all
  // valid life time.
  //
  for (Index = 0; Index < IaCb->Ia->IaAddressCount; Index++) {
    MinLt  = MIN (MinLt, IaCb->Ia->IaAddress[Index].ValidLifetime);
    MaxLt  = MAX (MinLt, IaCb->Ia->IaAddress[Index].ValidLifetime);
  }

  //
  // Take 50% minlt as t1, and 80% maxlt as t2 if Dhcp6 server doesn't offer
  // such information.
  //
  IaCb->T1            = (IaCb->T1 != 0) ? IaCb->T1 : (UINT32)(MinLt * 5 / 10);
  IaCb->T2            = (IaCb->T2 != 0) ? IaCb->T2 : (UINT32)(MinLt * 8 / 10);
  IaCb->AllExpireTime = MaxLt;
  IaCb->LeaseTime     = 0;
}


/**
  Check whether the addresses are all included by the configured Ia.

  @param[in]  Ia            The pointer to the Ia.
  @param[in]  AddressCount  The number of addresses.
  @param[in]  Addresses     The pointer to the addresses buffer.

  @retval EFI_SUCCESS         The addresses are all included by the configured IA.
  @retval EFI_NOT_FOUND       The addresses are not included by the configured IA.

**/
EFI_STATUS
Dhcp6CheckAddress (
  IN EFI_DHCP6_IA             *Ia,
  IN UINT32                   AddressCount,
  IN EFI_IPv6_ADDRESS         *Addresses
  )
{
  UINTN                       Index1;
  UINTN                       Index2;
  BOOLEAN                     Found;

  //
  // Check whether the addresses are all included by the configured IA. And it
  // will return success if address count is zero, which means all addresses.
  //
  for (Index1 = 0; Index1 < AddressCount; Index1++) {

    Found = FALSE;

    for (Index2 = 0; Index2 < Ia->IaAddressCount; Index2++) {

      if (CompareMem (
            &Addresses[Index1],
            &Ia->IaAddress[Index2],
            sizeof (EFI_IPv6_ADDRESS)
            ) == 0) {

        Found = TRUE;
        break;
      }
    }

    if (!Found) {
      return EFI_NOT_FOUND;
    }
  }

  return EFI_SUCCESS;
}


/**
  Deprive the addresses from current Ia, and generate another eliminated Ia.

  @param[in]  Ia            The pointer to the Ia.
  @param[in]  AddressCount  The number of addresses.
  @param[in]  Addresses     The pointer to the addresses buffer.

  @retval     NULL          If it failed to generate the deprived Ia.
  @retval     others        The pointer to the deprived Ia.

**/
EFI_DHCP6_IA *
Dhcp6DepriveAddress (
  IN EFI_DHCP6_IA             *Ia,
  IN UINT32                   AddressCount,
  IN EFI_IPv6_ADDRESS         *Addresses
  )
{
  EFI_DHCP6_IA                *IaCopy;
  UINTN                       IaCopySize;
  UINTN                       Index1;
  UINTN                       Index2;
  BOOLEAN                     Found;

  if (AddressCount == 0) {
    //
    // It means release all Ia addresses if address count is zero.
    //
    AddressCount = Ia->IaAddressCount;
  }

  ASSERT (AddressCount != 0);

  IaCopySize = sizeof (EFI_DHCP6_IA) + (AddressCount - 1) * sizeof (EFI_DHCP6_IA_ADDRESS);
  IaCopy     = AllocateZeroPool (IaCopySize);

  if (IaCopy == NULL) {
    return NULL;
  }

  if (AddressCount == Ia->IaAddressCount) {
    //
    // If release all Ia addresses, just copy the configured Ia and then set
    // its address count as zero.
    // We may decline/release part of addresses at the begining. So it's a
    // forwarding step to update address infor for decline/release, while the
    // other infor such as Ia state will be updated when receiving reply.
    //
    CopyMem (IaCopy, Ia, IaCopySize);
    Ia->IaAddressCount = 0;
    return IaCopy;
  }

  CopyMem (IaCopy, Ia, sizeof (EFI_DHCP6_IA));

  //
  // Move the addresses from the Ia of instance to the deprived Ia.
  //
  for (Index1 = 0; Index1 < AddressCount; Index1++) {

    Found = FALSE;

    for (Index2 = 0; Index2 < Ia->IaAddressCount; Index2++) {

      if (CompareMem (
            &Addresses[Index1],
            &Ia->IaAddress[Index2],
            sizeof (EFI_IPv6_ADDRESS)
            ) == 0) {
        //
        // Copy the deprived address to the copy of Ia
        //
        CopyMem (
          &IaCopy->IaAddress[Index1],
          &Ia->IaAddress[Index2],
          sizeof (EFI_DHCP6_IA_ADDRESS)
          );
        //
        // Delete the deprived address from the instance Ia
        //
        if (Index2 + 1 < Ia->IaAddressCount) {
          CopyMem (
            &Ia->IaAddress[Index2],
            &Ia->IaAddress[Index2 + 1],
            (Ia->IaAddressCount - Index2 - 1) * sizeof (EFI_DHCP6_IA_ADDRESS)
            );
        }
        Found = TRUE;
        break;
      }
    }
    ASSERT (Found == TRUE);
  }

  Ia->IaAddressCount    -= AddressCount;
  IaCopy->IaAddressCount = AddressCount;

  return IaCopy;
}


/**
  The dummy ext buffer free callback routine.

  @param[in]  Arg           The pointer to the parameter.

**/
VOID
EFIAPI
Dhcp6DummyExtFree (
  IN VOID                      *Arg
  )
{
}


/**
  The callback routine once message transmitted.

  @param[in]  Wrap          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
Dhcp6OnTransmitted (
  IN NET_BUF                   *Wrap,
  IN UDP_END_POINT             *EndPoint,
  IN EFI_STATUS                IoStatus,
  IN VOID                      *Context
  )
{
  NetbufFree (Wrap);
}


/**
  Append the option to Buf, and move Buf to the end.

  @param[in, out] Buf           The pointer to the buffer.
  @param[in]      OptType       The option type.
  @param[in]      OptLen        The length of option contents.
  @param[in]      Data          The pointer to the option content.

  @return         Buf           The position to append the next option.

**/
UINT8 *
Dhcp6AppendOption (
  IN OUT UINT8               *Buf,
  IN     UINT16              OptType,
  IN     UINT16              OptLen,
  IN     UINT8               *Data
  )
{
  //
  //  The format of Dhcp6 option:
  //
  //     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-code          |   option-len (option data)    |
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //    |                          option-data                          |
  //    |                      (option-len octets)                      |
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //

  ASSERT (OptLen != 0);

  WriteUnaligned16 ((UINT16 *) Buf, OptType);
  Buf            += 2;
  WriteUnaligned16 ((UINT16 *) Buf, OptLen);
  Buf            += 2;
  CopyMem (Buf, Data, NTOHS (OptLen));
  Buf            += NTOHS (OptLen);

  return Buf;
}

/**
  Append the appointed IA Address option to Buf, and move Buf to the end.

  @param[in, out] Buf           The pointer to the position to append.
  @param[in]      IaAddr        The pointer to the IA Address.
  @param[in]      MessageType   Message type of DHCP6 package.

  @return         Buf           The position to append the next option.

**/
UINT8 *
Dhcp6AppendIaAddrOption (
  IN OUT UINT8                  *Buf,
  IN     EFI_DHCP6_IA_ADDRESS   *IaAddr,
  IN     UINT32                 MessageType
)
{

  //  The format of the IA Address 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_IAADDR        |          option-len           |
  //      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //      |                                                               |
  //      |                         IPv6 address                          |
  //      |                                                               |
  //      |                                                               |
  //      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //      |                      preferred-lifetime                       |
  //      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //      |                        valid-lifetime                         |
  //      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //      .                                                               .
  //      .                        IAaddr-options                         .
  //      .                                                               .
  //      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  //
  // Fill the value of Ia Address option type
  //
  WriteUnaligned16 ((UINT16 *) Buf, HTONS (Dhcp6OptIaAddr));
  Buf                     += 2;

  WriteUnaligned16 ((UINT16 *) Buf, HTONS (sizeof (EFI_DHCP6_IA_ADDRESS)));
  Buf                     += 2;

  CopyMem (Buf, &IaAddr->IpAddress, sizeof(EFI_IPv6_ADDRESS));
  Buf                     += sizeof(EFI_IPv6_ADDRESS);

  //
  // Fill the value of preferred-lifetime and valid-lifetime.
  // According to RFC3315 Chapter 18.1.2, the preferred-lifetime and valid-lifetime fields
  // should set to 0 when initiate a Confirm message.
  //
  if (MessageType != Dhcp6MsgConfirm) {
    WriteUnaligned32 ((UINT32 *) Buf, HTONL (IaAddr->PreferredLifetime));
  }
  Buf                     += 4;

  if (MessageType != Dhcp6MsgConfirm) {
    WriteUnaligned32 ((UINT32 *) Buf, HTONL (IaAddr->ValidLifetime));
  }
  Buf                     += 4;

  return Buf;
}


/**
  Append the appointed Ia option to Buf, and move Buf to the end.

  @param[in, out] Buf           The pointer to the position to append.
  @param[in]      Ia            The pointer to the Ia.
  @param[in]      T1            The time of T1.
  @param[in]      T2            The time of T2.
  @param[in]      MessageType   Message type of DHCP6 package.

  @return         Buf           The position to append the next Ia option.

**/
UINT8 *
Dhcp6AppendIaOption (
  IN OUT UINT8                  *Buf,
  IN     EFI_DHCP6_IA           *Ia,
  IN     UINT32                 T1,
  IN     UINT32                 T2,
  IN     UINT32                 MessageType
  )
{
  UINT8                     *AddrOpt;
  UINT16                    *Len;
  UINTN                     Index;

  //
  //  The format of IA_NA and IA_TA option:
  //
  //     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 (only for IA_NA)                    |
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //    |                        T2 (only for IA_NA)                    |
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //    |                                                               |
  //    .                  IA_NA-options/IA_TA-options                  .
  //    .                                                               .
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //

  //
  // Fill the value of Ia option type
  //
  WriteUnaligned16 ((UINT16 *) Buf, HTONS (Ia->Descriptor.Type));
  Buf                     += 2;

  //
  // Fill the len of Ia option later, keep the pointer first
  //
  Len                      = (UINT16 *) Buf;
  Buf                     += 2;

  //
  // Fill the value of iaid
  //
  WriteUnaligned32 ((UINT32 *) Buf, HTONL (Ia->Descriptor.IaId));
  Buf                     += 4;

  //
  // Fill the value of t1 and t2 if iana, keep it 0xffffffff if no specified.
  //
  if (Ia->Descriptor.Type == Dhcp6OptIana) {
    WriteUnaligned32 ((UINT32 *) Buf, HTONL ((T1 != 0) ? T1 : 0xffffffff));
    Buf                   += 4;
    WriteUnaligned32 ((UINT32 *) Buf, HTONL ((T2 != 0) ? T2 : 0xffffffff));
    Buf                   += 4;
  }

  //
  // Fill all the addresses belong to the Ia
  //
  for (Index = 0; Index < Ia->IaAddressCount; Index++) {
    AddrOpt = (UINT8 *) Ia->IaAddress + Index * sizeof (EFI_DHCP6_IA_ADDRESS);
    Buf = Dhcp6AppendIaAddrOption (Buf, (EFI_DHCP6_IA_ADDRESS *) AddrOpt, MessageType);
  }

  //
  // Fill the value of Ia option length
  //
  *Len = HTONS ((UINT16) (Buf - (UINT8 *) Len - 2));

  return Buf;
}

/**
  Append the appointed Elapsed time option to Buf, and move Buf to the end.

  @param[in, out] Buf           The pointer to the position to append.
  @param[in]      Instance      The pointer to the Dhcp6 instance.
  @param[out]     Elapsed       The pointer to the elapsed time value in
                                  the generated packet.

  @return         Buf           The position to append the next Ia option.

**/
UINT8 *
Dhcp6AppendETOption (
  IN OUT UINT8                  *Buf,
  IN     DHCP6_INSTANCE         *Instance,
  OUT    UINT16                 **Elapsed
  )
{
  //
  //  The format of elapsed time option:
  //
  //   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_ELAPSED_TIME      |           option-len          |
  //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //  |          elapsed-time         |
  //  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //

  //
  // Fill the value of elapsed-time option type.
  //
  WriteUnaligned16 ((UINT16 *) Buf, HTONS (Dhcp6OptElapsedTime));
  Buf                     += 2;

  //
  // Fill the len of elapsed-time option, which is fixed.
  //
  WriteUnaligned16 ((UINT16 *) Buf, HTONS(2));
  Buf                     += 2;

  //
  // Fill in elapsed time value with 0 value for now.  The actual value is
  // filled in later just before the packet is transmitted.
  //
  WriteUnaligned16 ((UINT16 *) Buf, HTONS(0));
  *Elapsed                  = (UINT16 *) Buf;
  Buf                     += 2;

  return Buf;
}

/**
  Set the elapsed time based on the given instance and the pointer to the
  elapsed time option.

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

**/
VOID
SetElapsedTime (
  IN     UINT16                 *Elapsed,
  IN     DHCP6_INSTANCE         *Instance
  )
{
  EFI_TIME          Time;
  UINT64            CurrentStamp;
  UINT64            ElapsedTimeValue;

  //
  // Generate a time stamp of the centiseconds from 2000/1/1, assume 30day/month.
  //
  gRT->GetTime (&Time, NULL);
  CurrentStamp = MultU64x32 (
                   ((((UINT32)(Time.Year - 2000) * 360 + (Time.Month - 1) * 30 + (Time.Day - 1)) * 24 + Time.Hour) * 60 + Time.Minute) * 60 + Time.Second,
                   100
                   ) +
                 DivU64x32(
                   Time.Nanosecond,
                   10000000
                   );

  //
  // Sentinel value of 0 means that this is the first DHCP packet that we are
  // sending and that we need to initialize the value.  First DHCP message
  // gets 0 elapsed-time.  Otherwise, calculate based on StartTime.
  //
  if (Instance->StartTime == 0) {
    ElapsedTimeValue = 0;
    Instance->StartTime = CurrentStamp;
  } else {
    ElapsedTimeValue = CurrentStamp - Instance->StartTime;

    //
    // If elapsed time cannot fit in two bytes, set it to 0xffff.
    //
    if (ElapsedTimeValue > 0xffff) {
      ElapsedTimeValue = 0xffff;
    }
  }
  WriteUnaligned16 (Elapsed, HTONS((UINT16) ElapsedTimeValue));
}


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

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

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

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

  Option = NULL;
  Cursor = Buf;

  //
  // The format of Dhcp6 option refers to Dhcp6AppendOption().
  //
  while (Cursor < Buf + SeekLen) {
    OpCode = ReadUnaligned16 ((UINT16 *) Cursor);
    if (OpCode == HTONS (OptType)) {
      Option = Cursor;
      break;
    }
    DataLen = NTOHS (ReadUnaligned16 ((UINT16 *) (Cursor + 2)));
    Cursor += (DataLen + 4);
  }

  return Option;
}


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

  @param[in]  Buf           The pointer to the buffer.
  @param[in]  SeekLen       The length to seek.
  @param[in]  IaDesc        The pointer to the Ia descriptor.

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

**/
UINT8 *
Dhcp6SeekIaOption (
  IN UINT8                    *Buf,
  IN UINT32                   SeekLen,
  IN EFI_DHCP6_IA_DESCRIPTOR  *IaDesc
  )
{
  UINT8              *Cursor;
  UINT8              *Option;
  UINT16             DataLen;
  UINT16             OpCode;
  UINT32             IaId;

  //
  // The format of IA_NA and IA_TA option refers to Dhcp6AppendIaOption().
  //
  Option = NULL;
  Cursor = Buf;

  while (Cursor < Buf + SeekLen) {
    OpCode = ReadUnaligned16 ((UINT16 *) Cursor);
    IaId   = ReadUnaligned32 ((UINT32 *) (Cursor + 4));
    if (OpCode == HTONS (IaDesc->Type) && IaId == HTONL (IaDesc->IaId)) {
      Option = Cursor;
      break;
    }
    DataLen = NTOHS (ReadUnaligned16 ((UINT16 *) (Cursor + 2)));
    Cursor += (DataLen + 4);
  }

  return Option;
}

/**
  Check whether the incoming IPv6 address in IaAddr is one of the maintained
  addresses in the IA control blcok.

  @param[in]  IaAddr            The pointer to the IA Address to be checked.
  @param[in]  CurrentIa         The pointer to the IA in IA control block.

  @retval     TRUE              Yes, this Address is already in IA control block.
  @retval     FALSE             No, this Address is NOT in IA control block.

**/
BOOLEAN
Dhcp6AddrIsInCurrentIa (
  IN    EFI_DHCP6_IA_ADDRESS      *IaAddr,
  IN    EFI_DHCP6_IA              *CurrentIa
  )
{
  UINT32    Index;

  ASSERT (IaAddr != NULL && CurrentIa != NULL);

  for (Index = 0; Index < CurrentIa->IaAddressCount; Index++) {
    if (EFI_IP6_EQUAL(&IaAddr->IpAddress, &CurrentIa->IaAddress[Index].IpAddress)) {
      return TRUE;
    }
  }
  return FALSE;
}

/**
  Parse the address option and update the address infomation.

  @param[in]      CurrentIa     The pointer to the Ia Address in control blcok.
  @param[in]      IaInnerOpt    The pointer to the buffer.
  @param[in]      IaInnerLen    The length to parse.
  @param[out]     AddrNum       The number of addresses.
  @param[in, out] AddrBuf       The pointer to the address buffer.

**/
VOID
Dhcp6ParseAddrOption (
  IN     EFI_DHCP6_IA            *CurrentIa,
  IN     UINT8                   *IaInnerOpt,
  IN     UINT16                  IaInnerLen,
     OUT UINT32                  *AddrNum,
  IN OUT EFI_DHCP6_IA_ADDRESS    *AddrBuf
  )
{
  UINT8                       *Cursor;
  UINT16                      DataLen;
  UINT16                      OpCode;
  UINT32                      ValidLt;
  UINT32                      PreferredLt;
  EFI_DHCP6_IA_ADDRESS        *IaAddr;

  //
  //  The format of the IA Address option:
  //
  //     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_IAADDR        |          option-len           |
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //    |                                                               |
  //    |                         IPv6 address                          |
  //    |                                                               |
  //    |                                                               |
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //    |                      preferred-lifetime                       |
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //    |                        valid-lifetime                         |
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //    .                                                               .
  //    .                        IAaddr-options                         .
  //    .                                                               .
  //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  //

  //
  //  Two usage model:
  //
  //    1. Pass addrbuf == null, to get the addrnum over the Ia inner options.
  //    2. Pass addrbuf != null, to resolve the addresses over the Ia inner
  //       options to the addrbuf.
  //

  Cursor   = IaInnerOpt;
  *AddrNum = 0;

  while (Cursor < IaInnerOpt + IaInnerLen) {
    //
    // Refer to RFC3315 Chapter 18.1.8, we need to update lifetimes for any addresses in the IA option
    // that the client already has recorded in the IA, and discard the Ia address option with 0 valid time.
    //
    OpCode  = ReadUnaligned16 ((UINT16 *) Cursor);
    PreferredLt = NTOHL (ReadUnaligned32 ((UINT32 *) (Cursor + 20)));
    ValidLt = NTOHL (ReadUnaligned32 ((UINT32 *) (Cursor + 24)));
    IaAddr = (EFI_DHCP6_IA_ADDRESS *) (Cursor + 4);
    if (OpCode == HTONS (Dhcp6OptIaAddr) && ValidLt >= PreferredLt &&
        (Dhcp6AddrIsInCurrentIa(IaAddr, CurrentIa) || ValidLt !=0)) {
      if (AddrBuf != NULL) {
        CopyMem (AddrBuf, IaAddr, sizeof (EFI_DHCP6_IA_ADDRESS));
        AddrBuf->PreferredLifetime = PreferredLt;
        AddrBuf->ValidLifetime     = ValidLt;
        AddrBuf = (EFI_DHCP6_IA_ADDRESS *) ((UINT8 *) AddrBuf + sizeof (EFI_DHCP6_IA_ADDRESS));
      }
      (*AddrNum)++;
    }
    DataLen = NTOHS (ReadUnaligned16 ((UINT16 *) (Cursor + 2)));
    Cursor += (DataLen + 4);
  }
}


/**
  Create a control blcok for the Ia according to the corresponding options.

  @param[in]  Instance              The pointer to DHCP6 Instance.
  @param[in]  IaInnerOpt            The pointer to the inner options in the Ia option.
  @param[in]  IaInnerLen            The length of all the inner options in the Ia option.
  @param[in]  T1                    T1 time in the Ia option.
  @param[in]  T2                    T2 time in the Ia option.

  @retval     EFI_NOT_FOUND         No valid IA option is found.
  @retval     EFI_SUCCESS           Create an IA control block successfully.
  @retval     EFI_OUT_OF_RESOURCES  Required system resources could not be allocated.
  @retval     EFI_DEVICE_ERROR      An unexpected error.

**/
EFI_STATUS
Dhcp6GenerateIaCb (
  IN  DHCP6_INSTANCE           *Instance,
  IN  UINT8                    *IaInnerOpt,
  IN  UINT16                   IaInnerLen,
  IN  UINT32                   T1,
  IN  UINT32                   T2
  )
{
  UINT32                       AddrNum;
  UINT32                       IaSize;
  EFI_DHCP6_IA                 *Ia;

  if (Instance->IaCb.Ia == NULL) {
    return EFI_DEVICE_ERROR;
  }

  //
  // Calculate the number of addresses for this Ia, excluding the addresses with
  // the value 0 of valid lifetime.
  //
  Dhcp6ParseAddrOption (Instance->IaCb.Ia, IaInnerOpt, IaInnerLen, &AddrNum, NULL);

  if (AddrNum == 0) {
    return EFI_NOT_FOUND;
  }

  //
  // Allocate for new IA.
  //
  IaSize = sizeof (EFI_DHCP6_IA) + (AddrNum - 1) * sizeof (EFI_DHCP6_IA_ADDRESS);
  Ia = AllocateZeroPool (IaSize);

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

  //
  // Fill up this new IA fields.
  //
  Ia->State          = Instance->IaCb.Ia->State;
  Ia->IaAddressCount = AddrNum;
  CopyMem (&Ia->Descriptor, &Instance->Config->IaDescriptor, sizeof (EFI_DHCP6_IA_DESCRIPTOR));
  Dhcp6ParseAddrOption (Instance->IaCb.Ia, IaInnerOpt, IaInnerLen, &AddrNum, Ia->IaAddress);

  //
  // Free original IA resource.
  //
  if (Instance->IaCb.Ia->ReplyPacket != NULL) {
    FreePool (Instance->IaCb.Ia->ReplyPacket);
  }
  FreePool (Instance->IaCb.Ia);


  ZeroMem (&Instance->IaCb, sizeof (DHCP6_IA_CB));

  //
  // Update IaCb to use new IA.
  //
  Instance->IaCb.Ia   = Ia;

  //

 // Fill in IaCb fields. Such as T1, T2, AllExpireTime and LeaseTime.
  //
  Instance->IaCb.T1 = T1;
  Instance->IaCb.T2 = T2;
  Dhcp6CalculateLeaseTime (&Instance->IaCb);

  return EFI_SUCCESS;
}


/**
  Cache the current IA configuration information.

  @param[in] Instance           The pointer to DHCP6 Instance.

  @retval EFI_SUCCESS           Cache the current IA successfully.
  @retval EFI_OUT_OF_RESOURCES  Required system resources could not be allocated.

**/
EFI_STATUS
Dhcp6CacheIa (
  IN DHCP6_INSTANCE           *Instance
  )
{
  UINTN                        IaSize;
  EFI_DHCP6_IA                 *Ia;

  Ia = Instance->IaCb.Ia;

  if ((Instance->CacheIa == NULL) && (Ia != NULL)) {
    //
    // Cache the current IA.
    //
    IaSize = sizeof (EFI_DHCP6_IA) + (Ia->IaAddressCount - 1) * sizeof (EFI_DHCP6_IA_ADDRESS);

    Instance->CacheIa = AllocateZeroPool (IaSize);
    if (Instance->CacheIa == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }
    CopyMem (Instance->CacheIa, Ia, IaSize);
  }
  return EFI_SUCCESS;
}

/**
  Append CacheIa to the currrent IA. Meanwhile, clear CacheIa.ValidLifetime to 0.

  @param[in]  Instance            The pointer to DHCP6 instance.

**/
VOID
Dhcp6AppendCacheIa (
  IN DHCP6_INSTANCE           *Instance
  )
{
  UINT8                        *Ptr;
  UINTN                        Index;
  UINTN                        IaSize;
  UINTN                        NewIaSize;
  EFI_DHCP6_IA                 *Ia;
  EFI_DHCP6_IA                 *NewIa;
  EFI_DHCP6_IA                 *CacheIa;

  Ia      = Instance->IaCb.Ia;
  CacheIa = Instance->CacheIa;

  if ((CacheIa != NULL) && (CacheIa->IaAddressCount != 0)) {
    //
    // There are old addresses existing. Merge with current addresses.
    //
    NewIaSize = sizeof (EFI_DHCP6_IA) + (Ia->IaAddressCount + CacheIa->IaAddressCount - 1) * sizeof (EFI_DHCP6_IA_ADDRESS);
    NewIa     = AllocateZeroPool (NewIaSize);
    if (NewIa == NULL) {
      return;
    }

    IaSize = sizeof (EFI_DHCP6_IA) + (Ia->IaAddressCount - 1) * sizeof (EFI_DHCP6_IA_ADDRESS);
    CopyMem (NewIa, Ia, IaSize);

    //
    // Clear old address.ValidLifetime
    //
    for (Index = 0; Index < CacheIa->IaAddressCount; Index++) {
      CacheIa->IaAddress[Index].ValidLifetime  = 0;
    }

    NewIa->IaAddressCount += CacheIa->IaAddressCount;
    Ptr   = (UINT8*)&NewIa->IaAddress[Ia->IaAddressCount];
    CopyMem (Ptr, CacheIa->IaAddress, CacheIa->IaAddressCount * sizeof (EFI_DHCP6_IA_ADDRESS));

    //
    // Migrate to the NewIa and free previous.
    //
    FreePool (Instance->CacheIa);
    FreePool (Instance->IaCb.Ia);
    Instance->CacheIa  = NULL;
    Instance->IaCb.Ia  = NewIa;
  }
}

/**
  Calculate the Dhcp6 get mapping timeout by adding additinal delay to the IP6 DAD transmits count.

  @param[in]   Ip6Cfg              The pointer to Ip6 config protocol.
  @param[out]  TimeOut             The time out value in 100ns units.

  @retval   EFI_INVALID_PARAMETER  Input parameters are invalid.
  @retval   EFI_SUCCESS            Calculate the time out value successfully.
**/
EFI_STATUS
Dhcp6GetMappingTimeOut (
  IN  EFI_IP6_CONFIG_PROTOCOL       *Ip6Cfg,
  OUT UINTN                         *TimeOut
  )
{
  EFI_STATUS            Status;
  UINTN                 DataSize;
  EFI_IP6_CONFIG_DUP_ADDR_DETECT_TRANSMITS    DadXmits;

  if (Ip6Cfg == NULL || TimeOut == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  DataSize = sizeof (EFI_IP6_CONFIG_DUP_ADDR_DETECT_TRANSMITS);
  Status = Ip6Cfg->GetData (
                     Ip6Cfg,
                     Ip6ConfigDataTypeDupAddrDetectTransmits,
                     &DataSize,
                     &DadXmits
                     );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  *TimeOut = TICKS_PER_SECOND * DadXmits.DupAddrDetectTransmits + DHCP6_DAD_ADDITIONAL_DELAY;

  return EFI_SUCCESS;
}
