/** @file
  Functions implementation related with Mtftp for UefiPxeBc Driver.

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

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

**/

#include "PxeBcImpl.h"

CHAR8  *mMtftpOptions[PXE_MTFTP_OPTION_MAXIMUM_INDEX] = {
  "blksize",
  "timeout",
  "tsize",
  "multicast",
  "windowsize"
};

/**
  This is a callback function when packets are received or transmitted in Mtftp driver.

  A callback function that is provided by the caller to intercept
  the EFI_MTFTP6_OPCODE_DATA or EFI_MTFTP6_OPCODE_DATA8 packets processed in the
  EFI_MTFTP6_PROTOCOL.ReadFile() function, and alternatively to intercept
  EFI_MTFTP6_OPCODE_OACK or EFI_MTFTP6_OPCODE_ERROR packets during a call to
  EFI_MTFTP6_PROTOCOL.ReadFile(), WriteFile() or ReadDirectory().

  @param[in]  This           Pointer to EFI_MTFTP6_PROTOCOL.
  @param[in]  Token          Pointer to EFI_MTFTP6_TOKEN.
  @param[in]  PacketLen      Length of EFI_MTFTP6_PACKET.
  @param[in]  Packet         Pointer to EFI_MTFTP6_PACKET to be checked.

  @retval EFI_SUCCESS    The current operation succeeded.
  @retval EFI_ABORTED    Abort the current transfer process.

**/
EFI_STATUS
EFIAPI
PxeBcMtftp6CheckPacket (
  IN EFI_MTFTP6_PROTOCOL  *This,
  IN EFI_MTFTP6_TOKEN     *Token,
  IN UINT16               PacketLen,
  IN EFI_MTFTP6_PACKET    *Packet
  )
{
  PXEBC_PRIVATE_DATA                   *Private;
  EFI_PXE_BASE_CODE_CALLBACK_PROTOCOL  *Callback;
  EFI_STATUS                           Status;

  Private  = (PXEBC_PRIVATE_DATA *)Token->Context;
  Callback = Private->PxeBcCallback;
  Status   = EFI_SUCCESS;

  if (Packet->OpCode == EFI_MTFTP6_OPCODE_ERROR) {
    //
    // Store the tftp error message into mode data and set the received flag.
    //
    Private->Mode.TftpErrorReceived   = TRUE;
    Private->Mode.TftpError.ErrorCode = (UINT8)Packet->Error.ErrorCode;
    AsciiStrnCpyS (
      Private->Mode.TftpError.ErrorString,
      PXE_MTFTP_ERROR_STRING_LENGTH,
      (CHAR8 *)Packet->Error.ErrorMessage,
      PXE_MTFTP_ERROR_STRING_LENGTH - 1
      );
    Private->Mode.TftpError.ErrorString[PXE_MTFTP_ERROR_STRING_LENGTH - 1] = '\0';
  }

  if (Callback != NULL) {
    //
    // Callback to user if has when received any tftp packet.
    //
    Status = Callback->Callback (
                         Callback,
                         Private->Function,
                         TRUE,
                         PacketLen,
                         (EFI_PXE_BASE_CODE_PACKET *)Packet
                         );
    if (Status != EFI_PXE_BASE_CODE_CALLBACK_STATUS_CONTINUE) {
      //
      // User wants to abort current process if not EFI_PXE_BASE_CODE_CALLBACK_STATUS_CONTINUE.
      //
      Status = EFI_ABORTED;
    } else {
      //
      // User wants to continue current process if EFI_PXE_BASE_CODE_CALLBACK_STATUS_CONTINUE.
      //
      Status = EFI_SUCCESS;
    }
  }

  return Status;
}

