/** @file
  Mtftp6 option parse functions implementation.

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

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

**/

#include "Mtftp6Impl.h"

CHAR8  *mMtftp6SupportedOptions[MTFTP6_SUPPORTED_OPTIONS_NUM] = {
  "blksize",
  "windowsize",
  "timeout",
  "tsize",
  "multicast"
};

/**
  Parse the NULL terminated ASCII string of multicast option.

  @param[in]  Str           The pointer to the Ascii string of multicast option.
  @param[in]  ExtInfo       The pointer to the option information to be filled.

  @retval EFI_SUCCESS            Parse the multicast option successfully.
  @retval EFI_INVALID_PARAMETER  The string is malformatted.
  @retval EFI_OUT_OF_RESOURCES   Failed to perform the operation due to lack of
                                 resources.

**/
EFI_STATUS
Mtftp6ParseMcastOption (
  IN UINT8                   *Str,
  IN MTFTP6_EXT_OPTION_INFO  *ExtInfo
  )
{
  EFI_STATUS  Status;
  UINT32      Num;
  CHAR8       *Ip6Str;
  CHAR8       *TempStr;

  //
  // The multicast option is formatted like "addr,port,mc"
  // The server can also omit the ip and port, use ",,1"
  //
  if (*Str == ',') {
    ZeroMem (&ExtInfo->McastIp, sizeof (EFI_IPv6_ADDRESS));
  } else {
    Ip6Str = (CHAR8 *)AllocateCopyPool (AsciiStrSize ((CHAR8 *)Str), Str);
    if (Ip6Str == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    //
    // The IPv6 address locates before comma in the input Str.
    //
    TempStr = Ip6Str;
    while ((*TempStr != '\0') && (*TempStr != ',')) {
      TempStr++;
    }

    *TempStr = '\0';

    Status = NetLibAsciiStrToIp6 (Ip6Str, &ExtInfo->McastIp);
    FreePool (Ip6Str);

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

    while ((*Str != '\0') && (*Str != ',')) {
      Str++;
    }
  }

  if (*Str != ',') {
    return EFI_INVALID_PARAMETER;
  }

  Str++;

  //
  // Convert the port setting. the server can send us a port number or
  // empty string. such as the port in ",,1"
  //
  if (*Str == ',') {
    ExtInfo->McastPort = 0;
  } else {
    Num = (UINT32)AsciiStrDecimalToUintn ((CHAR8 *)Str);

    if (Num > 65535) {
      return EFI_INVALID_PARAMETER;
    }

    ExtInfo->McastPort = (UINT16)Num;

    while (NET_IS_DIGIT (*Str)) {
      Str++;
    }
  }

  if (*Str != ',') {
    return EFI_INVALID_PARAMETER;
  }

  Str++;

  //
  // Check the master/slave setting, 1 for master, 0 for slave.
  //
  Num = (UINT32)AsciiStrDecimalToUintn ((CHAR8 *)Str);

  if ((Num != 0) && (Num != 1)) {
    return EFI_INVALID_PARAMETER;
  }

  ExtInfo->IsMaster = (BOOLEAN)(Num == 1);

  while (NET_IS_DIGIT (*Str)) {
    Str++;
  }

  if (*Str != '\0') {
    return EFI_INVALID_PARAMETER;
  }

  return EFI_SUCCESS;
}

/**
  Parse the MTFTP6 extension options.

  @param[in]  Options       The pointer to the extension options list.
  @param[in]  Count         The num of the extension options.
  @param[in]  IsRequest     If FALSE, the extension options is included
                            by a request packet.
  @param[in]  Operation     The current performed operation.
  @param[in]  ExtInfo       The pointer to the option information to be filled.

  @retval EFI_SUCCESS            Parse the multicast option successfully.
  @retval EFI_INVALID_PARAMETER  There is one option is malformatted at least.
  @retval EFI_UNSUPPORTED        There is one option is not supported at least.

**/
EFI_STATUS
Mtftp6ParseExtensionOption (
  IN EFI_MTFTP6_OPTION       *Options,
  IN UINT32                  Count,
  IN BOOLEAN                 IsRequest,
  IN UINT16                  Operation,
  IN MTFTP6_EXT_OPTION_INFO  *ExtInfo
  )
{
  EFI_STATUS         Status;
  EFI_MTFTP6_OPTION  *Opt;
  UINT32             Index;
  UINT32             Value;

  ExtInfo->BitMap = 0;

  for (Index = 0; Index < Count; Index++) {
    Opt = Options + Index;

    if ((Opt->OptionStr == NULL) || (Opt->ValueStr == NULL)) {
      return EFI_INVALID_PARAMETER;
    }

    if (AsciiStriCmp ((CHAR8 *)Opt->OptionStr, "blksize") == 0) {
      //
      // block size option, valid value is between [8, 65464]
      //
      Value = (UINT32)AsciiStrDecimalToUintn ((CHAR8 *)Opt->ValueStr);

      if ((Value < 8) || (Value > 65464)) {
        return EFI_INVALID_PARAMETER;
      }

      ExtInfo->BlkSize = (UINT16)Value;
      ExtInfo->BitMap |= MTFTP6_OPT_BLKSIZE_BIT;
    } else if (AsciiStriCmp ((CHAR8 *)Opt->OptionStr, "timeout") == 0) {
      //
      // timeout option, valid value is between [1, 255]
      //
      Value = (UINT32)AsciiStrDecimalToUintn ((CHAR8 *)Opt->ValueStr);

      if ((Value < 1) || (Value > 255)) {
        return EFI_INVALID_PARAMETER;
      }

      ExtInfo->Timeout = (UINT8)Value;
      ExtInfo->BitMap |= MTFTP6_OPT_TIMEOUT_BIT;
    } else if (AsciiStriCmp ((CHAR8 *)Opt->OptionStr, "tsize") == 0) {
      //
      // tsize option, the biggest transfer supported is 4GB with block size option
      //
      ExtInfo->Tsize   = (UINT32)AsciiStrDecimalToUintn ((CHAR8 *)Opt->ValueStr);
      ExtInfo->BitMap |= MTFTP6_OPT_TSIZE_BIT;
    } else if (AsciiStriCmp ((CHAR8 *)Opt->OptionStr, "multicast") == 0) {
      //
      // Multicast option, if it is a request, the value must be a zero string,
      // otherwise, it must be like "addr,port,mc" string, mc indicates master.
      //
      if (!IsRequest) {
        Status = Mtftp6ParseMcastOption (Opt->ValueStr, ExtInfo);

        if (EFI_ERROR (Status)) {
          return Status;
        }
      } else if (*(Opt->ValueStr) != '\0') {
        return EFI_INVALID_PARAMETER;
      }

      ExtInfo->BitMap |= MTFTP6_OPT_MCAST_BIT;
    } else if (AsciiStriCmp ((CHAR8 *)Opt->OptionStr, "windowsize") == 0) {
      if (Operation == EFI_MTFTP6_OPCODE_WRQ) {
        //
        // Currently, windowsize is not supported in the write operation.
        //
        return EFI_UNSUPPORTED;
      }

      Value = (UINT32)AsciiStrDecimalToUintn ((CHAR8 *)Opt->ValueStr);

      if ((Value < 1)) {
        return EFI_INVALID_PARAMETER;
      }

      ExtInfo->WindowSize = (UINT16)Value;
      ExtInfo->BitMap    |= MTFTP6_OPT_WINDOWSIZE_BIT;
    } else if (IsRequest) {
      //
      // If it's a request, unsupported; else if it's a reply, ignore.
      //
      return EFI_UNSUPPORTED;
    }
  }

  return EFI_SUCCESS;
}

/**
  Go through the packet to fill the options array with the start
  addresses of each MTFTP option name/value pair.

  @param[in]      Packet                 The packet to be checked.
  @param[in]      PacketLen              The length of the packet.
  @param[in, out] Count                  The num of the Options on input.
                                         The actual one on output.
  @param[in]      Options                The option array to be filled.
                                         It is optional.

  @retval EFI_SUCCESS            The packet has been parsed successfully.
  @retval EFI_INVALID_PARAMETER  The packet is malformatted.
  @retval EFI_BUFFER_TOO_SMALL   The Options array is too small.
  @retval EFI_PROTOCOL_ERROR     An unexpected MTFTPv6 packet was received.

**/
EFI_STATUS
Mtftp6ParsePacketOption (
  IN     EFI_MTFTP6_PACKET  *Packet,
  IN     UINT32             PacketLen,
  IN OUT UINT32             *Count,
  IN     EFI_MTFTP6_OPTION  *Options          OPTIONAL
  )
{
  UINT8  *Cur;
  UINT8  *Last;
  UINT8  Num;
  UINT8  *Name;
  UINT8  *Value;

  Num  = 0;
  Cur  = (UINT8 *)Packet + MTFTP6_OPCODE_LEN;
  Last = (UINT8 *)Packet + PacketLen - 1;

  //
  // process option name and value pairs.
  // The last byte is always zero.
  //
  while (Cur < Last) {
    Name = Cur;

    while (*Cur != 0) {
      Cur++;
    }

    if (Cur == Last) {
      return EFI_PROTOCOL_ERROR;
    }

    Value = ++Cur;

    while (*Cur != 0) {
      Cur++;
    }

    Num++;

    if ((Options != NULL) && (Num <= *Count)) {
      Options[Num - 1].OptionStr = Name;
      Options[Num - 1].ValueStr  = Value;
    }

    Cur++;
  }

  //
  // Return buffer too small if the buffer passed-in isn't enough.
  //
  if ((*Count < Num) || (Options == NULL)) {
    *Count = Num;
    return EFI_BUFFER_TOO_SMALL;
  }

  *Count = Num;
  return EFI_SUCCESS;
}

/**
  Go through the packet, generate option list array and fill it
  by the result of parse options.

  @param[in]      Packet                 The packet to be checked.
  @param[in]      PacketLen              The length of the packet.
  @param[in, out] OptionCount            The num of the Options on input.
                                         The actual one on output.
  @param[out]     OptionList             The option list array to be generated
                                         and filled. It is optional.

  @retval EFI_SUCCESS            The packet has been parsed successfully.
  @retval EFI_INVALID_PARAMETER  The packet is malformatted.
  @retval EFI_PROTOCOL_ERROR     There is one option is malformatted at least.
  @retval EFI_NOT_FOUND          The packet has no options.
  @retval EFI_OUT_OF_RESOURCES   Failed to allocate memory for the array.
  @retval EFI_BUFFER_TOO_SMALL   The size of option list array is too small.

**/
EFI_STATUS
Mtftp6ParseStart (
  IN     EFI_MTFTP6_PACKET  *Packet,
  IN     UINT32             PacketLen,
  IN OUT UINT32             *OptionCount,
  OUT EFI_MTFTP6_OPTION     **OptionList          OPTIONAL
  )
{
  EFI_STATUS  Status;

  if ((PacketLen == 0) || (Packet == NULL) || (OptionCount == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  *OptionCount = 0;

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

  if (NTOHS (Packet->OpCode) != EFI_MTFTP6_OPCODE_OACK) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // The last byte must be zero to terminate the options.
  //
  if (*((UINT8 *)Packet + PacketLen - 1) != 0) {
    return EFI_PROTOCOL_ERROR;
  }

  //
  // Parse packet with NULL buffer for the first time to get the number
  // of options in the packet.
  //
  Status = Mtftp6ParsePacketOption (Packet, PacketLen, OptionCount, NULL);

  if (Status != EFI_BUFFER_TOO_SMALL) {
    return Status;
  }

  //
  // Return not found if there is no option parsed.
  //
  if (*OptionCount == 0) {
    return EFI_NOT_FOUND;
  }

  //
  // Only need parse out the number of options.
  //
  if (OptionList == NULL) {
    return EFI_SUCCESS;
  }

  //
  // Allocate the buffer according to the option number parsed before.
  //
  *OptionList = AllocateZeroPool (*OptionCount * sizeof (EFI_MTFTP6_OPTION));

  if (*OptionList == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Parse packet with allocated buffer for the second time to fill the pointer array
  // of the options in the packet.
  //
  Status = Mtftp6ParsePacketOption (Packet, PacketLen, OptionCount, *OptionList);

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

  return EFI_SUCCESS;
}
