/** @file
  Function to validate, parse, process the DHCP options.

Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "Dhcp4Impl.h"

///
/// A list of the format of DHCP Options sorted by option tag
/// to validate a dhcp message. Refere the comments of the
/// DHCP_OPTION_FORMAT structure.
///
DHCP_OPTION_FORMAT  DhcpOptionFormats[] = {
  { DHCP4_TAG_NETMASK,         DHCP_OPTION_IP,     1, 1,  TRUE  },
  { DHCP4_TAG_TIME_OFFSET,     DHCP_OPTION_INT32,  1, 1,  FALSE },
  { DHCP4_TAG_ROUTER,          DHCP_OPTION_IP,     1, -1, TRUE  },
  { DHCP4_TAG_TIME_SERVER,     DHCP_OPTION_IP,     1, -1, FALSE },
  { DHCP4_TAG_NAME_SERVER,     DHCP_OPTION_IP,     1, -1, FALSE },
  { DHCP4_TAG_DNS_SERVER,      DHCP_OPTION_IP,     1, -1, FALSE },
  { DHCP4_TAG_LOG_SERVER,      DHCP_OPTION_IP,     1, -1, FALSE },
  { DHCP4_TAG_COOKIE_SERVER,   DHCP_OPTION_IP,     1, -1, FALSE },
  { DHCP4_TAG_LPR_SERVER,      DHCP_OPTION_IP,     1, -1, FALSE },
  { DHCP4_TAG_IMPRESS_SERVER,  DHCP_OPTION_IP,     1, -1, FALSE },
  { DHCP4_TAG_RL_SERVER,       DHCP_OPTION_IP,     1, -1, FALSE },
  { DHCP4_TAG_HOSTNAME,        DHCP_OPTION_INT8,   1, -1, FALSE },
  { DHCP4_TAG_BOOTFILE_LEN,    DHCP_OPTION_INT16,  1, 1,  FALSE },
  { DHCP4_TAG_DUMP,            DHCP_OPTION_INT8,   1, -1, FALSE },
  { DHCP4_TAG_DOMAINNAME,      DHCP_OPTION_INT8,   1, -1, FALSE },
  { DHCP4_TAG_SWAP_SERVER,     DHCP_OPTION_IP,     1, 1,  FALSE },
  { DHCP4_TAG_ROOTPATH,        DHCP_OPTION_INT8,   1, -1, FALSE },
  { DHCP4_TAG_EXTEND_PATH,     DHCP_OPTION_INT8,   1, -1, FALSE },

  { DHCP4_TAG_IPFORWARD,       DHCP_OPTION_SWITCH, 1, 1,  FALSE },
  { DHCP4_TAG_NONLOCAL_SRR,    DHCP_OPTION_SWITCH, 1, 1,  FALSE },
  { DHCP4_TAG_POLICY_SRR,      DHCP_OPTION_IPPAIR, 1, -1, FALSE },
  { DHCP4_TAG_EMTU,            DHCP_OPTION_INT16,  1, 1,  FALSE },
  { DHCP4_TAG_TTL,             DHCP_OPTION_INT8,   1, 1,  FALSE },
  { DHCP4_TAG_PATHMTU_AGE,     DHCP_OPTION_INT32,  1, 1,  FALSE },
  { DHCP4_TAG_PATHMTU_PLATEAU, DHCP_OPTION_INT16,  1, -1, FALSE },

  { DHCP4_TAG_IFMTU,           DHCP_OPTION_INT16,  1, 1,  FALSE },
  { DHCP4_TAG_SUBNET_LOCAL,    DHCP_OPTION_SWITCH, 1, 1,  FALSE },
  { DHCP4_TAG_BROADCAST,       DHCP_OPTION_IP,     1, 1,  FALSE },
  { DHCP4_TAG_DISCOVER_MASK,   DHCP_OPTION_SWITCH, 1, 1,  FALSE },
  { DHCP4_TAG_SUPPLY_MASK,     DHCP_OPTION_SWITCH, 1, 1,  FALSE },
  { DHCP4_TAG_DISCOVER_ROUTE,  DHCP_OPTION_SWITCH, 1, 1,  FALSE },
  { DHCP4_TAG_ROUTER_SOLICIT,  DHCP_OPTION_IP,     1, 1,  FALSE },
  { DHCP4_TAG_STATIC_ROUTE,    DHCP_OPTION_IPPAIR, 1, -1, FALSE },

  { DHCP4_TAG_TRAILER,         DHCP_OPTION_SWITCH, 1, 1,  FALSE },
  { DHCP4_TAG_ARPAGE,          DHCP_OPTION_INT32,  1, 1,  FALSE },
  { DHCP4_TAG_ETHER_ENCAP,     DHCP_OPTION_SWITCH, 1, 1,  FALSE },

  { DHCP4_TAG_TCP_TTL,         DHCP_OPTION_INT8,   1, 1,  FALSE },
  { DHCP4_TAG_KEEP_INTERVAL,   DHCP_OPTION_INT32,  1, 1,  FALSE },
  { DHCP4_TAG_KEEP_GARBAGE,    DHCP_OPTION_SWITCH, 1, 1,  FALSE },

  { DHCP4_TAG_NIS_DOMAIN,      DHCP_OPTION_INT8,   1, -1, FALSE },
  { DHCP4_TAG_NIS_SERVER,      DHCP_OPTION_IP,     1, -1, FALSE },
  { DHCP4_TAG_NTP_SERVER,      DHCP_OPTION_IP,     1, -1, FALSE },
  { DHCP4_TAG_VENDOR,          DHCP_OPTION_INT8,   1, -1, FALSE },
  { DHCP4_TAG_NBNS,            DHCP_OPTION_IP,     1, -1, FALSE },
  { DHCP4_TAG_NBDD,            DHCP_OPTION_IP,     1, -1, FALSE },
  { DHCP4_TAG_NBTYPE,          DHCP_OPTION_INT8,   1, 1,  FALSE },
  { DHCP4_TAG_NBSCOPE,         DHCP_OPTION_INT8,   1, -1, FALSE },
  { DHCP4_TAG_XFONT,           DHCP_OPTION_IP,     1, -1, FALSE },
  { DHCP4_TAG_XDM,             DHCP_OPTION_IP,     1, -1, FALSE },

  { DHCP4_TAG_REQUEST_IP,      DHCP_OPTION_IP,     1, 1,  FALSE },
  { DHCP4_TAG_LEASE,           DHCP_OPTION_INT32,  1, 1,  TRUE  },
  { DHCP4_TAG_OVERLOAD,        DHCP_OPTION_INT8,   1, 1,  TRUE  },
  { DHCP4_TAG_MSG_TYPE,        DHCP_OPTION_INT8,   1, 1,  TRUE  },
  { DHCP4_TAG_SERVER_ID,       DHCP_OPTION_IP,     1, 1,  TRUE  },
  { DHCP4_TAG_PARA_LIST,       DHCP_OPTION_INT8,   1, -1, FALSE },
  { DHCP4_TAG_MESSAGE,         DHCP_OPTION_INT8,   1, -1, FALSE },
  { DHCP4_TAG_MAXMSG,          DHCP_OPTION_INT16,  1, 1,  FALSE },
  { DHCP4_TAG_T1,              DHCP_OPTION_INT32,  1, 1,  TRUE  },
  { DHCP4_TAG_T2,              DHCP_OPTION_INT32,  1, 1,  TRUE  },
  { DHCP4_TAG_VENDOR_CLASS_ID, DHCP_OPTION_INT8,   1, -1, FALSE },
  { DHCP4_TAG_CLIENT_ID,       DHCP_OPTION_INT8,   2, -1, FALSE },

  { DHCP4_TAG_NISPLUS,         DHCP_OPTION_INT8,   1, -1, FALSE },
  { DHCP4_TAG_NISPLUS_SERVER,  DHCP_OPTION_IP,     1, -1, FALSE },

  { DHCP4_TAG_TFTP,            DHCP_OPTION_INT8,   1, -1, FALSE },
  { DHCP4_TAG_BOOTFILE,        DHCP_OPTION_INT8,   1, -1, FALSE },

  { DHCP4_TAG_MOBILEIP,        DHCP_OPTION_IP,     0, -1, FALSE },
  { DHCP4_TAG_SMTP,            DHCP_OPTION_IP,     1, -1, FALSE },
  { DHCP4_TAG_POP3,            DHCP_OPTION_IP,     1, -1, FALSE },
  { DHCP4_TAG_NNTP,            DHCP_OPTION_IP,     1, -1, FALSE },
  { DHCP4_TAG_WWW,             DHCP_OPTION_IP,     1, -1, FALSE },
  { DHCP4_TAG_FINGER,          DHCP_OPTION_IP,     1, -1, FALSE },
  { DHCP4_TAG_IRC,             DHCP_OPTION_IP,     1, -1, FALSE },
  { DHCP4_TAG_STTALK,          DHCP_OPTION_IP,     1, -1, FALSE },
  { DHCP4_TAG_STDA,            DHCP_OPTION_IP,     1, -1, FALSE },

  { DHCP4_TAG_CLASSLESS_ROUTE, DHCP_OPTION_INT8,   5, -1, FALSE },
};

/**
  Binary search the DhcpOptionFormats array to find the format
  information about a specific option.

  @param[in]  Tag                    The option's tag.

  @return The point to the option's format, NULL if not found.

**/
DHCP_OPTION_FORMAT *
DhcpFindOptionFormat (
  IN UINT8  Tag
  )
{
  INTN  Left;
  INTN  Right;
  INTN  Middle;

  Left  = 0;
  Right = sizeof (DhcpOptionFormats) / sizeof (DHCP_OPTION_FORMAT) - 1;

  while (Right >= Left) {
    Middle = (Left + Right) / 2;

    if (Tag == DhcpOptionFormats[Middle].Tag) {
      return &DhcpOptionFormats[Middle];
    }

    if (Tag < DhcpOptionFormats[Middle].Tag) {
      Right = Middle - 1;
    } else {
      Left = Middle + 1;
    }
  }

  return NULL;
}