/**
  This function is to get the size of a file using Tftp.

  @param[in]      Private        Pointer to PxeBc private data.
  @param[in]      Config         Pointer to EFI_MTFTP6_CONFIG_DATA.
  @param[in]      Filename       Pointer to boot file name.
  @param[in]      BlockSize      Pointer to required block size.
  @param[in]      WindowSize     Pointer to required window size.
  @param[in, out] BufferSize     Pointer to buffer size.

  @retval EFI_SUCCESS        Successfully obtained the size of file.
  @retval EFI_NOT_FOUND      Parse the tftp options failed.
  @retval EFI_DEVICE_ERROR   The network device encountered an error during this operation.
  @retval Others             Has not obtained the size of the file.

**/
EFI_STATUS
PxeBcMtftp6GetFileSize (
  IN     PXEBC_PRIVATE_DATA      *Private,
  IN     EFI_MTFTP6_CONFIG_DATA  *Config,
  IN     UINT8                   *Filename,
  IN     UINTN                   *BlockSize,
  IN     UINTN                   *WindowSize,
  IN OUT UINT64                  *BufferSize
  )
{
  EFI_MTFTP6_PROTOCOL  *Mtftp6;
  EFI_MTFTP6_OPTION    ReqOpt[3];
  EFI_MTFTP6_PACKET    *Packet;
  EFI_MTFTP6_OPTION    *Option;
  UINT32               PktLen;
  UINT8                OptBuf[PXE_MTFTP_OPTBUF_MAXNUM_INDEX];
  UINTN                OptBufSize;
  UINT32               OptCnt;
  EFI_STATUS           Status;

  *BufferSize               = 0;
  Status                    = EFI_DEVICE_ERROR;
  Mtftp6                    = Private->Mtftp6;
  Packet                    = NULL;
  Option                    = NULL;
  PktLen                    = 0;
  OptBufSize                = PXE_MTFTP_OPTBUF_MAXNUM_INDEX;
  OptCnt                    = 1;
  Config->InitialServerPort = PXEBC_BS_DOWNLOAD_PORT;

  Status = Mtftp6->Configure (Mtftp6, Config);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Build the required options for get info.
  //
  ReqOpt[0].OptionStr = (UINT8 *)mMtftpOptions[PXE_MTFTP_OPTION_TSIZE_INDEX];
  PxeBcUintnToAscDec (0, OptBuf, OptBufSize);
  ReqOpt[0].ValueStr = OptBuf;

  if (BlockSize != NULL) {
    ReqOpt[OptCnt].OptionStr = (UINT8 *)mMtftpOptions[PXE_MTFTP_OPTION_BLKSIZE_INDEX];
    ReqOpt[OptCnt].ValueStr  = (UINT8 *)(ReqOpt[OptCnt-1].ValueStr + AsciiStrLen ((CHAR8 *)ReqOpt[OptCnt-1].ValueStr) + 1);
    OptBufSize              -= (AsciiStrLen ((CHAR8 *)ReqOpt[OptCnt-1].ValueStr) + 1);
    PxeBcUintnToAscDec (*BlockSize, ReqOpt[OptCnt].ValueStr, OptBufSize);
    OptCnt++;
  }

  if (WindowSize != NULL) {
    ReqOpt[OptCnt].OptionStr = (UINT8 *)mMtftpOptions[PXE_MTFTP_OPTION_WINDOWSIZE_INDEX];
    ReqOpt[OptCnt].ValueStr  = (UINT8 *)(ReqOpt[OptCnt-1].ValueStr + AsciiStrLen ((CHAR8 *)ReqOpt[OptCnt-1].ValueStr) + 1);
    OptBufSize              -= (AsciiStrLen ((CHAR8 *)ReqOpt[OptCnt-1].ValueStr) + 1);
    PxeBcUintnToAscDec (*WindowSize, ReqOpt[OptCnt].ValueStr, OptBufSize);
    OptCnt++;
  }

  Status = Mtftp6->GetInfo (
                     Mtftp6,
                     NULL,
                     Filename,
                     NULL,
                     (UINT8)OptCnt,
                     ReqOpt,
                     &PktLen,
                     &Packet
                     );
  if (EFI_ERROR (Status)) {
    if (Status == EFI_TFTP_ERROR) {
      //
      // Store the tftp error message into mode data and set the received flag.
      //
      Private->Mode.TftpErrorReceived   = TRUE;
      Private->Mode.TftpError.ErrorCode = (UINT8)Packet->Error.ErrorCode;
      AsciiStrnCpyS (
        Private->Mode.TftpError.ErrorString,
        PXE_MTFTP_ERROR_STRING_LENGTH,
        (CHAR8 *)Packet->Error.ErrorMessage,
        PXE_MTFTP_ERROR_STRING_LENGTH - 1
        );
      Private->Mode.TftpError.ErrorString[PXE_MTFTP_ERROR_STRING_LENGTH - 1] = '\0';
    }

    goto ON_ERROR;
  }

  //
  // Parse the options in the reply packet.
  //
  OptCnt = 0;
  Status = Mtftp6->ParseOptions (
                     Mtftp6,
                     PktLen,
                     Packet,
                     (UINT32 *)&OptCnt,
                     &Option
                     );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Parse out the value of "tsize" option.
  //
  Status = EFI_NOT_FOUND;
  while (OptCnt != 0) {
    if (AsciiStrnCmp ((CHAR8 *)Option[OptCnt - 1].OptionStr, "tsize", 5) == 0) {
      *BufferSize = AsciiStrDecimalToUint64 ((CHAR8 *)(Option[OptCnt - 1].ValueStr));
      Status      = EFI_SUCCESS;
    }

    OptCnt--;
  }

  FreePool (Option);

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

  Mtftp6->Configure (Mtftp6, NULL);

  return Status;
}

/**
  This function is to get data of a file using Tftp.

  @param[in]      Private        Pointer to PxeBc private data.
  @param[in]      Config         Pointer to EFI_MTFTP6_CONFIG_DATA.
  @param[in]      Filename       Pointer to boot file name.
  @param[in]      BlockSize      Pointer to required block size.
  @param[in]      WindowSize     Pointer to required window size.
  @param[in]      BufferPtr      Pointer to buffer.
  @param[in, out] BufferSize     Pointer to buffer size.
  @param[in]      DontUseBuffer  Indicates whether with a receive buffer.

  @retval EFI_SUCCESS        Successfully read the data from the special file.
  @retval EFI_DEVICE_ERROR   The network device encountered an error during this operation.
  @retval Others             Read data from file failed.

**/
EFI_STATUS
PxeBcMtftp6ReadFile (
  IN    PXEBC_PRIVATE_DATA       *Private,
  IN     EFI_MTFTP6_CONFIG_DATA  *Config,
  IN     UINT8                   *Filename,
  IN     UINTN                   *BlockSize,
  IN     UINTN                   *WindowSize,
  IN     UINT8                   *BufferPtr,
  IN OUT UINT64                  *BufferSize,
  IN     BOOLEAN                 DontUseBuffer
  )
{
  EFI_MTFTP6_PROTOCOL  *Mtftp6;
  EFI_MTFTP6_TOKEN     Token;
  EFI_MTFTP6_OPTION    ReqOpt[2];
  UINT32               OptCnt;
  UINT8                BlksizeBuf[10];
  UINT8                WindowsizeBuf[10];
  EFI_STATUS           Status;

  Status                    = EFI_DEVICE_ERROR;
  Mtftp6                    = Private->Mtftp6;
  OptCnt                    = 0;
  Config->InitialServerPort = PXEBC_BS_DOWNLOAD_PORT;

  Status = Mtftp6->Configure (Mtftp6, Config);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (BlockSize != NULL) {
    ReqOpt[OptCnt].OptionStr = (UINT8 *)mMtftpOptions[PXE_MTFTP_OPTION_BLKSIZE_INDEX];
    ReqOpt[OptCnt].ValueStr  = BlksizeBuf;
    PxeBcUintnToAscDec (*BlockSize, ReqOpt[OptCnt].ValueStr, sizeof (BlksizeBuf));
    OptCnt++;
  }

  if (WindowSize != NULL) {
    ReqOpt[OptCnt].OptionStr = (UINT8 *)mMtftpOptions[PXE_MTFTP_OPTION_WINDOWSIZE_INDEX];
    ReqOpt[OptCnt].ValueStr  = WindowsizeBuf;
    PxeBcUintnToAscDec (*WindowSize, ReqOpt[OptCnt].ValueStr, sizeof (WindowsizeBuf));
    OptCnt++;
  }

  Token.Event        = NULL;
  Token.OverrideData = NULL;
  Token.Filename     = Filename;
  Token.ModeStr      = NULL;
  Token.OptionCount  = OptCnt;
  Token.OptionList   = ReqOpt;
  Token.Context      = Private;

  if (DontUseBuffer) {
    Token.BufferSize = 0;
    Token.Buffer     = NULL;
  } else {
    Token.BufferSize = *BufferSize;
    Token.Buffer     = BufferPtr;
  }

  Token.CheckPacket     = PxeBcMtftp6CheckPacket;
  Token.TimeoutCallback = NULL;
  Token.PacketNeeded    = NULL;

  Status = Mtftp6->ReadFile (Mtftp6, &Token);
  //
  // Get the real size of received buffer.
  //
  *BufferSize = Token.BufferSize;

  Mtftp6->Configure (Mtftp6, NULL);

  return Status;
}

