/** @file

Copyright (c) 2004 - 2007, Intel Corporation
All rights reserved. This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

Module Name:

    pxe_bc_mtftp.c

Abstract:
  TFTP and MTFTP (multicast TFTP) implementation.

Revision History


**/

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

//
// The following #define is used to create a version that does not wait to
// open after a listen.  This is just for a special regression test of MTFTP
// server to make sure multiple opens are handled correctly.  Normally this
// next line should be a comment.
// #define SpecialNowaitVersion    // comment out for normal operation
//

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "Bc.h"

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
UINT64
Swap64 (
  UINT64 n
  )
{
  union {
    UINT64  n;
    UINT8   b[8];
  } u;

  UINT8 t;

  u.n     = n;

  t       = u.b[0];
  u.b[0]  = u.b[7];
  u.b[7]  = t;

  t       = u.b[1];
  u.b[1]  = u.b[6];
  u.b[6]  = t;

  t       = u.b[2];
  u.b[2]  = u.b[5];
  u.b[5]  = t;

  t       = u.b[3];
  u.b[3]  = u.b[4];
  u.b[4]  = t;

  return u.n;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/**

  @return EFI_SUCCESS :=
  @return EFI_TFTP_ERROR :=
  @return other :=

**/
EFI_STATUS
TftpUdpRead (
  PXE_BASECODE_DEVICE         *Private,
  UINT16                      Operation,
  VOID                        *HeaderPtr,
  UINTN                       *BufferSizePtr,
  VOID                        *BufferPtr,
  EFI_IP_ADDRESS              *ServerIpPtr,
  EFI_PXE_BASE_CODE_UDP_PORT  *ServerPortPtr,
  EFI_IP_ADDRESS              *OurIpPtr,
  EFI_PXE_BASE_CODE_UDP_PORT  *OurPortPtr,
  UINT16                      Timeout
  )
{
  EFI_PXE_BASE_CODE_MODE  *PxeBcMode;
  EFI_STATUS              Status;
  EFI_EVENT               TimeoutEvent;
  UINTN                   HeaderSize;

  //
  //
  //
  Status = gBS->CreateEvent (
                  EVT_TIMER,
                  TPL_CALLBACK,
                  NULL,
                  NULL,
                  &TimeoutEvent
                  );

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

  Status = gBS->SetTimer (
                  TimeoutEvent,
                  TimerRelative,
                  Timeout * 10000000 + 1000000
                  );

  if (EFI_ERROR (Status)) {
    gBS->CloseEvent (TimeoutEvent);
    return Status;
  }
  //
  //
  //
  HeaderSize = Private->BigBlkNumFlag ? sizeof (struct Tftpv4Ack8) : sizeof (struct Tftpv4Ack);

#define ERROR_MESSAGE_PTR ((struct Tftpv4Error *) HeaderPtr)

  Status = UdpRead (
            Private,
            Operation,
            OurIpPtr,
            OurPortPtr,
            ServerIpPtr,
            ServerPortPtr,
            &HeaderSize,
            HeaderPtr,
            BufferSizePtr,
            BufferPtr,
            TimeoutEvent
            );

  if (Status != EFI_SUCCESS || ERROR_MESSAGE_PTR->OpCode != HTONS (TFTP_ERROR)) {
    gBS->CloseEvent (TimeoutEvent);
    return Status;
  }
  //
  // got an error packet
  // write one byte error code followed by error message
  //
  PxeBcMode                       = Private->EfiBc.Mode;
  PxeBcMode->TftpErrorReceived    = TRUE;
  PxeBcMode->TftpError.ErrorCode  = (UINT8) NTOHS (ERROR_MESSAGE_PTR->ErrCode);
  HeaderSize                      = MIN (*BufferSizePtr, sizeof PxeBcMode->TftpError.ErrorString);
  CopyMem (PxeBcMode->TftpError.ErrorString, BufferPtr, HeaderSize);

  gBS->CloseEvent (TimeoutEvent);
  return EFI_TFTP_ERROR;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/**


**/
VOID
SendError (
  PXE_BASECODE_DEVICE         *Private,
  EFI_IP_ADDRESS              *ServerIpPtr,
  EFI_PXE_BASE_CODE_UDP_PORT  *ServerPortPtr,
  EFI_PXE_BASE_CODE_UDP_PORT  *OurPortPtr
  )
{
  struct Tftpv4Error  *ErrStr;
  UINTN               Len;

  ErrStr            = (VOID *) Private->TftpErrorBuffer;
  Len               = sizeof *ErrStr;

  ErrStr->OpCode    = HTONS (TFTP_ERROR);
  ErrStr->ErrCode   = HTONS (TFTP_ERR_OPTION);
  ErrStr->ErrMsg[0] = 0;

  UdpWrite (
    Private,
    EFI_PXE_BASE_CODE_UDP_OPFLAGS_MAY_FRAGMENT,
    ServerIpPtr,
    ServerPortPtr,
    0,
    0,
    OurPortPtr,
    0,
    0,
    &Len,
    ErrStr
    );
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/**


**/
EFI_STATUS
SendAckAndGetData (
  PXE_BASECODE_DEVICE         *Private,
  EFI_IP_ADDRESS              *ServerIpPtr,
  EFI_PXE_BASE_CODE_UDP_PORT  *ServerPortPtr,
  EFI_IP_ADDRESS              *ReplyIpPtr,
  EFI_PXE_BASE_CODE_UDP_PORT  *OurPortPtr,
  UINT16                      Timeout,
  UINTN                       *ReplyLenPtr,
  UINT8                       *PxeBcMode,
  UINT64                      *BlockNumPtr,
  BOOLEAN                     AckOnly
  )
{
  struct Tftpv4Data DataBuffer;
  struct Tftpv4Ack  *Ack2Ptr;
  struct Tftpv4Ack8 *Ack8Ptr;
  EFI_STATUS        Status;
  UINTN             Len;

  Ack2Ptr = (VOID *) Private->TftpAckBuffer;
  Ack8Ptr = (VOID *) Private->TftpAckBuffer;

  if (Private->BigBlkNumFlag) {
    Len               = sizeof (struct Tftpv4Ack8);

    Ack8Ptr->OpCode   = HTONS (TFTP_ACK8);
    Ack8Ptr->BlockNum = Swap64 (*BlockNumPtr);

    Status = UdpWrite (
              Private,
              EFI_PXE_BASE_CODE_UDP_OPFLAGS_MAY_FRAGMENT,
              ServerIpPtr,
              ServerPortPtr,
              0,
              0,
              OurPortPtr,
              0,
              0,
              &Len,
              Ack8Ptr
              );

    if (EFI_ERROR (Status)) {
      return Status;
    }
  } else {
    Len               = sizeof (struct Tftpv4Ack);

    Ack2Ptr->OpCode   = HTONS (TFTP_ACK);
    Ack2Ptr->BlockNum = HTONS ((UINT16) *BlockNumPtr);

    Status = UdpWrite (
              Private,
              EFI_PXE_BASE_CODE_UDP_OPFLAGS_MAY_FRAGMENT,
              ServerIpPtr,
              ServerPortPtr,
              0,
              0,
              OurPortPtr,
              0,
              0,
              &Len,
              Ack2Ptr
              );

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

  if (AckOnly) {
    //
    // ACK of last packet.  This is just a courtesy.
    // Do not wait for response.
    //
    return EFI_SUCCESS;
  }
  //
  // read reply
  //
  Status = TftpUdpRead (
            Private,
            0,
            &DataBuffer,
            ReplyLenPtr,
            PxeBcMode,
            ServerIpPtr,
            ServerPortPtr,
            ReplyIpPtr,
            OurPortPtr,
            Timeout
            );

  if (EFI_ERROR (Status) && Status != EFI_BUFFER_TOO_SMALL) {
    return Status;
  }
  //
  // got a good reply (so far)
  // check for next data packet
  //
  if (!Private->BigBlkNumFlag && DataBuffer.Header.OpCode == HTONS (TFTP_DATA)) {
    if (Status == EFI_BUFFER_TOO_SMALL) {
      SendError (Private, ServerIpPtr, ServerPortPtr, OurPortPtr);
    }

    *BlockNumPtr = NTOHS (DataBuffer.Header.BlockNum);
    return Status;
  }

  if (Private->BigBlkNumFlag && DataBuffer.Header.OpCode == HTONS (TFTP_DATA8)) {
    if (Status == EFI_BUFFER_TOO_SMALL) {
      SendError (Private, ServerIpPtr, ServerPortPtr, OurPortPtr);
    }

    *BlockNumPtr = Swap64 (*(UINT64 *) &DataBuffer.Header.BlockNum);
    return Status;
  }

  return EFI_PROTOCOL_ERROR;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/**


**/
EFI_STATUS
LockStepReceive (
  PXE_BASECODE_DEVICE         *Private,
  UINTN                       PacketSize,
  UINT64                      *BufferSizePtr,
  UINT64                      Offset,
  UINT8                       *BufferPtr,
  EFI_IP_ADDRESS              *ServerIpPtr,
  EFI_PXE_BASE_CODE_UDP_PORT  *ServerPortPtr,
  EFI_IP_ADDRESS              *ReplyIpPtr,
  EFI_PXE_BASE_CODE_UDP_PORT  *OurPortPtr,
  UINT64                      LastBlock,
  UINT16                      Timeout,
  IN BOOLEAN                  DontUseBuffer
  )
{
  EFI_STATUS  Status;
  UINT64      BlockNum;
  UINT64      BufferSize;
  UINTN       Retries;
  UINTN       SaveLen;
  UINTN       ReplyLen;

  ReplyLen  = PacketSize;
  BlockNum  = LastBlock;

  DEBUG ((DEBUG_INFO, "\nLockStepReceive()  PacketSize = %d", PacketSize));

  if (DontUseBuffer) {
    BufferSize = PacketSize;
  } else {
    BufferSize = *BufferSizePtr - Offset;
    BufferPtr += Offset;
  }

  while (ReplyLen >= 512 && ReplyLen == PacketSize) {
    if (BufferSize < PacketSize) {
      ReplyLen = (UINTN) ((BufferSize > 0) ? BufferSize : 0);
    }

    SaveLen = ReplyLen;

    //
    // write an ack packet and get data - retry up to NUM_ACK_RETRIES on timeout
    //
    Retries = NUM_ACK_RETRIES;

    do {
      ReplyLen = SaveLen;

      Status = SendAckAndGetData (
                Private,
                ServerIpPtr,
                ServerPortPtr,
                ReplyIpPtr,
                OurPortPtr,
                Timeout,
                (UINTN *) &ReplyLen,
                BufferPtr,
                &BlockNum,
                FALSE
                );

      if (!EFI_ERROR (Status) || Status == EFI_BUFFER_TOO_SMALL) {
        if (BlockNum == LastBlock) {
          DEBUG ((DEBUG_NET, "\nresend"));
          //
          // a resend - continue
          //
          Status = EFI_TIMEOUT;
        } else if (Private->BigBlkNumFlag) {
          if (BlockNum != ++LastBlock) {
            DEBUG ((DEBUG_NET, "\nLockStepReceive()  Exit #1a"));
            //
            // not correct blocknum - error
            //
            return EFI_PROTOCOL_ERROR;
          }
        } else {
          LastBlock = (LastBlock + 1) & 0xFFFF;
          if (BlockNum != LastBlock) {
            DEBUG ((DEBUG_NET, "\nLockStepReceive()  Exit #1b"));
            return EFI_PROTOCOL_ERROR;
            //
            // not correct blocknum - error
            //
          }
        }
      }
    } while (Status == EFI_TIMEOUT && --Retries);

    if (EFI_ERROR (Status)) {
      if (Status != EFI_BUFFER_TOO_SMALL) {
        SendError (Private, ServerIpPtr, ServerPortPtr, OurPortPtr);
      }

      return Status;
    }

    if (DontUseBuffer) {
      BufferSize += ReplyLen;
    } else {
      BufferPtr += ReplyLen;
      BufferSize -= ReplyLen;
    }
  }
  //
  // while (ReplyLen == PacketSize);
  //
  if (DontUseBuffer) {
    if (BufferSizePtr != NULL) {
      *BufferSizePtr = (BufferSize - PacketSize);
    }
  } else {
    *BufferSizePtr -= BufferSize;
  }

  /* Send ACK of last packet. */
  ReplyLen = 0;

  SendAckAndGetData (
    Private,
    ServerIpPtr,
    ServerPortPtr,
    ReplyIpPtr,
    OurPortPtr,
    Timeout,
    (UINTN *) &ReplyLen,
    BufferPtr,
    &BlockNum,
    TRUE
    );

  return EFI_SUCCESS;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

//
// some literals
//
UINT8                      Mode[]          = MODE_BINARY;
UINT8                      BlockSizeOp[]   = OP_BLKSIZE;
UINT8                      TsizeOp[]       = OP_TFRSIZE;
UINT8                      OverwriteOp[]   = OP_OVERWRITE;
UINT8                      BigBlkNumOp[]   = OP_BIGBLKNUM;
EFI_PXE_BASE_CODE_UDP_PORT TftpRequestPort = TFTP_OPEN_PORT;

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/**

  @return Pointer to value field if option found or NULL if not found.

**/
UINT8 *
FindOption (
  UINT8 *OptionPtr,
  INTN  OpLen,
  UINT8 *OackPtr,
  INTN  OackSize
  )
{
  if ((OackSize -= OpLen) <= 0) {
    return NULL;
  }

  do {
    if (!CompareMem (OackPtr, OptionPtr, OpLen)) {
      return OackPtr + OpLen;
    }

    ++OackPtr;
  } while (--OackSize);

  return NULL;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#define BKSZOP      1 // block size
#define TSIZEOP     2 // transfer size
#define OVERWRITEOP 4 // overwrite
#define BIGBLKNUMOP 8 // big block numbers
EFI_STATUS
TftpRwReq (
  UINT16                      Req,
  UINT16                      Options,
  PXE_BASECODE_DEVICE         *Private,
  EFI_IP_ADDRESS              *ServerIpPtr,
  EFI_PXE_BASE_CODE_UDP_PORT  *ServerPortPtr,
  EFI_PXE_BASE_CODE_UDP_PORT  *OurPortPtr,
  UINT8                       *FilenamePtr,
  UINTN                       *PacketSizePtr,
  VOID                        *Buffer
  )
{
  union {
    UINT8             Data[514];
    struct Tftpv4Req  ReqStr;
  } *u;

  UINT16  OpFlags;
  INTN    Len;
  INTN    TotalLen;
  UINT8   *Ptr;

  if (*OurPortPtr == 0) {
    OpFlags = EFI_PXE_BASE_CODE_UDP_OPFLAGS_MAY_FRAGMENT | EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_PORT;
  } else {
    OpFlags = EFI_PXE_BASE_CODE_UDP_OPFLAGS_MAY_FRAGMENT;
  }
  //
  // build the basic request - opcode, filename, mode
  //
  u                 = Buffer;
  u->ReqStr.OpCode  = HTONS (Req);
  TotalLen = sizeof (Mode) + sizeof (u->ReqStr.OpCode) + (Len = 1 + AsciiStrLen ((CHAR8 *) FilenamePtr));

  CopyMem (u->ReqStr.FileName, FilenamePtr, Len);
  Ptr = (UINT8 *) (u->ReqStr.FileName + Len);

  CopyMem (Ptr, Mode, sizeof (Mode));
  Ptr += sizeof (Mode);

  if (Options & BKSZOP) {
    CopyMem (Ptr, BlockSizeOp, sizeof (BlockSizeOp));
    UtoA10 (*PacketSizePtr, Ptr + sizeof (BlockSizeOp));

    TotalLen += (Len = 1 + AsciiStrLen ((CHAR8 *) (Ptr + sizeof (BlockSizeOp))) + sizeof (BlockSizeOp));

    Ptr += Len;
  }

  if (Options & TSIZEOP) {
    CopyMem (Ptr, TsizeOp, sizeof (TsizeOp));
    CopyMem (Ptr + sizeof (TsizeOp), "0", 2);
    TotalLen += sizeof (TsizeOp) + 2;
    Ptr += sizeof (TsizeOp) + 2;
  }

  if (Options & OVERWRITEOP) {
    CopyMem (Ptr, OverwriteOp, sizeof (OverwriteOp));
    CopyMem (Ptr + sizeof (OverwriteOp), "1", 2);
    TotalLen += sizeof (OverwriteOp) + 2;
    Ptr += sizeof (OverwriteOp) + 2;
  }

  if (Options & BIGBLKNUMOP) {
    CopyMem (Ptr, BigBlkNumOp, sizeof (BigBlkNumOp));
    CopyMem (Ptr + sizeof (BigBlkNumOp), "8", 2);
    TotalLen += sizeof (BigBlkNumOp) + 2;
    Ptr += sizeof (BigBlkNumOp) + 2;
  }
  //
  // send it
  //
  return UdpWrite (
          Private,
          OpFlags,
          ServerIpPtr,
          ServerPortPtr,
          0,
          0,
          OurPortPtr,
          0,
          0,
          (UINTN *) &TotalLen,
          u
          );
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/**


**/
EFI_STATUS
TftpRwReqwResp (
  UINT16                      Req,
  UINT16                      Options,
  PXE_BASECODE_DEVICE         *Private,
  VOID                        *HeaderPtr,
  UINTN                       *PacketSizePtr,
  UINTN                       *ReplyLenPtr,
  VOID                        *BufferPtr,
  EFI_IP_ADDRESS              *ServerIpPtr,
  EFI_PXE_BASE_CODE_UDP_PORT  *ServerPortPtr,
  EFI_PXE_BASE_CODE_UDP_PORT  *ServerReplyPortPtr,
  EFI_PXE_BASE_CODE_UDP_PORT  *OurPortPtr,
  UINT8                       *FilenamePtr,
  UINT16                      Timeout
  )
{
  EFI_STATUS  Status;
  UINTN       SaveReplyLen;
  INTN        Retries;
  UINT8       Buffer[514];

  SaveReplyLen            = *ReplyLenPtr;
  Retries                 = 3;
  Private->BigBlkNumFlag  = FALSE;
  *OurPortPtr             = 0;
  //
  // generate random
  //
  do {
    if (*OurPortPtr != 0) {
      if (++ *OurPortPtr == 0) {
        *OurPortPtr = PXE_RND_PORT_LOW;
      }
    }
    //
    // send request from our Ip = StationIp
    //
    if ((Status = TftpRwReq (
                    Req,
                    Options,
                    Private,
                    ServerIpPtr,
                    ServerPortPtr,
                    OurPortPtr,
                    FilenamePtr,
                    PacketSizePtr,
                    Buffer
                    )) != EFI_SUCCESS) {
      DEBUG (
        (DEBUG_WARN,
        "\nTftpRwReqwResp()  Exit #1  %xh (%r)",
        Status,
        Status)
        );

      return Status;
    }
    //
    // read reply to our Ip = StationIp
    //
    *ReplyLenPtr = SaveReplyLen;

    Status = TftpUdpRead (
              Private,
              EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_PORT,
              HeaderPtr,
              ReplyLenPtr,
              BufferPtr,
              ServerIpPtr,
              ServerReplyPortPtr,
              0,
              OurPortPtr,
              Timeout
              );
  } while (Status == EFI_TIMEOUT && --Retries);

  if (!Options || Status != EFI_TFTP_ERROR) {
    DEBUG (
      (DEBUG_WARN,
      "\nTftpRwReqwResp()  Exit #2  %xh (%r)",
      Status,
      Status)
      );
    return Status;
  }

  Status = TftpRwReqwResp (
            Req,
            0,
            Private,
            HeaderPtr,
            PacketSizePtr,
            ReplyLenPtr,
            BufferPtr,
            ServerIpPtr,
            ServerPortPtr,
            ServerReplyPortPtr,
            OurPortPtr,
            FilenamePtr,
            Timeout
            );

  DEBUG ((DEBUG_WARN, "\nTftpRwReqwResp()  Exit #3  %xh (%r)", Status, Status));

  return Status;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

//
// mtftp listen
// read on mcast ip, cport, from sport, for data packet
// returns success if gets multicast last packet or all up to last block
// if not missing, then finished
//

/**


**/
EFI_STATUS
MtftpListen (
  PXE_BASECODE_DEVICE           *Private,
  UINT64                        *BufferSizePtr,
  UINT8                         *BufferPtr,
  EFI_IP_ADDRESS                *ServerIpPtr,
  EFI_PXE_BASE_CODE_MTFTP_INFO  *MtftpInfoPtr,
  UINT64                        *StartBlockPtr,
  UINTN                         *NumMissedPtr,
  UINT16                        TransTimeout,
  UINT16                        ListenTimeout,
  UINT64                        FinalBlock,
  IN BOOLEAN                    DontUseBuffer
  )
{
  EFI_STATUS        Status;
  struct Tftpv4Ack  Header;
  UINT64            Offset;
  UINT64            BlockNum;
  UINT64            LastBlockNum;
  UINT64            BufferSize;
  UINTN             NumMissed;
  UINTN             PacketSize;
  UINTN             SaveReplyLen;
  UINTN             ReplyLen;
  UINT16            Timeout;

  LastBlockNum  = *StartBlockPtr;
  Timeout       = ListenTimeout;
  *NumMissedPtr = 0;
  PacketSize    = 0;
  BufferSize    = *BufferSizePtr;
  ReplyLen      = MAX_TFTP_PKT_SIZE;;

  //
  // receive
  //
  do {
    if ((SaveReplyLen = ReplyLen) > BufferSize) {
      SaveReplyLen = 0;
    }

    /* %%TBD - add big block number support */

    //
    // get data - loop on resends
    //
    do {
      ReplyLen = SaveReplyLen;

      if ((Status = TftpUdpRead (
                      Private,
                      0,
                      &Header,
                      &ReplyLen,
                      BufferPtr,
                      ServerIpPtr,
                      &MtftpInfoPtr->SPort,
                      &MtftpInfoPtr->MCastIp,
                      &MtftpInfoPtr->CPort,
                      Timeout
                      )) != EFI_SUCCESS) {
        return Status;
      }
      //
      // make sure a data packet
      //
      if (Header.OpCode != HTONS (TFTP_DATA)) {
        return EFI_PROTOCOL_ERROR;
      }
    } while ((BlockNum = NTOHS (Header.BlockNum)) == LastBlockNum);

    //
    // make sure still going up
    //
    if (LastBlockNum > BlockNum) {
      return EFI_PROTOCOL_ERROR;
    }

    if (BlockNum - LastBlockNum > 0xFFFFFFFF) {
      return EFI_PROTOCOL_ERROR;
    } else {
      NumMissed = (UINTN) (BlockNum - LastBlockNum - 1);
    }

    LastBlockNum = BlockNum;

    //
    // if first time through, some reinitialization
    //
    if (!PacketSize) {
      *StartBlockPtr  = BlockNum;
      PacketSize      = ReplyLen;
      Timeout         = TransTimeout;
    } else {
      *NumMissedPtr = (UINT16) (*NumMissedPtr + NumMissed);
    }
    //
    // if missed packets, update start block,
    // etc. and move packet to proper place in buffer
    //
    if (NumMissed) {
      *StartBlockPtr = BlockNum;
      if (!DontUseBuffer) {
        Offset = NumMissed * PacketSize;
        CopyMem (BufferPtr + Offset, BufferPtr, ReplyLen);
        BufferPtr += Offset;
        BufferSize -= Offset;
      }
    }

    if (!DontUseBuffer) {
      BufferPtr += ReplyLen;
      BufferSize -= ReplyLen;
    }
  } while (ReplyLen == PacketSize && BlockNum != FinalBlock);

  *BufferSizePtr = BufferSize;

  return EFI_SUCCESS;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/**

  @return // mtftp open session
  @return // return code EFI_SUCCESS
  @return //      and *CompletionStatusPtr = GOTUNI | GOTMULTI means done
  @return //      and *CompletionStatusPtr = GOTMULTI means got first two multicast packets, use listen for rest
  @return //      and *CompletionStatusPtr = 0 means did not get first two multicast packets, use listen for all
  @retval GOTUNI  returns NO_DATA go will go to TFTP session)

**/
EFI_STATUS
MtftpOpen (
  PXE_BASECODE_DEVICE                                               * Private,
  UINT64                                                            *BufferSizePtr,
  UINT8                                                             *BufferPtr,
  UINTN                                                             *PacketSizePtr,
  EFI_IP_ADDRESS                                                    * ServerIpPtr,
  UINT8                                                             *FilenamePtr,
  EFI_PXE_BASE_CODE_MTFTP_INFO                                      * MtftpInfoPtr,
  UINT8                                                             *CompletionStatusPtr,
#define GOTUNI 1
#define GOTMULTI 2
  IN BOOLEAN                    DontUseBuffer
  )
{
  EFI_STATUS        Status;
  EFI_IP_ADDRESS    OurReplyIp;
  struct Tftpv4Ack  Header;
  INTN              ReplyLen;
  INTN              Retries;
  UINT8             *BufferPtr2;
  UINT8             TmpBuf[514];

  Retries         = NUM_MTFTP_OPEN_RETRIES;
  BufferPtr2      = BufferPtr;
  *PacketSizePtr  = (UINTN) (MIN (*BufferSizePtr, MAX_TFTP_PKT_SIZE));

  do {
    //
    // send a read request
    //
    *CompletionStatusPtr = 0;

    if ((Status = TftpRwReq (
                    TFTP_RRQ,
                    0,
                    Private,
                    ServerIpPtr,
                    &MtftpInfoPtr->SPort,
                    &MtftpInfoPtr->CPort,
                    FilenamePtr,
                    PacketSizePtr,
                    TmpBuf
                    )) != EFI_SUCCESS) {
      return Status;
    }

    for (;;) {
      //
      // read reply
      //
      ZeroMem (&OurReplyIp, Private->IpLength);
      ReplyLen = *PacketSizePtr;

      if ((Status = TftpUdpRead (
                      Private,
                      EFI_PXE_BASE_CODE_UDP_OPFLAGS_USE_FILTER,
                      &Header,
                      (UINTN *) &ReplyLen,
                      BufferPtr2,
                      ServerIpPtr,
                      &MtftpInfoPtr->SPort,
                      &OurReplyIp,
                      &MtftpInfoPtr->CPort,
                      MtftpInfoPtr->TransmitTimeout
                      )) == EFI_SUCCESS) {
        //
        // check for first data packet
        //
        if (Header.OpCode != HTONS (TFTP_DATA)) {
          return EFI_PROTOCOL_ERROR;
        }
        //
        // check block num
        //
        if (Header.BlockNum != HTONS (1)) {
          //
          // it's not first
          // if we are not the primary client,
          // we probably got first and now second
          // multicast but no unicast, so
          // *CompletionStatusPtr = GOTMULTI - if this is
          // the second, can just go on to listen
          // starting with 2 as the last block
          // received
          //
          if (Header.BlockNum != HTONS (2)) {
            //
            // not second
            //
            *CompletionStatusPtr = 0;
          }

          return Status;
        }

        //
        // now actual
        //
        *PacketSizePtr = ReplyLen;
        //
        // see if a unicast data packet
        //
        if (!CompareMem (
              &OurReplyIp,
              &Private->EfiBc.Mode->StationIp,
              Private->IpLength
              )) {
          *CompletionStatusPtr |= GOTUNI;
          //
          // it is
          // if already got multicast packet,
          // got em both
          //
          if (*CompletionStatusPtr & GOTMULTI) {
            break;
          }
        } else if (!CompareMem (
                    &OurReplyIp,
                    &MtftpInfoPtr->MCastIp,
                    Private->IpLength
                    )) {
          //
          // otherwise see if a multicast data packet
          //
          *CompletionStatusPtr |= GOTMULTI;
          //
          // it is
          // got first - bump pointer so that if
          // second multi comes along, we're OK
          //
          if (!DontUseBuffer) {
            BufferPtr2 = (UINT8 *) BufferPtr + ReplyLen;
          }
          //
          // if already got unicast packet,
          // got em both
          //
          if (*CompletionStatusPtr & GOTUNI) {
            break;
          }
        } else {
          //
          // else protocol error
          //
          return EFI_PROTOCOL_ERROR;
        }
      } else if (Status == EFI_TIMEOUT) {
        //
        // bad return code - if timed out, retry
        //
        break;
      } else {
        //
        // else just bad - failed MTFTP open
        //
        return Status;
      }
    }
  } while (Status == EFI_TIMEOUT && --Retries);

  if (Status != EFI_SUCCESS) {
    //
    // open failed
    //
    return Status;
  }
  //
  // got em both - go into receive mode
  // routine to read rest of file after a successful open (TFTP or MTFTP)
  // sends ACK and gets next data packet until short packet arrives,
  // then sends ACK and (hopefully) times out
  //
  return LockStepReceive (
          Private,
          (UINT16) ReplyLen,
          BufferSizePtr,
          ReplyLen,
          BufferPtr,
          ServerIpPtr,
          &MtftpInfoPtr->SPort,
          &MtftpInfoPtr->MCastIp,
          &MtftpInfoPtr->CPort,
          1,
          MtftpInfoPtr->TransmitTimeout,
          DontUseBuffer
          );
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/**


**/
EFI_STATUS
MtftpDownload (
  PXE_BASECODE_DEVICE           *Private,
  UINT64                        *BufferSizePtr,
  UINT8                         *BufferPtr,
  EFI_IP_ADDRESS                *ServerIpPtr,
  UINT8                         *FilenamePtr,
  EFI_PXE_BASE_CODE_MTFTP_INFO  *MtftpInfoPtr,
  IN BOOLEAN                    DontUseBuffer
  )
{
  EFI_PXE_BASE_CODE_IP_FILTER Filter;
  EFI_STATUS                  Status;
  UINT64                      StartBlock;
  UINT64                      LastBlock;
  UINT64                      LastStartBlock;
  UINT64                      BufferSize;
  UINTN                       Offset;
  UINTN                       NumMissed;
  UINT16                      TransTimeout;
  UINT16                      ListenTimeout;
  UINT8                       *BufferPtrLocal;

  TransTimeout      = MtftpInfoPtr->TransmitTimeout;
  ListenTimeout     = MtftpInfoPtr->ListenTimeout;
  LastBlock         = 0;
  LastStartBlock    = 0;
  Offset            = 0;

  Filter.Filters    = EFI_PXE_BASE_CODE_IP_FILTER_BROADCAST;
  Filter.IpCnt      = 2;
  CopyMem (&Filter.IpList[0], &Private->EfiBc.Mode->StationIp, sizeof (EFI_IP_ADDRESS));
  CopyMem (&Filter.IpList[1], &MtftpInfoPtr->MCastIp, sizeof (EFI_IP_ADDRESS));

  if ((Status = IpFilter (Private, &Filter)) != EFI_SUCCESS) {
    return Status;
  }

  for (;;) {
    StartBlock  = LastStartBlock;
    BufferSize  = *BufferSizePtr - Offset;

    if (DontUseBuffer) {
    //
    // overwrie the temp buf
    //
      BufferPtrLocal = BufferPtr;
    } else {
      BufferPtrLocal = BufferPtr + Offset;

    }
    //
    // special !!! do not leave enabled in saved version on Source Safe
    // Following code put in in order to create a special version for regression
    // test of MTFTP server to make sure it handles mulitple opens correctly.
    // This code should NOT be enabled normally.
    //
    if (((Status = MtftpListen (
                      Private,
                      &BufferSize,
                      BufferPtrLocal,
                      ServerIpPtr,
                      MtftpInfoPtr,
                      &StartBlock,
                      &NumMissed,
                      TransTimeout,
                      ListenTimeout,
                      LastBlock,
                      DontUseBuffer
                      )) != EFI_SUCCESS) && (Status != EFI_TIMEOUT)) {
        return Status;
        //
        // failed
        //
    }
    //
    // if none were received, start block is not reset
    //
    if (StartBlock == LastStartBlock) {
      UINT8 CompStat;

      //
      // timed out with none received - try MTFTP open
      //
      if ((Status = MtftpOpen (
                      Private,
                      BufferSizePtr,
                      BufferPtr,
                      &Offset,
                      ServerIpPtr,
                      FilenamePtr,
                      MtftpInfoPtr,
                      &CompStat,
                      DontUseBuffer
                      )) != EFI_SUCCESS) {
        //
        // open failure - try TFTP
        //
        return Status;
      }
      //
      // return code EFI_SUCCESS
      // and *CompletionStatusPtr = GOTUNI | GOTMULTI means done
      // and *CompletionStatusPtr = GOTMULTI means got first two multicast packets, use listen for rest
      // and *CompletionStatusPtr = 0 means did not get first two multicast packets, use listen for all
      // (do not get = GOTUNI - returns NO_DATA go will go to TFTP session)
      //
      if (CompStat == (GOTUNI | GOTMULTI)) {
      //
      // finished - got it all
      //
        return Status;
      }

      if (CompStat) {
        //
        // offset is two packet lengths
        //
        Offset <<= 1;
        //
        // last block received
        //
        LastStartBlock = 2;
      } else {
        Offset          = 0;
        LastStartBlock  = 0;
      }

      ListenTimeout = TransTimeout;
      continue;
    }
    //
    // did we get the last block
    //
    if (Status == EFI_SUCCESS) {
      //
      // yes - set the file size if this was first time
      //
      if (!LastBlock) {
        *BufferSizePtr -= BufferSize;
      }
      //
      // if buffer was too small, finished
      //
      if (!DontUseBuffer) {
        return EFI_BUFFER_TOO_SMALL;
      }
      //
      // if we got them all, finished
      //
      if (!NumMissed && StartBlock == LastStartBlock + 1) {
        return Status;
      }
      //
      // did not get them all - set last block
      //
      LastBlock = (UINT16) (StartBlock - 1);
    }
    //
    // compute listen timeout
    //
    ListenTimeout = (UINT16) ((NumMissed > MtftpInfoPtr->ListenTimeout) ? 0 : (MtftpInfoPtr->ListenTimeout - NumMissed));

    //
    // reset
    //
    Offset          = 0;
    LastStartBlock  = 0;
  }
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/**


**/
EFI_STATUS
TftpInfo (
  PXE_BASECODE_DEVICE         *Private,
  UINT64                      *BufferSizePtr,
  EFI_IP_ADDRESS              *ServerIpPtr,
  EFI_PXE_BASE_CODE_UDP_PORT  SrvPort,
  UINT8                       *FilenamePtr,
  UINTN                       *PacketSizePtr
  )
{
  EFI_PXE_BASE_CODE_UDP_PORT  OurPort;
  EFI_PXE_BASE_CODE_UDP_PORT  ServerReplyPort;
  EFI_STATUS                  Status;
  UINT64                      BlockNum;
  UINTN                       Offset;
  UINTN                       ReplyLen;
  UINT8                       *Ptr;

  union {
    struct Tftpv4Oack OAck2Ptr;
    struct Tftpv4Ack  Ack2Ptr;
    struct Tftpv4Data Datastr;
  } u;

  OurPort         = 0;
  ServerReplyPort = 0;
  ReplyLen        = sizeof (u.Datastr.Data);

  //
  // send a write request with the blocksize option -
  // sets our IP and port - and receive reply - sets his port
  // will retry operation up to 3 times if no response,
  // and will retry without options on an error reply
  //
  if ((Status = TftpRwReqwResp (
                  TFTP_RRQ,
                  /* BIGBLKNUMOP | */BKSZOP | TSIZEOP,
                  Private,
                  &u,
                  PacketSizePtr,
                  &ReplyLen,
                  u.Datastr.Data,
                  ServerIpPtr,
                  &SrvPort,
                  &ServerReplyPort,
                  &OurPort,
                  FilenamePtr,
                  REQ_RESP_TIMEOUT
                  )) != EFI_SUCCESS) {
    DEBUG ((DEBUG_WARN, "\nTftpInfo()  Exit #1"));
    return Status;
  }
  //
  // check for good OACK
  //
  if (u.OAck2Ptr.OpCode == HTONS (TFTP_OACK)) {
    //
    // now parse it for options
    // bigblk#
    //
    Ptr = FindOption (
            BigBlkNumOp,
            sizeof (BigBlkNumOp),
            u.OAck2Ptr.OpAck[0].Option,
            ReplyLen + sizeof (u.Ack2Ptr.BlockNum)
            );

    if (Ptr != NULL) {
      if (AtoU (Ptr) == 8) {
        Private->BigBlkNumFlag = TRUE;
      } else {
        return EFI_PROTOCOL_ERROR;
      }
    }
    //
    // blksize
    //
    Ptr = FindOption (
            BlockSizeOp,
            sizeof (BlockSizeOp),
            u.OAck2Ptr.OpAck[0].Option,
            ReplyLen += sizeof (u.Ack2Ptr.BlockNum)
            );

    *PacketSizePtr = (Ptr) ? AtoU (Ptr) : 512;

    //
    // tsize
    //
    Ptr = FindOption (
            TsizeOp,
            sizeof (TsizeOp),
            u.OAck2Ptr.OpAck[0].Option,
            ReplyLen
            );

    if (Ptr != NULL) {
      *BufferSizePtr = AtoU64 (Ptr);

      //
      // teminate session with error
      //
      SendError (Private, ServerIpPtr, &ServerReplyPort, &OurPort);

      return EFI_SUCCESS;
    }

    Offset    = 0;
    BlockNum  = 0;
  } else {
    //
    // if MTFTP get filesize, return unsupported
    //
    if (SrvPort != TftpRequestPort) {
      SendError (Private, ServerIpPtr, &ServerReplyPort, &OurPort);
      DEBUG ((DEBUG_WARN, "\nTftpInfo()  Exit #3"));
      return EFI_UNSUPPORTED;
    }

    Offset    = ReplyLen;
    //
    // last block received
    //
    BlockNum  = 1;
  }
  //
  // does not support the option - do a download with no buffer
  //
  *BufferSizePtr = 0;

  Status = LockStepReceive (
            Private,
            (UINT16) ReplyLen,
            BufferSizePtr,
            Offset,
            (UINT8 *) &u,
            ServerIpPtr,
            &ServerReplyPort,
            &Private->EfiBc.Mode->StationIp,
            &OurPort,
            BlockNum,
            ACK_TIMEOUT,
            TRUE
            );

  if (Status != EFI_SUCCESS) {
    DEBUG ((DEBUG_WARN, "\nTftpInfo()  LockStepReceive() == %Xh", Status));
  }

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

  return EFI_SUCCESS;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/**


**/
EFI_STATUS
TftpDownload (
  PXE_BASECODE_DEVICE         *Private,
  UINT64                      *BufferSizePtr,
  UINT8                       *BufferPtr,
  EFI_IP_ADDRESS              *ServerIpPtr,
  UINT8                       *FilenamePtr,
  UINTN                       *PacketSizePtr,
  EFI_PXE_BASE_CODE_UDP_PORT  SrvPort,
  UINT16                      Req,
  IN BOOLEAN                  DontUseBuffer
  )
{
  EFI_PXE_BASE_CODE_UDP_PORT  OurPort;
  EFI_PXE_BASE_CODE_UDP_PORT  ServerReplyPort;
  EFI_STATUS                  Status;
  UINT64                      Offset;
  UINT64                      BlockNum;
  UINTN                       ReplyLen;
  UINT8                       *Ptr;

  union {
    struct Tftpv4Ack    Ack2Ptr;
    struct Tftpv4Oack   OAck2Ptr;
    struct Tftpv4Data   Data;
    struct Tftpv4Ack8   Ack8Ptr;
    struct Tftpv4Data8  Data8;
  } U;

  OurPort         = 0;
  ServerReplyPort = 0;
  ReplyLen        = (UINTN) ((*BufferSizePtr > 0xFFFF) ? 0xFFFF : *BufferSizePtr);

  //
  // send a read request with the blocksize option - sets our IP and port
  // - and receive reply - sets his port will retry operation up to 3
  // times if no response, and will retry without options on an error
  // reply
  //
  if ((Status = TftpRwReqwResp (
                  Req,
                  /* BIGBLKNUMOP | */BKSZOP,
                  Private,
                  &U,
                  PacketSizePtr,
                  &ReplyLen,
                  BufferPtr,
                  ServerIpPtr,
                  &SrvPort,
                  &ServerReplyPort,
                  &OurPort,
                  FilenamePtr,
                  REQ_RESP_TIMEOUT
                  )) != EFI_SUCCESS) {
    DEBUG ((DEBUG_WARN, "\nTftpDownload()  Exit #1  %xh (%r)", Status, Status));
    return Status;
  }
  //
  // check for OACK
  //
  if (U.OAck2Ptr.OpCode == HTONS (TFTP_OACK)) {
    //
    // get the OACK
    //
    CopyMem (U.Data.Data, BufferPtr, ReplyLen);

    Ptr = FindOption (
            BigBlkNumOp,
            sizeof (BigBlkNumOp),
            U.OAck2Ptr.OpAck[0].Option,
            ReplyLen + sizeof (U.Ack2Ptr.BlockNum)
            );

    if (Ptr != NULL) {
      if (AtoU (Ptr) == 8) {
        Private->BigBlkNumFlag = TRUE;
      } else {
        return EFI_PROTOCOL_ERROR;
      }
    }
    //
    // now parse it for blocksize option
    //
    Ptr = FindOption (
            BlockSizeOp,
            sizeof (BlockSizeOp),
            U.OAck2Ptr.OpAck[0].Option,
            ReplyLen += sizeof (U.Ack2Ptr.BlockNum)
            );

    ReplyLen  = (Ptr != NULL) ? AtoU (Ptr) : 512;

    Offset    = 0;
    //
    // last block received
    //
    BlockNum  = 0;
  } else if (U.Ack2Ptr.OpCode != HTONS (TFTP_DATA) || U.Ack2Ptr.BlockNum != HTONS (1)) {
    //
    // or data
    //
    DEBUG ((DEBUG_WARN, "\nTftpDownload()  Exit #2  %xh (%r)", Status, Status));

    return EFI_PROTOCOL_ERROR;
  } else {
    //
    // got good data packet
    //
    Offset    = ReplyLen;
    //
    // last block received
    //
    BlockNum  = 1;
  }

  if (PacketSizePtr != NULL) {
    *PacketSizePtr = ReplyLen;
  }
  //
  // routine to read rest of file after a successful open (TFTP or MTFTP)
  // sends ACK and gets next data packet until short packet arrives, then sends
  // ACK and (hopefully) times out
  // if first packet has been read, BufferPtr and BufferSize must reflect fact
  //
  Status = LockStepReceive (
            Private,
            ReplyLen,
            BufferSizePtr,
            Offset,
            BufferPtr,
            ServerIpPtr,
            &ServerReplyPort,
            &Private->EfiBc.Mode->StationIp,
            &OurPort,
            BlockNum,
            ACK_TIMEOUT,
            DontUseBuffer
            );

  if (Status != EFI_SUCCESS) {
    DEBUG ((DEBUG_WARN, "\nTftpDownload()  Exit #3  %xh (%r)", Status, Status));

    if (Status == EFI_BUFFER_TOO_SMALL) {
      Status = TftpInfo (
                Private,
                BufferSizePtr,
                ServerIpPtr,
                SrvPort,
                FilenamePtr,
                PacketSizePtr
                );

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

  return Status;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/**


**/
EFI_STATUS
TftpUpload (
  PXE_BASECODE_DEVICE *Private,
  UINT64              *BufferSizePtr,
  VOID                *BufferPtr,
  EFI_IP_ADDRESS      *ServerIpPtr,
  UINT8               *FilenamePtr,
  UINTN               *PacketSizePtr,
  BOOLEAN             Overwrite
  )
{
  struct Tftpv4Ack            Header;
  EFI_PXE_BASE_CODE_UDP_PORT  OurPort;
  EFI_PXE_BASE_CODE_UDP_PORT  ServerReplyPort;
  EFI_STATUS                  Status;
  UINT64                      BlockNum;
  UINT64                      TransferSize;
  UINTN                       ReplyLen;
  UINTN                       TransferLen;
  UINT16                      Options;
  UINT8                       *Ptr;

  union {
    struct Tftpv4Oack OAck2Ptr;
    struct Tftpv4Ack  Ack2Ptr;
    struct Tftpv4Data Datastr;
  } u;

  OurPort         = 0;
  ServerReplyPort = 0;
  TransferSize    = *BufferSizePtr;
  ReplyLen        = sizeof (u.Datastr.Data);
  Options         = (UINT16) ((Overwrite) ? OVERWRITEOP | BKSZOP : BKSZOP);

  //
  // send a write request with the blocksize option - sets our IP and port -
  // and receive reply - sets his port
  // will retry operation up to 3 times if no response, and will retry without
  // options on an error reply
  //
  if ((Status = TftpRwReqwResp (
                  TFTP_WRQ,
                  Options,
                  Private,
                  &u,
                  PacketSizePtr,
                  &ReplyLen,
                  u.Datastr.Data,
                  ServerIpPtr,
                  &TftpRequestPort,
                  &ServerReplyPort,
                  &OurPort,
                  FilenamePtr,
                  REQ_RESP_TIMEOUT
                  )) != EFI_SUCCESS) {
    return Status;
  }
  //
  // check for OACK
  //
  if (u.OAck2Ptr.OpCode == HTONS (TFTP_OACK)) {
    //
    // parse it for blocksize option
    //
    Ptr = FindOption (
            BlockSizeOp,
            sizeof (BlockSizeOp),
            u.OAck2Ptr.OpAck[0].Option,
            ReplyLen += sizeof (u.Ack2Ptr.BlockNum)
            );
    *PacketSizePtr = (Ptr) ? AtoU (Ptr) : 512;
  }
  //
  // or ACK
  //
  else if (u.Ack2Ptr.OpCode == HTONS (TFTP_ACK)) {
    //
    // option was not supported
    //
    *PacketSizePtr = 512;
  } else {
    return EFI_PROTOCOL_ERROR;
  }
  //
  // loop
  //
  Header.OpCode   = HTONS (TFTP_DATA);
  BlockNum        = 1;
  Header.BlockNum = HTONS (1);

  do {
    UINTN HeaderSize;
    INTN  Retries;

    Retries     = NUM_ACK_RETRIES;
    HeaderSize  = sizeof (Header);
    TransferLen = (UINTN) (MIN (*PacketSizePtr, TransferSize));

    //
    // write a data packet and get an ack
    //
    do {
      //
      // write
      //
      if ((Status = UdpWrite (
                      Private,
                      EFI_PXE_BASE_CODE_UDP_OPFLAGS_MAY_FRAGMENT,
                      ServerIpPtr,
                      &ServerReplyPort,
                      0,
                      0,
                      &OurPort,
                      &HeaderSize,
                      &Header,
                      &TransferLen,
                      BufferPtr
                      )) != EFI_SUCCESS) {
        return Status;
      }
      //
      // read reply
      //
      ReplyLen = sizeof (u.Datastr.Data);

      if ((Status = TftpUdpRead (
                      Private,
                      0,
                      &u,
                      &ReplyLen,
                      u.Datastr.Data,
                      ServerIpPtr,
                      &ServerReplyPort,
                      0,
                      &OurPort,
                      ACK_TIMEOUT
                      )) == EFI_SUCCESS) {
        //
        // check for ACK for this data packet
        //
        if (u.Ack2Ptr.OpCode != HTONS (TFTP_ACK)) {
          return EFI_PROTOCOL_ERROR;
        }

        if (u.Ack2Ptr.BlockNum != Header.BlockNum) {
          //
          // not for this packet - continue
          //
          Status = EFI_TIMEOUT;
        }
      }
    } while (Status == EFI_TIMEOUT && --Retries);

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

    BufferPtr = (VOID *) ((UINT8 *) (BufferPtr) + TransferLen);
    TransferSize -= TransferLen;
    ++BlockNum;
    Header.BlockNum = HTONS ((UINT16) BlockNum);
  } while (TransferLen == *PacketSizePtr);

  return EFI_SUCCESS;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/**

  @return *  EFI_INVALID_PARAMETER
  @return *  EFI_OUT_OF_RESOURCES
  @return *  EFI_BAD_BUFFER_SIZE
  @return *  Status is also returned from IpFilter(), TftpInfo(), MtftpDownload(),
  @return *  TftpDownload() and TftpUpload().

**/
EFI_STATUS
PxeBcMtftp (
  PXE_BASECODE_DEVICE               *Private,
  IN EFI_PXE_BASE_CODE_TFTP_OPCODE  Operation,
  UINT64                            *BufferSizePtr,
  VOID                              *BufferPtr,
  EFI_IP_ADDRESS                    *ServerIpPtr,
  UINT8                             *FilenamePtr,
  UINTN                             *PacketSizePtr,
  IN EFI_PXE_BASE_CODE_MTFTP_INFO   *MtftpInfoPtr, OPTIONAL
  IN BOOLEAN                        Overwrite,
  IN BOOLEAN                        DontUseBuffer
  )
{
  EFI_PXE_BASE_CODE_IP_FILTER Filter;
  EFI_STATUS                  StatCode;
  EFI_STATUS                  Status;
  UINT64                      BufferSizeLocal;
  UINTN                       PacketSize;
  UINT8                       *BufferPtrLocal;

  Filter.Filters  = EFI_PXE_BASE_CODE_IP_FILTER_STATION_IP;
  Filter.IpCnt    = 0;
  Filter.reserved = 0;

  /* No error has occurred, yet. */
  Private->EfiBc.Mode->TftpErrorReceived = FALSE;

  /* We must at least have an MTFTP server IP address and
   * a pointer to the buffer size.
   */
  if (!ServerIpPtr || !BufferSizePtr) {
    DEBUG ((DEBUG_WARN, "\nPxeBcMtftp()  Exit #1"));

    return EFI_INVALID_PARAMETER;
  }

  Private->Function = EFI_PXE_BASE_CODE_FUNCTION_MTFTP;

  //
  // make sure filter set to unicast at start
  //
  if ((StatCode = IpFilter (Private, &Filter)) != EFI_SUCCESS) {
    DEBUG (
      (DEBUG_NET,
      "\nPxeBcMtftp()  Exit  IpFilter() == %Xh",
      StatCode)
      );

    return StatCode;
  }
  //
  // set unset parms to default values
  //
  if (!PacketSizePtr) {
    *(PacketSizePtr = &PacketSize) = MAX_TFTP_PKT_SIZE;
  }

  if ((*PacketSizePtr > *BufferSizePtr) &&
    (Operation != EFI_PXE_BASE_CODE_TFTP_GET_FILE_SIZE) &&
    (Operation != EFI_PXE_BASE_CODE_MTFTP_GET_FILE_SIZE)) {
    *PacketSizePtr = MAX ((UINTN) *BufferSizePtr, MIN_TFTP_PKT_SIZE);
  }

  if (*PacketSizePtr < MIN_TFTP_PKT_SIZE) {
    *PacketSizePtr = MIN_TFTP_PKT_SIZE;
    return EFI_INVALID_PARAMETER;
  }

  if (*PacketSizePtr > BUFFER_ALLOCATE_SIZE) {
    *PacketSizePtr = BUFFER_ALLOCATE_SIZE;
  }

  if (*PacketSizePtr > MAX_TFTP_PKT_SIZE) {
    *PacketSizePtr = MAX_TFTP_PKT_SIZE;
  }

  if (Operation == EFI_PXE_BASE_CODE_TFTP_GET_FILE_SIZE) {
    StatCode = TftpInfo (
                Private,
                BufferSizePtr,
                ServerIpPtr,
                TftpRequestPort,
                FilenamePtr,
                PacketSizePtr
                );

    if (StatCode != EFI_SUCCESS) {
      DEBUG (
        (DEBUG_WARN,
        "\nPxeBcMtftp()  Exit  TftpInfo() == %Xh",
        StatCode)
        );
    }

    return StatCode;
  }

  if (Operation == EFI_PXE_BASE_CODE_MTFTP_GET_FILE_SIZE) {
    if (!MtftpInfoPtr || !MtftpInfoPtr->SPort) {
      DEBUG ((DEBUG_WARN, "\nPxeBcMtftp()  Exit #2"));
      return EFI_INVALID_PARAMETER;
    } else {
      StatCode = TftpInfo (
                  Private,
                  BufferSizePtr,
                  ServerIpPtr,
                  MtftpInfoPtr->SPort,
                  FilenamePtr,
                  PacketSizePtr
                  );

      gBS->Stall (10000);

      if (StatCode != EFI_SUCCESS) {
        DEBUG (
          (DEBUG_WARN,
          "\nPxeBcMtftp()  Exit  TftpInfo() == %Xh",
          StatCode)
          );
      }

      return StatCode;
    }
  }

  if (!BufferPtr && !DontUseBuffer) {
    //
    // if dontusebuffer is false and no buffer???
    //
    DEBUG ((DEBUG_WARN, "\nPxeBcMtftp()  Exit #3"));
    //
    // DontUseBuffer can be true only for read_file operation
    //
    return EFI_INVALID_PARAMETER;
  }

  if (DontUseBuffer) {
    Status = gBS->AllocatePool (
                    EfiBootServicesData,
                    BUFFER_ALLOCATE_SIZE,
                    (VOID **) &BufferPtrLocal
                    );

    if (EFI_ERROR (Status) || BufferPtrLocal == NULL) {
      DEBUG ((DEBUG_NET, "\nPxeBcMtftp()  Exit #4"));
      return EFI_OUT_OF_RESOURCES;
    }

    BufferSizeLocal = BUFFER_ALLOCATE_SIZE;
  } else {
    if (!*BufferSizePtr && Operation != EFI_PXE_BASE_CODE_TFTP_WRITE_FILE) {
      DEBUG ((DEBUG_WARN, "\nPxeBcMtftp()  Exit #5"));
      return EFI_BAD_BUFFER_SIZE;
    }

    BufferPtrLocal  = BufferPtr;
    BufferSizeLocal = *BufferSizePtr;
  }

  switch (Operation) {
  case EFI_PXE_BASE_CODE_MTFTP_READ_FILE:
    if (FilenamePtr == NULL ||
        MtftpInfoPtr == NULL ||
        MtftpInfoPtr->MCastIp.Addr[0] == 0 ||
        MtftpInfoPtr->SPort == 0 ||
        MtftpInfoPtr->CPort == 0 ||
        MtftpInfoPtr->ListenTimeout == 0 ||
        MtftpInfoPtr->TransmitTimeout == 0
        ) {
      StatCode = EFI_INVALID_PARAMETER;
      break;
    }
    //
    // try MTFTP - if fails, drop into TFTP read
    //
    if ((StatCode = MtftpDownload (
                      Private,
                      &BufferSizeLocal,
                      BufferPtrLocal,
                      ServerIpPtr,
                      FilenamePtr,
                      MtftpInfoPtr,
                      DontUseBuffer
                      )) == EFI_SUCCESS || StatCode == EFI_BUFFER_TOO_SMALL) {
      if (BufferSizePtr /* %% !DontUseBuffer */ ) {
        *BufferSizePtr = BufferSizeLocal;
      }

      break;
    }
    //
    // go back to unicast
    //
    if ((StatCode = IpFilter (Private, &Filter)) != EFI_SUCCESS) {
      break;
    }

  /* fall thru */
  case EFI_PXE_BASE_CODE_TFTP_READ_FILE:
    if (FilenamePtr == NULL) {
      StatCode = EFI_INVALID_PARAMETER;
      break;
    }

    StatCode = TftpDownload (
                Private,
                &BufferSizeLocal,
                BufferPtrLocal,
                ServerIpPtr,
                FilenamePtr,
                PacketSizePtr,
                TftpRequestPort,
                TFTP_RRQ,
                DontUseBuffer
                );

    if (StatCode == EFI_SUCCESS || StatCode == EFI_BUFFER_TOO_SMALL) {
      if (BufferSizePtr /* !DontUseBuffer */ ) {
        *BufferSizePtr = BufferSizeLocal;
      }
    }

    break;

  case EFI_PXE_BASE_CODE_TFTP_WRITE_FILE:
    if (FilenamePtr == NULL || DontUseBuffer) {
      //
      // not a valid option
      //
      StatCode = EFI_INVALID_PARAMETER;
      break;
    }

    StatCode = TftpUpload (
                Private,
                BufferSizePtr,
                BufferPtr,
                ServerIpPtr,
                FilenamePtr,
                PacketSizePtr,
                Overwrite
                );

    if (StatCode != EFI_SUCCESS) {
      DEBUG (
        (DEBUG_WARN,
        "\nPxeBcMtftp()  Exit #6  %xh (%r)",
        StatCode,
        StatCode)
        );
    }

    return StatCode;

  case EFI_PXE_BASE_CODE_TFTP_READ_DIRECTORY:
    if (FilenamePtr == NULL || DontUseBuffer) {
      //
      // not a valid option
      //
      StatCode = EFI_INVALID_PARAMETER;
      break;
    }

    StatCode = TftpDownload (
                Private,
                BufferSizePtr,
                BufferPtr,
                ServerIpPtr,
                FilenamePtr,
                PacketSizePtr,
                TftpRequestPort,
                TFTP_DIR,
                DontUseBuffer
                );

    if (StatCode != EFI_SUCCESS) {
      DEBUG (
        (DEBUG_WARN,
        "\nPxeBcMtftp()  Exit #7  %xh (%r)",
        StatCode,
        StatCode)
        );
    }

    return StatCode;

  case EFI_PXE_BASE_CODE_MTFTP_READ_DIRECTORY:
    if (DontUseBuffer) {
      StatCode = EFI_INVALID_PARAMETER;
      break;
    }

    if (MtftpInfoPtr == NULL || !MtftpInfoPtr->SPort) {
      DEBUG (
        (DEBUG_WARN,
        "\nPxeBcMtftp()  Exit #9  %xh (%r)",
        EFI_INVALID_PARAMETER,
        EFI_INVALID_PARAMETER)
        );

      return EFI_INVALID_PARAMETER;
    }

    StatCode = TftpDownload (
                Private,
                BufferSizePtr,
                BufferPtr,
                ServerIpPtr,
                (UINT8 *) "/",
                PacketSizePtr,
                MtftpInfoPtr->SPort,
                TFTP_DIR,
                DontUseBuffer
                );

    break;

  default:
    StatCode = EFI_INVALID_PARAMETER;
  }

  if (DontUseBuffer) {
    gBS->FreePool (BufferPtrLocal);
  }

  if (StatCode != EFI_SUCCESS) {
    DEBUG (
      (DEBUG_WARN,
      "\nPxeBcMtftp()  Exit #8  %xh (%r)",
      StatCode,
      StatCode)
      );
  }

  gBS->Stall (10000);

  return StatCode;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/**

  @return *  EFI_INVALID_PARAMETER
  @return *  Status is also returned from PxeBcMtftp();

**/
EFI_STATUS
EFIAPI
BcMtftp (
  IN EFI_PXE_BASE_CODE_PROTOCOL       * This,
  IN EFI_PXE_BASE_CODE_TFTP_OPCODE    Operation,
  IN OUT VOID                         *BufferPtr,
  IN BOOLEAN                          Overwrite,
  IN OUT UINT64                       *BufferSizePtr,
  IN UINTN                            *BlockSizePtr OPTIONAL,
  IN EFI_IP_ADDRESS                   * ServerIpPtr,
  IN UINT8                            *FilenamePtr,
  IN EFI_PXE_BASE_CODE_MTFTP_INFO     * MtftpInfoPtr OPTIONAL,
  IN BOOLEAN                          DontUseBuffer
  )
{
  EFI_PXE_BASE_CODE_IP_FILTER Filter;
  EFI_STATUS                  StatCode;
  PXE_BASECODE_DEVICE         *Private;

  //
  // Lock the instance data and make sure started
  //
  StatCode = EFI_SUCCESS;

  if (This == NULL) {
    DEBUG ((DEBUG_ERROR, "BC *This pointer == NULL"));
    return EFI_INVALID_PARAMETER;
  }

  Private = CR (This, PXE_BASECODE_DEVICE, EfiBc, PXE_BASECODE_DEVICE_SIGNATURE);

  if (Private == NULL) {
    DEBUG ((DEBUG_ERROR, "PXE_BASECODE_DEVICE poiner == NULL"));
    return EFI_INVALID_PARAMETER;
  }

  if (!IS_INADDR_UNICAST (ServerIpPtr)) {
      //
      // The station IP is not a unicast address.
      //
      return EFI_INVALID_PARAMETER;
  }

  EfiAcquireLock (&Private->Lock);

  if (This->Mode == NULL || !This->Mode->Started) {
    DEBUG ((DEBUG_ERROR, "BC was not started."));
    EfiReleaseLock (&Private->Lock);
    return EFI_NOT_STARTED;
  }
  //
  // Issue BC command
  //
  Filter.Filters  = EFI_PXE_BASE_CODE_IP_FILTER_STATION_IP;
  Filter.IpCnt    = 0;
  Filter.reserved = 0;

  DEBUG ((DEBUG_WARN, "\nBcMtftp()  Op=%d  Buf=%Xh", Operation, BufferPtr));

  StatCode = PxeBcMtftp (
              Private,
              Operation,
              BufferSizePtr,
              BufferPtr,
              ServerIpPtr,
              FilenamePtr,
              BlockSizePtr,
              MtftpInfoPtr,
              Overwrite,
              DontUseBuffer
              );

  //
  // restore to unicast
  //
  IpFilter (Private, &Filter);

  //
  // Unlock the instance data
  //
  EfiReleaseLock (&Private->Lock);
  return StatCode;
}

/* eof - PxeBcMtftp.c */