/**
  Validate whether a single DHCP option is valid according to its format.

  @param[in]  Format                 The option's format
  @param[in]  OptValue               The value of the option
  @param[in]  Len                    The length of the option value

  @retval TRUE     The option is valid.
  @retval FALSE    Otherwise.

**/
BOOLEAN
DhcpOptionIsValid (
  IN DHCP_OPTION_FORMAT  *Format,
  IN UINT8               *OptValue,
  IN INTN                Len
  )
{
  INTN  Unit;
  INTN  Occur;
  INTN  Index;

  Unit = 0;

  switch (Format->Type) {
    case DHCP_OPTION_SWITCH:
    case DHCP_OPTION_INT8:
      Unit = 1;
      break;

    case DHCP_OPTION_INT16:
      Unit = 2;
      break;

    case DHCP_OPTION_INT32:
    case DHCP_OPTION_IP:
      Unit = 4;
      break;

    case DHCP_OPTION_IPPAIR:
      Unit = 8;
      break;
  }

  ASSERT (Unit != 0);

  //
  // Validate that the option appears in the full units.
  //
  if ((Len % Unit) != 0) {
    return FALSE;
  }

  //
  // Validate the occurrence of the option unit is with in [MinOccur, MaxOccur]
  //
  Occur = Len / Unit;

  if (((Format->MinOccur != -1) && (Occur < Format->MinOccur)) ||
      ((Format->MaxOccur != -1) && (Occur > Format->MaxOccur))
      )
  {
    return FALSE;
  }

  //
  // If the option is of type switch, only 0/1 are valid values.
  //
  if (Format->Type == DHCP_OPTION_SWITCH) {
    for (Index = 0; Index < Occur; Index++) {
      if ((OptValue[Index] != 0) && (OptValue[Index] != 1)) {
        return FALSE;
      }
    }
  }

  return TRUE;
}