/**
  This function is used to write the data of a file using Tftp.

  @param[in]       Private        Pointer to PxeBc private data.
  @param[in]       Config         Pointer to EFI_MTFTP6_CONFIG_DATA.
  @param[in]       Filename       Pointer to boot file name.
  @param[in]       Overwrite      Indicate whether with overwrite attribute.
  @param[in]       BlockSize      Pointer to required block size.
  @param[in]       BufferPtr      Pointer to buffer.
  @param[in, out]  BufferSize     Pointer to buffer size.

  @retval EFI_SUCCESS        Successfully wrote the data into a special file.
  @retval EFI_DEVICE_ERROR   The network device encountered an error during this operation.
  @retval other              Write data into file failed.

**/
EFI_STATUS
PxeBcMtftp6WriteFile (
  IN     PXEBC_PRIVATE_DATA      *Private,
  IN     EFI_MTFTP6_CONFIG_DATA  *Config,
  IN     UINT8                   *Filename,
  IN     BOOLEAN                 Overwrite,
  IN     UINTN                   *BlockSize,
  IN     UINT8                   *BufferPtr,
  IN OUT UINT64                  *BufferSize
  )
{
  EFI_MTFTP6_PROTOCOL  *Mtftp6;
  EFI_MTFTP6_TOKEN     Token;
  EFI_MTFTP6_OPTION    ReqOpt[1];
  UINT32               OptCnt;
  UINT8                OptBuf[128];
  EFI_STATUS           Status;

  Status                    = EFI_DEVICE_ERROR;
  Mtftp6                    = Private->Mtftp6;
  OptCnt                    = 0;
  Config->InitialServerPort = PXEBC_BS_DOWNLOAD_PORT;

  Status = Mtftp6->Configure (Mtftp6, Config);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (BlockSize != NULL) {
    ReqOpt[0].OptionStr = (UINT8 *)mMtftpOptions[PXE_MTFTP_OPTION_BLKSIZE_INDEX];
    ReqOpt[0].ValueStr  = OptBuf;
    PxeBcUintnToAscDec (*BlockSize, ReqOpt[0].ValueStr, PXE_MTFTP_OPTBUF_MAXNUM_INDEX);
    OptCnt++;
  }

  Token.Event           = NULL;
  Token.OverrideData    = NULL;
  Token.Filename        = Filename;
  Token.ModeStr         = NULL;
  Token.OptionCount     = OptCnt;
  Token.OptionList      = ReqOpt;
  Token.BufferSize      = *BufferSize;
  Token.Buffer          = BufferPtr;
  Token.CheckPacket     = PxeBcMtftp6CheckPacket;
  Token.TimeoutCallback = NULL;
  Token.PacketNeeded    = NULL;

  Status = Mtftp6->WriteFile (Mtftp6, &Token);
  //
  // Get the real size of transmitted buffer.
  //
  *BufferSize = Token.BufferSize;

  Mtftp6->Configure (Mtftp6, NULL);

  return Status;
}

