/** @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        Sucessfully obtained the size of file.
  @retval EFI_NOT_FOUND      Parse the tftp ptions 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 succeeeded.
  @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_SUCCES         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        Sucessfully 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_SUCCES         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
             );
  }
}