/**
  Extract the client interested options, all the parameters are
  converted to host byte order.

  @param[in]  Tag                    The DHCP option tag
  @param[in]  Len                    The length of the option
  @param[in]  Data                   The value of the DHCP option
  @param[out] Para                   The variable to save the interested parameter

  @retval EFI_SUCCESS            The DHCP option is successfully extracted.
  @retval EFI_INVALID_PARAMETER  The DHCP option is mal-formatted

**/
EFI_STATUS
DhcpGetParameter (
  IN  UINT8           Tag,
  IN  INTN            Len,
  IN  UINT8           *Data,
  OUT DHCP_PARAMETER  *Para
  )
{
  switch (Tag) {
    case DHCP4_TAG_NETMASK:
      Para->NetMask = NetGetUint32 (Data);
      break;

    case DHCP4_TAG_ROUTER:
      //
      // Return the first router to consumer which is the preferred one
      //
      Para->Router = NetGetUint32 (Data);
      break;

    case DHCP4_TAG_LEASE:
      Para->Lease = NetGetUint32 (Data);
      break;

    case DHCP4_TAG_OVERLOAD:
      Para->Overload = *Data;

      if ((Para->Overload < 1) || (Para->Overload > 3)) {
        return EFI_INVALID_PARAMETER;
      }

      break;

    case DHCP4_TAG_MSG_TYPE:
      Para->DhcpType = *Data;

      if ((Para->DhcpType < 1) || (Para->DhcpType > 9)) {
        return EFI_INVALID_PARAMETER;
      }

      break;

    case DHCP4_TAG_SERVER_ID:
      Para->ServerId = NetGetUint32 (Data);
      break;

    case DHCP4_TAG_T1:
      Para->T1 = NetGetUint32 (Data);
      break;

    case DHCP4_TAG_T2:
      Para->T2 = NetGetUint32 (Data);
      break;
  }

  return EFI_SUCCESS;
}