/**
  This function is to read the data (file) from a directory using Tftp.

  @param[in]       Private        Pointer to PxeBc private data.
  @param[in]       Config         Pointer to EFI_MTFTP6_CONFIG_DATA.
  @param[in]       Filename       Pointer to boot file name.
  @param[in]       BlockSize      Pointer to required block size.
  @param[in]       WindowSize     Pointer to required window size.
  @param[in]       BufferPtr      Pointer to buffer.
  @param[in, out]  BufferSize     Pointer to buffer size.
  @param[in]       DontUseBuffer  Indicates whether to use a receive buffer.

  @retval EFI_SUCCESS        Successfully obtained the data from the file included in directory.
  @retval EFI_DEVICE_ERROR   The network device encountered an error during this operation.
  @retval Others             Operation failed.

**/
EFI_STATUS
PxeBcMtftp6ReadDirectory (
  IN     PXEBC_PRIVATE_DATA      *Private,
  IN     EFI_MTFTP6_CONFIG_DATA  *Config,
  IN     UINT8                   *Filename,
  IN     UINTN                   *BlockSize,
  IN     UINTN                   *WindowSize,
  IN     UINT8                   *BufferPtr,
  IN OUT UINT64                  *BufferSize,
  IN     BOOLEAN                 DontUseBuffer
  )
{
  EFI_MTFTP6_PROTOCOL  *Mtftp6;
  EFI_MTFTP6_TOKEN     Token;
  EFI_MTFTP6_OPTION    ReqOpt[2];
  UINT32               OptCnt;
  UINT8                BlksizeBuf[10];
  UINT8                WindowsizeBuf[10];
  EFI_STATUS           Status;

  Status                    = EFI_DEVICE_ERROR;
  Mtftp6                    = Private->Mtftp6;
  OptCnt                    = 0;
  Config->InitialServerPort = PXEBC_BS_DOWNLOAD_PORT;

  Status = Mtftp6->Configure (Mtftp6, Config);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (BlockSize != NULL) {
    ReqOpt[OptCnt].OptionStr = (UINT8 *)mMtftpOptions[PXE_MTFTP_OPTION_BLKSIZE_INDEX];
    ReqOpt[OptCnt].ValueStr  = BlksizeBuf;
    PxeBcUintnToAscDec (*BlockSize, ReqOpt[OptCnt].ValueStr, sizeof (BlksizeBuf));
    OptCnt++;
  }

  if (WindowSize != NULL) {
    ReqOpt[OptCnt].OptionStr = (UINT8 *)mMtftpOptions[PXE_MTFTP_OPTION_WINDOWSIZE_INDEX];
    ReqOpt[OptCnt].ValueStr  = WindowsizeBuf;
    PxeBcUintnToAscDec (*WindowSize, ReqOpt[OptCnt].ValueStr, sizeof (WindowsizeBuf));
    OptCnt++;
  }

  Token.Event        = NULL;
  Token.OverrideData = NULL;
  Token.Filename     = Filename;
  Token.ModeStr      = NULL;
  Token.OptionCount  = OptCnt;
  Token.OptionList   = ReqOpt;
  Token.Context      = Private;

  if (DontUseBuffer) {
    Token.BufferSize = 0;
    Token.Buffer     = NULL;
  } else {
    Token.BufferSize = *BufferSize;
    Token.Buffer     = BufferPtr;
  }

  Token.CheckPacket     = PxeBcMtftp6CheckPacket;
  Token.TimeoutCallback = NULL;
  Token.PacketNeeded    = NULL;

  Status = Mtftp6->ReadDirectory (Mtftp6, &Token);
  //
  // Get the real size of received buffer.
  //
  *BufferSize = Token.BufferSize;

  Mtftp6->Configure (Mtftp6, NULL);

  return Status;
}

/**
  This is a callback function when packets are received or transmitted in Mtftp driver.

  A callback function that is provided by the caller to intercept
  the EFI_MTFTP6_OPCODE_DATA or EFI_MTFTP4_OPCODE_DATA8 packets processed in the
  EFI_MTFTP4_PROTOCOL.ReadFile() function, and alternatively to intercept
  EFI_MTFTP4_OPCODE_OACK or EFI_MTFTP4_OPCODE_ERROR packets during a call to
  EFI_MTFTP4_PROTOCOL.ReadFile(), WriteFile() or ReadDirectory().

  @param[in]  This           Pointer to EFI_MTFTP4_PROTOCOL.
  @param[in]  Token          Pointer to EFI_MTFTP4_TOKEN.
  @param[in]  PacketLen      Length of EFI_MTFTP4_PACKET.
  @param[in]  Packet         Pointer to EFI_MTFTP4_PACKET to be checked.

  @retval EFI_SUCCESS    The current operation succeeded.
  @retval EFI_ABORTED    Abort the current transfer process.

**/
EFI_STATUS
EFIAPI
PxeBcMtftp4CheckPacket (
  IN EFI_MTFTP4_PROTOCOL  *This,
  IN EFI_MTFTP4_TOKEN     *Token,
  IN UINT16               PacketLen,
  IN EFI_MTFTP4_PACKET    *Packet
  )
{
  PXEBC_PRIVATE_DATA                   *Private;
  EFI_PXE_BASE_CODE_CALLBACK_PROTOCOL  *Callback;
  EFI_STATUS                           Status;

  Private  = (PXEBC_PRIVATE_DATA *)Token->Context;
  Callback = Private->PxeBcCallback;
  Status   = EFI_SUCCESS;

  if (Packet->OpCode == EFI_MTFTP4_OPCODE_ERROR) {
    //
    // Store the tftp error message into mode data and set the received flag.
    //
    Private->Mode.TftpErrorReceived   = TRUE;
    Private->Mode.TftpError.ErrorCode = (UINT8)Packet->Error.ErrorCode;
    AsciiStrnCpyS (
      Private->Mode.TftpError.ErrorString,
      PXE_MTFTP_ERROR_STRING_LENGTH,
      (CHAR8 *)Packet->Error.ErrorMessage,
      PXE_MTFTP_ERROR_STRING_LENGTH - 1
      );
    Private->Mode.TftpError.ErrorString[PXE_MTFTP_ERROR_STRING_LENGTH - 1] = '\0';
  }

  if (Callback != NULL) {
    //
    // Callback to user if has when received any tftp packet.
    //
    Status = Callback->Callback (
                         Callback,
                         Private->Function,
                         TRUE,
                         PacketLen,
                         (EFI_PXE_BASE_CODE_PACKET *)Packet
                         );
    if (Status != EFI_PXE_BASE_CODE_CALLBACK_STATUS_CONTINUE) {
      //
      // User wants to abort current process if not EFI_PXE_BASE_CODE_CALLBACK_STATUS_CONTINUE.
      //
      Status = EFI_ABORTED;
    } else {
      //
      // User wants to continue current process if EFI_PXE_BASE_CODE_CALLBACK_STATUS_CONTINUE.
      //
      Status = EFI_SUCCESS;
    }
  }

  return Status;
}

/**
  This function is to get size of a file using Tftp.

  @param[in]      Private        Pointer to PxeBc private data.
  @param[in]      Config         Pointer to EFI_MTFTP4_CONFIG_DATA.
  @param[in]      Filename       Pointer to boot file name.
  @param[in]      BlockSize      Pointer to required block size.
  @param[in]      WindowSize     Pointer to required window size.
  @param[in, out] BufferSize     Pointer to buffer size.

  @retval EFI_SUCCESS        Successfully obtained the size of file.
  @retval EFI_NOT_FOUND      Parse the tftp options failed.
  @retval EFI_DEVICE_ERROR   The network device encountered an error during this operation.
  @retval Others             Did not obtain the size of the file.

**/
EFI_STATUS
PxeBcMtftp4GetFileSize (
  IN     PXEBC_PRIVATE_DATA      *Private,
  IN     EFI_MTFTP4_CONFIG_DATA  *Config,
  IN     UINT8                   *Filename,
  IN     UINTN                   *BlockSize,
  IN     UINTN                   *WindowSize,
  IN OUT UINT64                  *BufferSize
  )
{
  EFI_MTFTP4_PROTOCOL  *Mtftp4;
  EFI_MTFTP4_OPTION    ReqOpt[3];
  EFI_MTFTP4_PACKET    *Packet;
  EFI_MTFTP4_OPTION    *Option;
  UINT32               PktLen;
  UINT8                OptBuf[PXE_MTFTP_OPTBUF_MAXNUM_INDEX];
  UINTN                OptBufSize;
  UINT32               OptCnt;
  EFI_STATUS           Status;

  *BufferSize               = 0;
  Status                    = EFI_DEVICE_ERROR;
  Mtftp4                    = Private->Mtftp4;
  Packet                    = NULL;
  Option                    = NULL;
  PktLen                    = 0;
  OptBufSize                = PXE_MTFTP_OPTBUF_MAXNUM_INDEX;
  OptCnt                    = 1;
  Config->InitialServerPort = PXEBC_BS_DOWNLOAD_PORT;

  Status = Mtftp4->Configure (Mtftp4, Config);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Build the required options for get info.
  //
  ReqOpt[0].OptionStr = (UINT8 *)mMtftpOptions[PXE_MTFTP_OPTION_TSIZE_INDEX];
  PxeBcUintnToAscDec (0, OptBuf, OptBufSize);
  ReqOpt[0].ValueStr = OptBuf;

  if (BlockSize != NULL) {
    ReqOpt[OptCnt].OptionStr = (UINT8 *)mMtftpOptions[PXE_MTFTP_OPTION_BLKSIZE_INDEX];
    ReqOpt[OptCnt].ValueStr  = (UINT8 *)(ReqOpt[OptCnt-1].ValueStr + AsciiStrLen ((CHAR8 *)ReqOpt[OptCnt-1].ValueStr) + 1);
    OptBufSize              -= (AsciiStrLen ((CHAR8 *)ReqOpt[OptCnt-1].ValueStr) + 1);
    PxeBcUintnToAscDec (*BlockSize, ReqOpt[OptCnt].ValueStr, OptBufSize);
    OptCnt++;
  }

  if (WindowSize != NULL) {
    ReqOpt[OptCnt].OptionStr = (UINT8 *)mMtftpOptions[PXE_MTFTP_OPTION_WINDOWSIZE_INDEX];
    ReqOpt[OptCnt].ValueStr  = (UINT8 *)(ReqOpt[OptCnt-1].ValueStr + AsciiStrLen ((CHAR8 *)ReqOpt[OptCnt-1].ValueStr) + 1);
    OptBufSize              -= (AsciiStrLen ((CHAR8 *)ReqOpt[OptCnt-1].ValueStr) + 1);
    PxeBcUintnToAscDec (*WindowSize, ReqOpt[OptCnt].ValueStr, OptBufSize);
    OptCnt++;
  }

  Status = Mtftp4->GetInfo (
                     Mtftp4,
                     NULL,
                     Filename,
                     NULL,
                     (UINT8)OptCnt,
                     ReqOpt,
                     &PktLen,
                     &Packet
                     );
  if (EFI_ERROR (Status)) {
    if (Status == EFI_TFTP_ERROR) {
      //
      // Store the tftp error message into mode data and set the received flag.
      //
      Private->Mode.TftpErrorReceived   = TRUE;
      Private->Mode.TftpError.ErrorCode = (UINT8)Packet->Error.ErrorCode;
      AsciiStrnCpyS (
        Private->Mode.TftpError.ErrorString,
        PXE_MTFTP_ERROR_STRING_LENGTH,
        (CHAR8 *)Packet->Error.ErrorMessage,
        PXE_MTFTP_ERROR_STRING_LENGTH - 1
        );
      Private->Mode.TftpError.ErrorString[PXE_MTFTP_ERROR_STRING_LENGTH - 1] = '\0';
    }

    goto ON_ERROR;
  }

  //
  // Parse the options in the reply packet.
  //
  OptCnt = 0;
  Status = Mtftp4->ParseOptions (
                     Mtftp4,
                     PktLen,
                     Packet,
                     (UINT32 *)&OptCnt,
                     &Option
                     );
  if (EFI_ERROR (Status)) {
    goto ON_ERROR;
  }

  //
  // Parse out the value of "tsize" option.
  //
  Status = EFI_NOT_FOUND;
  while (OptCnt != 0) {
    if (AsciiStrnCmp ((CHAR8 *)Option[OptCnt - 1].OptionStr, "tsize", 5) == 0) {
      *BufferSize = AsciiStrDecimalToUint64 ((CHAR8 *)(Option[OptCnt - 1].ValueStr));
      Status      = EFI_SUCCESS;
    }

    OptCnt--;
  }

  FreePool (Option);

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

  Mtftp4->Configure (Mtftp4, NULL);

  return Status;
}