/**
  Inspect all the options in a single buffer. DHCP options may be contained
  in several buffers, such as the BOOTP options filed, boot file or server
  name. Each option buffer is required to end with DHCP4_TAG_EOP.

  @param[in]  Buffer                 The buffer which contains DHCP options
  @param[in]  BufLen                 The length of the buffer
  @param[in]  Check                  The callback function for each option found
  @param[in]  Context                The opaque parameter for the Check
  @param[out] Overload               Variable to save the value of DHCP4_TAG_OVERLOAD
                                     option.

  @retval EFI_SUCCESS            All the options are valid
  @retval EFI_INVALID_PARAMETER  The options are mal-formatted.

**/
EFI_STATUS
DhcpIterateBufferOptions (
  IN  UINT8              *Buffer,
  IN  INTN               BufLen,
  IN  DHCP_CHECK_OPTION  Check             OPTIONAL,
  IN  VOID               *Context,
  OUT UINT8              *Overload         OPTIONAL
  )
{
  INTN   Cur;
  UINT8  Tag;
  UINT8  Len;

  Cur = 0;

  while (Cur < BufLen) {
    Tag = Buffer[Cur];

    if (Tag == DHCP4_TAG_PAD) {
      Cur++;
      continue;
    } else if (Tag == DHCP4_TAG_EOP) {
      return EFI_SUCCESS;
    }

    Cur++;

    if (Cur == BufLen) {
      return EFI_INVALID_PARAMETER;
    }

    Len = Buffer[Cur++];

    if (Cur + Len > BufLen) {
      return EFI_INVALID_PARAMETER;
    }

    if ((Tag == DHCP4_TAG_OVERLOAD) && (Overload != NULL)) {
      if (Len != 1) {
        return EFI_INVALID_PARAMETER;
      }

      *Overload = Buffer[Cur];
    }

    if ((Check != NULL) && EFI_ERROR (Check (Tag, Len, Buffer + Cur, Context))) {
      return EFI_INVALID_PARAMETER;
    }

    Cur += Len;
  }

  //
  // Each option buffer is expected to end with an EOP
  //
  return EFI_INVALID_PARAMETER;
}