/**
  This function is to read the data of a file using Tftp.

  @param[in]      Private        Pointer to PxeBc private data.
  @param[in]      Config         Pointer to EFI_MTFTP4_CONFIG_DATA.
  @param[in]      Filename       Pointer to boot file name.
  @param[in]      BlockSize      Pointer to required block size.
  @param[in]      WindowSize     Pointer to required window size.
  @param[in]      BufferPtr      Pointer to buffer.
  @param[in, out] BufferSize     Pointer to buffer size.
  @param[in]      DontUseBuffer  Indicates whether to use a receive buffer.

  @retval EFI_SUCCESS        Successfully read the data from the special file.
  @retval EFI_DEVICE_ERROR   The network device encountered an error during this operation.
  @retval Others             Read data from file failed.

**/
EFI_STATUS
PxeBcMtftp4ReadFile (
  IN     PXEBC_PRIVATE_DATA      *Private,
  IN     EFI_MTFTP4_CONFIG_DATA  *Config,
  IN     UINT8                   *Filename,
  IN     UINTN                   *BlockSize,
  IN     UINTN                   *WindowSize,
  IN     UINT8                   *BufferPtr,
  IN OUT UINT64                  *BufferSize,
  IN     BOOLEAN                 DontUseBuffer
  )
{
  EFI_MTFTP4_PROTOCOL  *Mtftp4;
  EFI_MTFTP4_TOKEN     Token;
  EFI_MTFTP4_OPTION    ReqOpt[2];
  UINT32               OptCnt;
  UINT8                BlksizeBuf[10];
  UINT8                WindowsizeBuf[10];
  EFI_STATUS           Status;

  Status                    = EFI_DEVICE_ERROR;
  Mtftp4                    = Private->Mtftp4;
  OptCnt                    = 0;
  Config->InitialServerPort = PXEBC_BS_DOWNLOAD_PORT;

  Status = Mtftp4->Configure (Mtftp4, Config);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (BlockSize != NULL) {
    ReqOpt[OptCnt].OptionStr = (UINT8 *)mMtftpOptions[PXE_MTFTP_OPTION_BLKSIZE_INDEX];
    ReqOpt[OptCnt].ValueStr  = BlksizeBuf;
    PxeBcUintnToAscDec (*BlockSize, ReqOpt[OptCnt].ValueStr, sizeof (BlksizeBuf));
    OptCnt++;
  }

  if (WindowSize != NULL) {
    ReqOpt[OptCnt].OptionStr = (UINT8 *)mMtftpOptions[PXE_MTFTP_OPTION_WINDOWSIZE_INDEX];
    ReqOpt[OptCnt].ValueStr  = WindowsizeBuf;
    PxeBcUintnToAscDec (*WindowSize, ReqOpt[OptCnt].ValueStr, sizeof (WindowsizeBuf));
    OptCnt++;
  }

  Token.Event        = NULL;
  Token.OverrideData = NULL;
  Token.Filename     = Filename;
  Token.ModeStr      = NULL;
  Token.OptionCount  = OptCnt;
  Token.OptionList   = ReqOpt;
  Token.Context      = Private;

  if (DontUseBuffer) {
    Token.BufferSize = 0;
    Token.Buffer     = NULL;
  } else {
    Token.BufferSize = *BufferSize;
    Token.Buffer     = BufferPtr;
  }

  Token.CheckPacket     = PxeBcMtftp4CheckPacket;
  Token.TimeoutCallback = NULL;
  Token.PacketNeeded    = NULL;

  Status = Mtftp4->ReadFile (Mtftp4, &Token);
  //
  // Get the real size of received buffer.
  //
  *BufferSize = Token.BufferSize;

  Mtftp4->Configure (Mtftp4, NULL);

  return Status;
}

/**
  This function is to write the data of a file using Tftp.

  @param[in]       Private        Pointer to PxeBc private data.
  @param[in]       Config         Pointer to EFI_MTFTP4_CONFIG_DATA.
  @param[in]       Filename       Pointer to boot file name.
  @param[in]       Overwrite      Indicates whether to use the overwrite attribute.
  @param[in]       BlockSize      Pointer to required block size.
  @param[in]       BufferPtr      Pointer to buffer.
  @param[in, out]  BufferSize     Pointer to buffer size.

  @retval EFI_SUCCESS        Successfully write the data  into the special file.
  @retval EFI_DEVICE_ERROR   The network device encountered an error during this operation.
  @retval other              Write data into file failed.

**/
EFI_STATUS
PxeBcMtftp4WriteFile (
  IN     PXEBC_PRIVATE_DATA      *Private,
  IN     EFI_MTFTP4_CONFIG_DATA  *Config,
  IN     UINT8                   *Filename,
  IN     BOOLEAN                 Overwrite,
  IN     UINTN                   *BlockSize,
  IN     UINT8                   *BufferPtr,
  IN OUT UINT64                  *BufferSize
  )
{
  EFI_MTFTP4_PROTOCOL  *Mtftp4;
  EFI_MTFTP4_TOKEN     Token;
  EFI_MTFTP4_OPTION    ReqOpt[1];
  UINT32               OptCnt;
  UINT8                OptBuf[128];
  EFI_STATUS           Status;

  Status                    = EFI_DEVICE_ERROR;
  Mtftp4                    = Private->Mtftp4;
  OptCnt                    = 0;
  Config->InitialServerPort = PXEBC_BS_DOWNLOAD_PORT;

  Status = Mtftp4->Configure (Mtftp4, Config);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (BlockSize != NULL) {
    ReqOpt[0].OptionStr = (UINT8 *)mMtftpOptions[PXE_MTFTP_OPTION_BLKSIZE_INDEX];
    ReqOpt[0].ValueStr  = OptBuf;
    PxeBcUintnToAscDec (*BlockSize, ReqOpt[0].ValueStr, PXE_MTFTP_OPTBUF_MAXNUM_INDEX);
    OptCnt++;
  }

  Token.Event           = NULL;
  Token.OverrideData    = NULL;
  Token.Filename        = Filename;
  Token.ModeStr         = NULL;
  Token.OptionCount     = OptCnt;
  Token.OptionList      = ReqOpt;
  Token.BufferSize      = *BufferSize;
  Token.Buffer          = BufferPtr;
  Token.CheckPacket     = PxeBcMtftp4CheckPacket;
  Token.TimeoutCallback = NULL;
  Token.PacketNeeded    = NULL;

  Status = Mtftp4->WriteFile (Mtftp4, &Token);
  //
  // Get the real size of transmitted buffer.
  //
  *BufferSize = Token.BufferSize;

  Mtftp4->Configure (Mtftp4, NULL);

  return Status;
}

/**
  This function is to get data (file) from a directory using Tftp.

  @param[in]       Private        Pointer to PxeBc private data.
  @param[in]       Config         Pointer to EFI_MTFTP4_CONFIG_DATA.
  @param[in]       Filename       Pointer to boot file name.
  @param[in]       BlockSize      Pointer to required block size.
  @param[in]       WindowSize     Pointer to required window size.
  @param[in]       BufferPtr      Pointer to buffer.
  @param[in, out]  BufferSize     Pointer to buffer size.
  @param[in]       DontUseBuffer  Indicates whether to use a receive buffer.

  @retval EFI_SUCCESS        Successfully obtained the data from the file included in the directory.
  @retval EFI_DEVICE_ERROR   The network device encountered an error during this operation.
  @retval Others             Operation failed.

**/
EFI_STATUS
PxeBcMtftp4ReadDirectory (
  IN     PXEBC_PRIVATE_DATA      *Private,
  IN     EFI_MTFTP4_CONFIG_DATA  *Config,
  IN     UINT8                   *Filename,
  IN     UINTN                   *BlockSize,
  IN     UINTN                   *WindowSize,
  IN     UINT8                   *BufferPtr,
  IN OUT UINT64                  *BufferSize,
  IN     BOOLEAN                 DontUseBuffer
  )
{
  EFI_MTFTP4_PROTOCOL  *Mtftp4;
  EFI_MTFTP4_TOKEN     Token;
  EFI_MTFTP4_OPTION    ReqOpt[2];
  UINT32               OptCnt;
  UINT8                BlksizeBuf[10];
  UINT8                WindowsizeBuf[10];
  EFI_STATUS           Status;

  Status                    = EFI_DEVICE_ERROR;
  Mtftp4                    = Private->Mtftp4;
  OptCnt                    = 0;
  Config->InitialServerPort = PXEBC_BS_DOWNLOAD_PORT;

  Status = Mtftp4->Configure (Mtftp4, Config);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (BlockSize != NULL) {
    ReqOpt[OptCnt].OptionStr = (UINT8 *)mMtftpOptions[PXE_MTFTP_OPTION_BLKSIZE_INDEX];
    ReqOpt[OptCnt].ValueStr  = BlksizeBuf;
    PxeBcUintnToAscDec (*BlockSize, ReqOpt[OptCnt].ValueStr, sizeof (BlksizeBuf));
    OptCnt++;
  }

  if (WindowSize != NULL) {
    ReqOpt[OptCnt].OptionStr = (UINT8 *)mMtftpOptions[PXE_MTFTP_OPTION_WINDOWSIZE_INDEX];
    ReqOpt[OptCnt].ValueStr  = WindowsizeBuf;
    PxeBcUintnToAscDec (*WindowSize, ReqOpt[OptCnt].ValueStr, sizeof (WindowsizeBuf));
    OptCnt++;
  }

  Token.Event        = NULL;
  Token.OverrideData = NULL;
  Token.Filename     = Filename;
  Token.ModeStr      = NULL;
  Token.OptionCount  = OptCnt;
  Token.OptionList   = ReqOpt;
  Token.Context      = Private;

  if (DontUseBuffer) {
    Token.BufferSize = 0;
    Token.Buffer     = NULL;
  } else {
    Token.BufferSize = *BufferSize;
    Token.Buffer     = BufferPtr;
  }

  Token.CheckPacket     = PxeBcMtftp4CheckPacket;
  Token.TimeoutCallback = NULL;
  Token.PacketNeeded    = NULL;

  Status = Mtftp4->ReadDirectory (Mtftp4, &Token);
  //
  // Get the real size of received buffer.
  //
  *BufferSize = Token.BufferSize;

  Mtftp4->Configure (Mtftp4, NULL);

  return Status;
}

/**
  This function is wrapper to get the file size using TFTP.

  @param[in]      Private        Pointer to PxeBc private data.
  @param[in]      Config         Pointer to configure data.
  @param[in]      Filename       Pointer to boot file name.
  @param[in]      BlockSize      Pointer to required block size.
  @param[in]      WindowSize     Pointer to required window size.
  @param[in, out] BufferSize     Pointer to buffer size.

  @retval EFI_SUCCESS        Successfully obtained the size of file.
  @retval EFI_NOT_FOUND      Parse the tftp options failed.
  @retval EFI_DEVICE_ERROR   The network device encountered an error during this operation.
  @retval Others             Did not obtain the size of the file.

**/
EFI_STATUS
PxeBcTftpGetFileSize (
  IN     PXEBC_PRIVATE_DATA  *Private,
  IN     VOID                *Config,
  IN     UINT8               *Filename,
  IN     UINTN               *BlockSize,
  IN     UINTN               *WindowSize,
  IN OUT UINT64              *BufferSize
  )
{
  if (Private->PxeBc.Mode->UsingIpv6) {
    return PxeBcMtftp6GetFileSize (
             Private,
             (EFI_MTFTP6_CONFIG_DATA *)Config,
             Filename,
             BlockSize,
             WindowSize,
             BufferSize
             );
  } else {
    return PxeBcMtftp4GetFileSize (
             Private,
             (EFI_MTFTP4_CONFIG_DATA *)Config,
             Filename,
             BlockSize,
             WindowSize,
             BufferSize
             );
  }
}