/**
  Iterate through a DHCP message to visit each option. First inspect
  all the options in the OPTION field. Then if overloaded, inspect
  the options in FILENAME and SERVERNAME fields. One option may be
  encoded in several places. See RFC 3396 Encoding Long Options in DHCP

  @param[in]  Packet                 The DHCP packet to check the options for
  @param[in]  Check                  The callback function to be called for each option
                                     found
  @param[in]  Context                The opaque parameter for Check

  @retval EFI_SUCCESS            The DHCP packet's options are well formatted
  @retval EFI_INVALID_PARAMETER  The DHCP packet's options are not well formatted

**/
EFI_STATUS
DhcpIterateOptions (
  IN  EFI_DHCP4_PACKET   *Packet,
  IN  DHCP_CHECK_OPTION  Check         OPTIONAL,
  IN  VOID               *Context
  )
{
  EFI_STATUS  Status;
  UINT8       Overload;

  Overload = 0;

  Status = DhcpIterateBufferOptions (
             Packet->Dhcp4.Option,
             Packet->Length - sizeof (EFI_DHCP4_HEADER) - sizeof (UINT32),
             Check,
             Context,
             &Overload
             );

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

  if ((Overload == DHCP_OVERLOAD_FILENAME) || (Overload == DHCP_OVERLOAD_BOTH)) {
    Status = DhcpIterateBufferOptions (
               (UINT8 *)Packet->Dhcp4.Header.BootFileName,
               128,
               Check,
               Context,
               NULL
               );

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

  if ((Overload == DHCP_OVERLOAD_SVRNAME) || (Overload == DHCP_OVERLOAD_BOTH)) {
    Status = DhcpIterateBufferOptions (
               (UINT8 *)Packet->Dhcp4.Header.ServerName,
               64,
               Check,
               Context,
               NULL
               );

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

  return EFI_SUCCESS;
}

/**
  Call back function to DhcpIterateOptions to compute each option's
  length. It just adds the data length of all the occurrences of this
  Tag. Context is an array of 256 DHCP_OPTION_COUNT.

  @param[in]  Tag                    The current option to check
  @param[in]  Len                    The length of the option data
  @param[in]  Data                   The option data
  @param[in]  Context                The context, which is a array of 256
                                     DHCP_OPTION_COUNT.

  @retval EFI_SUCCESS            It always returns EFI_SUCCESS.

**/
EFI_STATUS
DhcpGetOptionLen (
  IN UINT8  Tag,
  IN UINT8  Len,
  IN UINT8  *Data,
  IN VOID   *Context
  )
{
  DHCP_OPTION_COUNT  *OpCount;

  OpCount             = (DHCP_OPTION_COUNT *)Context;
  OpCount[Tag].Offset = (UINT16)(OpCount[Tag].Offset + Len);

  return EFI_SUCCESS;
}

/**
  Call back function to DhcpIterateOptions to consolidate each option's
  data. There are maybe several occurrence of the same option.

  @param[in]  Tag                    The option to consolidate its data
  @param[in]  Len                    The length of option data
  @param[in]  Data                   The data of the option's current occurrence
  @param[in]  Context                The context, which is DHCP_OPTION_CONTEXT. This
                                     array is  just a wrap to pass THREE parameters.

  @retval EFI_SUCCESS            It always returns EFI_SUCCESS

**/
EFI_STATUS
DhcpFillOption (
  IN UINT8  Tag,
  IN UINT8  Len,
  IN UINT8  *Data,
  IN VOID   *Context
  )
{
  DHCP_OPTION_CONTEXT  *OptContext;
  DHCP_OPTION_COUNT    *OptCount;
  DHCP_OPTION          *Options;
  UINT8                *Buf;
  UINT8                Index;

  OptContext = (DHCP_OPTION_CONTEXT *)Context;

  OptCount = OptContext->OpCount;
  Index    = OptCount[Tag].Index;
  Options  = OptContext->Options;
  Buf      = OptContext->Buf;

  if (Options[Index].Data == NULL) {
    Options[Index].Tag  = Tag;
    Options[Index].Data = Buf + OptCount[Tag].Offset;
  }

  CopyMem (Buf + OptCount[Tag].Offset, Data, Len);

  OptCount[Tag].Offset = (UINT16)(OptCount[Tag].Offset + Len);
  Options[Index].Len   = (UINT16)(Options[Index].Len + Len);
  return EFI_SUCCESS;
}

/**
  Parse the options of a DHCP packet. It supports RFC 3396: Encoding
  Long Options in DHCP. That is, it will combine all the option value
  of all the occurrences of each option.
  A little bit of implementation:
  It adopts the "Key indexed counting" algorithm. First, it allocates
  an array of 256 DHCP_OPTION_COUNTs because DHCP option tag is encoded
  as a UINT8. It then iterates the DHCP packet to get data length of
  each option by calling DhcpIterOptions with DhcpGetOptionLen. Now, it
  knows the number of present options and their length. It allocates a
  array of DHCP_OPTION and a continuous buffer after the array to put
  all the options' data. Each option's data is pointed to by the Data
  field in DHCP_OPTION structure. At last, it call DhcpIterateOptions
  with DhcpFillOption to fill each option's data to its position in the
  buffer.

  @param[in]  Packet                 The DHCP packet to parse the options
  @param[out] Count                  The number of valid dhcp options present in the
                                     packet
  @param[out] OptionPoint            The array that contains the DHCP options. Caller
                                     should free it.

  @retval EFI_NOT_FOUND          Cannot find any option.
  @retval EFI_OUT_OF_RESOURCES   Failed to allocate memory to parse the packet.
  @retval EFI_INVALID_PARAMETER  The options are mal-formatted
  @retval EFI_SUCCESS            The options are parsed into OptionPoint

**/
EFI_STATUS
DhcpParseOption (
  IN  EFI_DHCP4_PACKET  *Packet,
  OUT INTN              *Count,
  OUT DHCP_OPTION       **OptionPoint
  )
{
  DHCP_OPTION_CONTEXT  Context;
  DHCP_OPTION          *Options;
  DHCP_OPTION_COUNT    *OptCount;
  EFI_STATUS           Status;
  UINT16               TotalLen;
  INTN                 OptNum;
  INTN                 Index;

  ASSERT ((Count != NULL) && (OptionPoint != NULL));

  //
  // First compute how many options and how long each option is
  // with the "Key indexed counting" algorithms.
  //
  OptCount = AllocateZeroPool (DHCP_MAX_OPTIONS * sizeof (DHCP_OPTION_COUNT));

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

  Status = DhcpIterateOptions (Packet, DhcpGetOptionLen, OptCount);

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

  //
  // Before the loop, Offset is the length of the option. After loop,
  // OptCount[Index].Offset specifies the offset into the continuous
  // option value buffer to put the data.
  //
  TotalLen = 0;
  OptNum   = 0;

  for (Index = 0; Index < DHCP_MAX_OPTIONS; Index++) {
    if (OptCount[Index].Offset != 0) {
      OptCount[Index].Index = (UINT8)OptNum;

      TotalLen               = (UINT16)(TotalLen + OptCount[Index].Offset);
      OptCount[Index].Offset = (UINT16)(TotalLen - OptCount[Index].Offset);

      OptNum++;
    }
  }

  *Count       = OptNum;
  *OptionPoint = NULL;

  if (OptNum == 0) {
    goto ON_EXIT;
  }

  //
  // Allocate a buffer to hold the DHCP options, and after that, a
  // continuous buffer to put all the options' data.
  //
  Options = AllocateZeroPool ((UINTN)(OptNum * sizeof (DHCP_OPTION)) + TotalLen);

  if (Options == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_EXIT;
  }

  Context.OpCount = OptCount;
  Context.Options = Options;
  Context.Buf     = (UINT8 *)(Options + OptNum);

  Status = DhcpIterateOptions (Packet, DhcpFillOption, &Context);

  if (EFI_ERROR (Status)) {
    FreePool (Options);
    goto ON_EXIT;
  }

  *OptionPoint = Options;

ON_EXIT:
  FreePool (OptCount);
  return Status;
}

/**
  Validate the packet's options. If necessary, allocate
  and fill in the interested parameters.

  @param[in]  Packet                 The packet to validate the options
  @param[out] Para                   The variable to save the DHCP parameters.

  @retval EFI_OUT_OF_RESOURCES   Failed to allocate memory to validate the packet.
  @retval EFI_INVALID_PARAMETER  The options are mal-formatted
  @retval EFI_SUCCESS            The options are parsed into OptionPoint

**/
EFI_STATUS
DhcpValidateOptions (
  IN  EFI_DHCP4_PACKET  *Packet,
  OUT DHCP_PARAMETER    **Para       OPTIONAL
  )
{
  DHCP_PARAMETER      Parameter;
  DHCP_OPTION_FORMAT  *Format;
  DHCP_OPTION         *AllOption;
  DHCP_OPTION         *Option;
  EFI_STATUS          Status;
  BOOLEAN             Updated;
  INTN                Count;
  INTN                Index;

  if (Para != NULL) {
    *Para = NULL;
  }

  AllOption = NULL;

  Status = DhcpParseOption (Packet, &Count, &AllOption);
  if (EFI_ERROR (Status) || (Count == 0)) {
    return Status;
  }

  ASSERT (AllOption != NULL);

  Updated = FALSE;
  ZeroMem (&Parameter, sizeof (Parameter));

  for (Index = 0; Index < Count; Index++) {
    Option = &AllOption[Index];

    //
    // Find the format of the option then validate it.
    //
    Format = DhcpFindOptionFormat (Option->Tag);

    if (Format == NULL) {
      continue;
    }

    if (!DhcpOptionIsValid (Format, Option->Data, Option->Len)) {
      Status = EFI_INVALID_PARAMETER;
      goto ON_EXIT;
    }

    //
    // Get the client interested parameters
    //
    if (Format->Alert && (Para != NULL)) {
      Updated = TRUE;
      Status  = DhcpGetParameter (Option->Tag, Option->Len, Option->Data, &Parameter);

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

  if (Updated && (Para != NULL)) {
    *Para = AllocateCopyPool (sizeof (DHCP_PARAMETER), &Parameter);
    if (*Para == NULL) {
      Status = EFI_OUT_OF_RESOURCES;
      goto ON_EXIT;
    }
  }

ON_EXIT:
  FreePool (AllOption);
  return Status;
}

/**
  Append an option to the memory, if the option is longer than
  255 bytes, splits it into several options.

  @param[out] Buf                    The buffer to append the option to
  @param[in]  Tag                    The option's tag
  @param[in]  DataLen                The length of the option's data
  @param[in]  Data                   The option's data

  @return The position to append the next option

**/
UINT8 *
DhcpAppendOption (
  OUT UINT8   *Buf,
  IN  UINT8   Tag,
  IN  UINT16  DataLen,
  IN  UINT8   *Data
  )
{
  INTN  Index;
  INTN  Len;

  ASSERT (DataLen != 0);

  for (Index = 0; Index < (DataLen + 254) / 255; Index++) {
    Len = MIN (255, DataLen - Index * 255);

    *(Buf++) = Tag;
    *(Buf++) = (UINT8)Len;
    CopyMem (Buf, Data + Index * 255, (UINTN)Len);

    Buf += Len;
  }

  return Buf;
}

/**
  Build a new DHCP packet from a seed packet. Options may be deleted or
  appended. The caller should free the NewPacket when finished using it.

  @param[in]  SeedPacket             The seed packet to start with
  @param[in]  DeleteCount            The number of options to delete
  @param[in]  DeleteList             The options to delete from the packet
  @param[in]  AppendCount            The number of options to append
  @param[in]  AppendList             The options to append to the packet
  @param[out] NewPacket              The new packet, allocated and built by this
                                     function.

  @retval EFI_OUT_OF_RESOURCES   Failed to allocate memory
  @retval EFI_INVALID_PARAMETER  The options in SeekPacket are mal-formatted
  @retval EFI_SUCCESS            The packet is build.

**/
EFI_STATUS
DhcpBuild (
  IN  EFI_DHCP4_PACKET         *SeedPacket,
  IN  UINT32                   DeleteCount,
  IN  UINT8                    *DeleteList     OPTIONAL,
  IN  UINT32                   AppendCount,
  IN  EFI_DHCP4_PACKET_OPTION  *AppendList[]   OPTIONAL,
  OUT EFI_DHCP4_PACKET         **NewPacket
  )
{
  DHCP_OPTION       *Mark;
  DHCP_OPTION       *SeedOptions;
  EFI_DHCP4_PACKET  *Packet;
  EFI_STATUS        Status;
  INTN              Count;
  UINT32            Index;
  UINT32            Len;
  UINT8             *Buf;

  //
  // Use an array of DHCP_OPTION to mark the existence
  // and position of each valid options.
  //
  Mark = AllocatePool (sizeof (DHCP_OPTION) * DHCP_MAX_OPTIONS);

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

  for (Index = 0; Index < DHCP_MAX_OPTIONS; Index++) {
    Mark[Index].Tag = (UINT8)Index;
    Mark[Index].Len = 0;
  }

  //
  // Get list of the options from the seed packet, then put
  // them to the mark array according to their tags.
  //
  SeedOptions = NULL;
  Status      = DhcpParseOption (SeedPacket, &Count, &SeedOptions);

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

  if (SeedOptions != NULL) {
    for (Index = 0; Index < (UINT32)Count; Index++) {
      Mark[SeedOptions[Index].Tag] = SeedOptions[Index];
    }
  }

  //
  // Mark the option's length is zero if it is in the DeleteList.
  //
  for (Index = 0; Index < DeleteCount; Index++) {
    Mark[DeleteList[Index]].Len = 0;
  }

  //
  // Add or replace the option if it is in the append list.
  //
  for (Index = 0; Index < AppendCount; Index++) {
    Mark[AppendList[Index]->OpCode].Len  = AppendList[Index]->Length;
    Mark[AppendList[Index]->OpCode].Data = AppendList[Index]->Data;
  }

  //
  // compute the new packet length. No need to add 1 byte for
  // EOP option since EFI_DHCP4_PACKET includes one extra byte
  // for option. It is necessary to split the option if it is
  // longer than 255 bytes.
  //
  Len = sizeof (EFI_DHCP4_PACKET);

  for (Index = 0; Index < DHCP_MAX_OPTIONS; Index++) {
    if (Mark[Index].Len != 0) {
      Len += ((Mark[Index].Len + 254) / 255) * 2 + Mark[Index].Len;
    }
  }

  Status = EFI_OUT_OF_RESOURCES;
  Packet = (EFI_DHCP4_PACKET *)AllocatePool (Len);

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

  Packet->Size   = Len;
  Packet->Length = 0;
  CopyMem (&Packet->Dhcp4.Header, &SeedPacket->Dhcp4.Header, sizeof (Packet->Dhcp4.Header));
  Packet->Dhcp4.Magik = DHCP_OPTION_MAGIC;
  Buf                 = Packet->Dhcp4.Option;

  for (Index = 0; Index < DHCP_MAX_OPTIONS; Index++) {
    if (Mark[Index].Len != 0) {
      Buf = DhcpAppendOption (Buf, Mark[Index].Tag, Mark[Index].Len, Mark[Index].Data);
    }
  }

  *(Buf++)       = DHCP4_TAG_EOP;
  Packet->Length = sizeof (EFI_DHCP4_HEADER) + sizeof (UINT32)
                   + (UINT32)(Buf - Packet->Dhcp4.Option);

  *NewPacket = Packet;
  Status     = EFI_SUCCESS;

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

  FreePool (Mark);
  return Status;
}