/**
  This function is a wrapper to get file using TFTP.

  @param[in]      Private        Pointer to PxeBc private data.
  @param[in]      Config         Pointer to config data.
  @param[in]      Filename       Pointer to boot file name.
  @param[in]      BlockSize      Pointer to required block size.
  @param[in]      WindowSize     Pointer to required window size.
  @param[in]      BufferPtr      Pointer to buffer.
  @param[in, out] BufferSize     Pointer to buffer size.
  @param[in]      DontUseBuffer  Indicates whether to use a receive buffer.

  @retval EFI_SUCCESS        Successfully read the data from the special file.
  @retval EFI_DEVICE_ERROR   The network device encountered an error during this operation.
  @retval Others             Read data from file failed.

**/
EFI_STATUS
PxeBcTftpReadFile (
  IN     PXEBC_PRIVATE_DATA  *Private,
  IN     VOID                *Config,
  IN     UINT8               *Filename,
  IN     UINTN               *BlockSize,
  IN     UINTN               *WindowSize,
  IN     UINT8               *BufferPtr,
  IN OUT UINT64              *BufferSize,
  IN     BOOLEAN             DontUseBuffer
  )
{
  if (Private->PxeBc.Mode->UsingIpv6) {
    return PxeBcMtftp6ReadFile (
             Private,
             (EFI_MTFTP6_CONFIG_DATA *)Config,
             Filename,
             BlockSize,
             WindowSize,
             BufferPtr,
             BufferSize,
             DontUseBuffer
             );
  } else {
    return PxeBcMtftp4ReadFile (
             Private,
             (EFI_MTFTP4_CONFIG_DATA *)Config,
             Filename,
             BlockSize,
             WindowSize,
             BufferPtr,
             BufferSize,
             DontUseBuffer
             );
  }
}

/**
  This function is a wrapper to write file using TFTP.

  @param[in]       Private        Pointer to PxeBc private data.
  @param[in]       Config         Pointer to config data.
  @param[in]       Filename       Pointer to boot file name.
  @param[in]       Overwrite      Indicate whether with overwrite attribute.
  @param[in]       BlockSize      Pointer to required block size.
  @param[in]       BufferPtr      Pointer to buffer.
  @param[in, out]  BufferSize     Pointer to buffer size.

  @retval EFI_SUCCESS        Successfully wrote the data into a special file.
  @retval EFI_DEVICE_ERROR   The network device encountered an error during this operation.
  @retval other              Write data into file failed.

**/
EFI_STATUS
PxeBcTftpWriteFile (
  IN     PXEBC_PRIVATE_DATA  *Private,
  IN     VOID                *Config,
  IN     UINT8               *Filename,
  IN     BOOLEAN             Overwrite,
  IN     UINTN               *BlockSize,
  IN     UINT8               *BufferPtr,
  IN OUT UINT64              *BufferSize
  )
{
  if (Private->PxeBc.Mode->UsingIpv6) {
    return PxeBcMtftp6WriteFile (
             Private,
             (EFI_MTFTP6_CONFIG_DATA *)Config,
             Filename,
             Overwrite,
             BlockSize,
             BufferPtr,
             BufferSize
             );
  } else {
    return PxeBcMtftp4WriteFile (
             Private,
             (EFI_MTFTP4_CONFIG_DATA *)Config,
             Filename,
             Overwrite,
             BlockSize,
             BufferPtr,
             BufferSize
             );
  }
}

/**
  This function is a wrapper to get the data (file) from a directory using TFTP.

  @param[in]       Private        Pointer to PxeBc private data.
  @param[in]       Config         Pointer to config data.
  @param[in]       Filename       Pointer to boot file name.
  @param[in]       BlockSize      Pointer to required block size.
  @param[in]       WindowSize     Pointer to required window size.
  @param[in]       BufferPtr      Pointer to buffer.
  @param[in, out]  BufferSize     Pointer to buffer size.
  @param[in]       DontUseBuffer  Indicatse whether to use a receive buffer.

  @retval EFI_SUCCESS        Successfully obtained the data from the file included in the directory.
  @retval EFI_DEVICE_ERROR   The network device encountered an error during this operation.
  @retval Others             Operation failed.

**/
EFI_STATUS
PxeBcTftpReadDirectory (
  IN     PXEBC_PRIVATE_DATA  *Private,
  IN     VOID                *Config,
  IN     UINT8               *Filename,
  IN     UINTN               *BlockSize,
  IN     UINTN               *WindowSize,
  IN     UINT8               *BufferPtr,
  IN OUT UINT64              *BufferSize,
  IN     BOOLEAN             DontUseBuffer
  )
{
  if (Private->PxeBc.Mode->UsingIpv6) {
    return PxeBcMtftp6ReadDirectory (
             Private,
             (EFI_MTFTP6_CONFIG_DATA *)Config,
             Filename,
             BlockSize,
             WindowSize,
             BufferPtr,
             BufferSize,
             DontUseBuffer
             );
  } else {
    return PxeBcMtftp4ReadDirectory (
             Private,
             (EFI_MTFTP4_CONFIG_DATA *)Config,
             Filename,
             BlockSize,
             WindowSize,
             BufferPtr,
             BufferSize,
             DontUseBuffer
             );
  }
}
