blob: 30d9dae41da5cf17e20fb4a2dd09c5ebc74d952e [file] [log] [blame]
/** @file
This is a simple TFTP server application
Copyright (c) 2011, 2012, 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.
**/
#include <TftpServer.h>
TSDT_TFTP_SERVER mTftpServer; ///< TFTP server's control structure
volatile BOOLEAN mbTftpServerExit; ///< Set TRUE to cause TFTP server to exit
/**
Read file data into a buffer
@param [in] pContext Connection context structure address
@retval TRUE if a read error occurred
**/
BOOLEAN
BufferFill (
IN TSDT_CONNECTION_CONTEXT * pContext
)
{
BOOLEAN bReadError;
size_t BytesRead;
UINT64 LengthInBytes;
DBG_ENTER ( );
//
// Use break instead of goto
//
bReadError = FALSE;
for ( ; ; ) {
//
// Determine if there is any work to do
//
LengthInBytes = DIM ( pContext->FileData ) >> 1;
if (( pContext->ValidBytes > LengthInBytes )
|| ( 0 == pContext->BytesRemaining )) {
break;
}
//
// Determine the number of bytes to read
//
if ( LengthInBytes > pContext->BytesRemaining ) {
LengthInBytes = pContext->BytesRemaining;
}
//
// Read in the next portion of the file
//
BytesRead = fread ( pContext->pFill,
1,
(size_t)LengthInBytes,
pContext->File );
if ( -1 == BytesRead ) {
bReadError = TRUE;
break;
}
//
// Account for the file data read
//
pContext->BytesRemaining -= BytesRead;
pContext->ValidBytes += BytesRead;
DEBUG (( DEBUG_FILE_BUFFER,
"0x%08x: Buffer filled with %Ld bytes, %Ld bytes ramaining\r\n",
pContext->pFill,
BytesRead,
pContext->BytesRemaining ));
//
// Set the next buffer location
//
pContext->pFill += BytesRead;
if ( pContext->pEnd <= pContext->pFill ) {
pContext->pFill = &pContext->FileData[ 0 ];
}
//
// Verify that the end of the buffer is reached
//
ASSERT ( 0 == ( DIM ( pContext->FileData ) & 1 ));
break;
}
//
// Return the read status
//
DBG_EXIT ( );
return bReadError;
}
/**
Add a connection context to the list of connection contexts.
@param [in] pTftpServer Address of the ::TSDT_TFTP_SERVER structure
@param [in] SocketFd Socket file descriptor
@retval Context structure address, NULL if allocation fails
**/
TSDT_CONNECTION_CONTEXT *
ContextAdd (
IN TSDT_TFTP_SERVER * pTftpServer,
IN int SocketFd
)
{
TSDT_CONNECTION_CONTEXT * pContext;
TFTP_PACKET * pEnd;
TFTP_PACKET * pPacket;
DBG_ENTER ( );
//
// Allocate a new context
//
pContext = (TSDT_CONNECTION_CONTEXT *)AllocateZeroPool ( sizeof ( *pContext ));
if ( NULL != pContext ) {
//
// Initialize the context
//
pContext->SocketFd = SocketFd;
CopyMem ( &pContext->RemoteAddress,
&pTftpServer->RemoteAddress,
sizeof ( pContext->RemoteAddress ));
pContext->BlockSize = 512;
//
// Buffer management
//
pContext->pFill = &pContext->FileData[ 0 ];
pContext->pEnd = &pContext->FileData[ sizeof ( pContext->FileData )];
pContext->pBuffer = pContext->pFill;
//
// Window management
//
pContext->MaxTimeout = MultU64x32 ( PcdGet32 ( Tftp_MaxTimeoutInSec ),
2 * 1000 * 1000 * 1000 );
pContext->Rtt2x = pContext->MaxTimeout;
pContext->WindowSize = MAX_PACKETS;
WindowTimeout ( pContext );
//
// Place the packets on the free list
//
pPacket = &pContext->Tx[ 0 ];
pEnd = &pPacket[ DIM ( pContext->Tx )];
while ( pEnd > pPacket ) {
PacketFree ( pContext, pPacket );
pPacket += 1;
}
//
// Display the new context
//
if ( AF_INET == pTftpServer->RemoteAddress.v4.sin_family ) {
DEBUG (( DEBUG_PORT_WORK,
"0x%08x: Context for %d.%d.%d.%d:%d\r\n",
pContext,
(UINT8)pTftpServer->RemoteAddress.v4.sin_addr.s_addr,
(UINT8)( pTftpServer->RemoteAddress.v4.sin_addr.s_addr >> 8 ),
(UINT8)( pTftpServer->RemoteAddress.v4.sin_addr.s_addr >> 16 ),
(UINT8)( pTftpServer->RemoteAddress.v4.sin_addr.s_addr >> 24 ),
htons ( pTftpServer->RemoteAddress.v4.sin_port )));
}
else {
DEBUG (( DEBUG_PORT_WORK,
"0x%08x: Context for [%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x]:%d\r\n",
pContext,
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 0 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 1 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 2 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 3 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 4 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 5 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 6 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 7 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 8 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 9 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 10 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 11 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 12 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 13 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 14 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 15 ],
htons ( pTftpServer->RemoteAddress.v6.sin6_port )));
}
//
// Add the context to the context list
//
pContext->pNext = pTftpServer->pContextList;
pTftpServer->pContextList = pContext;
}
//
// Return the connection context
//
DBG_EXIT_STATUS ( pContext );
return pContext;
}
/**
Locate a remote connection context.
@param [in] pTftpServer Address of the ::TSDT_TFTP_SERVER structure
@param [in] pIpAddress The start of the remote IP address in network order
@param [in] Port The remote port number
@retval Context structure address, NULL if not found
**/
TSDT_CONNECTION_CONTEXT *
ContextFind (
IN TSDT_TFTP_SERVER * pTftpServer
)
{
TSDT_CONNECTION_CONTEXT * pContext;
DBG_ENTER ( );
//
// Walk the list of connection contexts
//
pContext = pTftpServer->pContextList;
while ( NULL != pContext ) {
//
// Attempt to locate the remote network connection
//
if ( 0 == memcmp ( &pTftpServer->RemoteAddress,
&pContext->RemoteAddress,
pTftpServer->RemoteAddress.v6.sin6_len )) {
//
// The connection was found
//
DEBUG (( DEBUG_TFTP_REQUEST,
"0x%08x: pContext found\r\n",
pContext ));
break;
}
//
// Set the next context
//
pContext = pContext->pNext;
}
//
// Return the connection context structure address
//
DBG_EXIT_HEX ( pContext );
return pContext;
}
/**
Remove a context from the list.
@param [in] pTftpServer Address of the ::TSDT_TFTP_SERVER structure
@param [in] pContext Address of a ::TSDT_CONNECTION_CONTEXT structure
**/
VOID
ContextRemove (
IN TSDT_TFTP_SERVER * pTftpServer,
IN TSDT_CONNECTION_CONTEXT * pContext
)
{
TSDT_CONNECTION_CONTEXT * pNextContext;
TSDT_CONNECTION_CONTEXT * pPreviousContext;
DBG_ENTER ( );
//
// Attempt to locate the context in the list
//
pPreviousContext = NULL;
pNextContext = pTftpServer->pContextList;
while ( NULL != pNextContext ) {
//
// Determine if the context was found
//
if ( pNextContext == pContext ) {
//
// Remove the context from the list
//
if ( NULL == pPreviousContext ) {
pTftpServer->pContextList = pContext->pNext;
}
else {
pPreviousContext->pNext = pContext->pNext;
}
break;
}
//
// Set the next context
//
pPreviousContext = pNextContext;
pNextContext = pNextContext->pNext;
}
//
// Determine if the context was found
//
if ( NULL != pContext ) {
//
// Return the resources
//
gBS->FreePool ( pContext );
}
DBG_EXIT ( );
}
/**
Queue data packets for transmission
@param [in] pContext Connection context structure address
@retval TRUE if a read error occurred
**/
BOOLEAN
PacketFill (
IN TSDT_CONNECTION_CONTEXT * pContext
)
{
BOOLEAN bReadError;
UINT64 LengthInBytes;
UINT8 * pBuffer;
TFTP_PACKET * pPacket;
DBG_ENTER ( );
//
// Use break instead of goto
//
bReadError = FALSE;
for ( ; ; ) {
//
// Fill the buffer if necessary
//
bReadError = BufferFill ( pContext );
if ( bReadError ) {
//
// File access mode not supported
//
DEBUG (( DEBUG_ERROR | DEBUG_TFTP_REQUEST,
"ERROR - File read failure!\r\n" ));
//
// Tell the client of the error
//
SendError ( pContext,
TFTP_ERROR_SEE_MSG,
(UINT8 *)"Read failure" );
break;
}
//
// Determine if any packets can be filled
//
if ( pContext->bEofSent
|| ( NULL == pContext->pFreeList )) {
//
// All of the packets are filled
//
break;
}
//
// Set the TFTP opcode and block number
//
pPacket = PacketGet ( pContext );
pBuffer = &pPacket->TxBuffer[ 0 ];
*pBuffer++ = 0;
*pBuffer++ = TFTP_OP_DATA;
*pBuffer++ = (UINT8)( pContext->BlockNumber >> 8 );
*pBuffer++ = (UINT8)pContext->BlockNumber;
//
// Determine how much data needs to be sent
//
LengthInBytes = pContext->BlockSize;
if (( pContext->BytesToSend < TFTP_MAX_BLOCK_SIZE )
&& ( LengthInBytes > pContext->BytesToSend )) {
LengthInBytes = pContext->BytesToSend;
pContext->bEofSent = TRUE;
}
DEBUG (( DEBUG_TX_PACKET,
"0x%08x: Packet, Block %d filled with %d bytes\r\n",
pPacket,
pContext->BlockNumber,
(UINT32)LengthInBytes ));
//
// Copy the file data into the packet
//
pPacket->TxBytes = (ssize_t)( 2 + 2 + LengthInBytes );
if ( 0 < LengthInBytes ) {
CopyMem ( pBuffer,
pContext->pBuffer,
(UINTN)LengthInBytes );
DEBUG (( DEBUG_FILE_BUFFER,
"0x%08x: Buffer consumed %d bytes of file data\r\n",
pContext->pBuffer,
LengthInBytes ));
//
// Account for the file data consumed
//
pContext->ValidBytes -= LengthInBytes;
pContext->BytesToSend -= LengthInBytes;
pContext->pBuffer += LengthInBytes;
if ( pContext->pEnd <= pContext->pBuffer ) {
pContext->pBuffer = &pContext->FileData[ 0 ];
}
}
//
// Queue the packet for transmission
//
PacketQueue ( pContext, pPacket );
}
//
// Return the read status
//
DBG_EXIT ( );
return bReadError;
}
/**
Free the packet
@param [in] pContext Address of a ::TSDT_CONNECTION_CONTEXT structure
@param [in] pPacket Address of a ::TFTP_PACKET structure
**/
VOID
PacketFree(
IN TSDT_CONNECTION_CONTEXT * pContext,
IN TFTP_PACKET * pPacket
)
{
DBG_ENTER ( );
//
// Don't free the error packet
//
if ( pPacket != &pContext->ErrorPacket ) {
//
// Place the packet on the free list
//
pPacket->pNext = pContext->pFreeList;
pContext->pFreeList = pPacket;
DEBUG (( DEBUG_TX_PACKET,
"0x%08x: Packet queued to free list\r\n",
pPacket ));
}
DBG_EXIT ( );
}
/**
Get a packet from the free list for transmission
@param [in] pContext Address of a ::TSDT_CONNECTION_CONTEXT structure
@retval Address of a ::TFTP_PACKET structure
**/
TFTP_PACKET *
PacketGet (
IN TSDT_CONNECTION_CONTEXT * pContext
)
{
TFTP_PACKET * pPacket;
DBG_ENTER ( );
//
// Get the next packet from the free list
//
pPacket = pContext->pFreeList;
if ( NULL != pPacket ) {
pContext->pFreeList = pPacket->pNext;
pPacket->RetryCount = 0;
DEBUG (( DEBUG_TX_PACKET,
"0x%08x: Packet removed from free list\r\n",
pPacket ));
}
//
// Return the packet
//
DBG_EXIT_HEX ( pPacket );
return pPacket;
}
/**
Queue the packet for transmission
@param [in] pContext Address of a ::TSDT_CONNECTION_CONTEXT structure
@param [in] pPacket Address of a ::TFTP_PACKET structure
@retval TRUE if a transmission error has occurred
**/
BOOLEAN
PacketQueue (
IN TSDT_CONNECTION_CONTEXT * pContext,
IN TFTP_PACKET * pPacket
)
{
BOOLEAN bTransmitError;
TFTP_PACKET * pTail;
EFI_STATUS Status;
DBG_ENTER ( );
//
// Account for this data block
//
pPacket->BlockNumber = pContext->BlockNumber;
pContext->BlockNumber += 1;
//
// Queue the packet for transmission
//
pTail = pContext->pTxTail;
if ( NULL == pTail ) {
pContext->pTxHead = pPacket;
}
else {
pTail->pNext = pPacket;
}
pContext->pTxTail = pPacket;
pPacket->pNext = NULL;
DEBUG (( DEBUG_TX_PACKET,
"0x%08x: Packet queued to TX list\r\n",
pPacket ));
//
// Start the transmission if necessary
//
bTransmitError = FALSE;
if ( pContext->PacketsInWindow < pContext->WindowSize ) {
Status = PacketTx ( pContext, pPacket );
bTransmitError = (BOOLEAN)( EFI_ERROR ( Status ));
}
//
// Return the transmit status
//
DBG_EXIT_TF ( bTransmitError );
return bTransmitError;
}
/**
Remove a packet from the transmit queue
@param [in] pContext Address of a ::TSDT_CONNECTION_CONTEXT structure
**/
TFTP_PACKET *
PacketRemove(
IN TSDT_CONNECTION_CONTEXT * pContext
)
{
TFTP_PACKET * pNext;
TFTP_PACKET * pPacket;
DBG_ENTER ( );
//
// Remove a packet from the transmit queue
//
//
pPacket = pContext->pTxHead;
if ( NULL != pPacket ) {
pNext = pPacket->pNext;
pContext->pTxHead = pNext;
if ( NULL == pNext ) {
pContext->pTxTail = NULL;
}
DEBUG (( DEBUG_TX_PACKET,
"0x%08x: Packet removed from TX list\r\n",
pPacket ));
//
// Remove this packet from the window
//
pContext->PacketsInWindow -= 1;
}
//
// Return the packet
//
DBG_EXIT_HEX ( pPacket );
return pPacket;
}
/**
Transmit the packet
@param [in] pContext Address of a ::TSDT_CONNECTION_CONTEXT structure
@param [in] pPacket Address of a ::TFTP_PACKET structure
@retval EFI_SUCCESS Message processed successfully
**/
EFI_STATUS
PacketTx (
IN TSDT_CONNECTION_CONTEXT * pContext,
IN TFTP_PACKET * pPacket
)
{
ssize_t LengthInBytes;
EFI_STATUS Status;
DBG_ENTER ( );
//
// Assume success
//
Status = EFI_SUCCESS;
//
// Determine if this packet should be transmitted
//
if ( PcdGet32 ( Tftp_MaxRetry ) >= pPacket->RetryCount ) {
pPacket->RetryCount += 1;
//
// Display the operation
//
DEBUG (( DEBUG_TX_PACKET,
"0x%08x: Packet transmiting\r\n",
pPacket ));
DEBUG (( DEBUG_TX,
"0x%08x: pContext sending 0x%08x bytes\r\n",
pContext,
pPacket->TxBytes ));
//
// Keep track of when the packet was transmitted
//
if ( PcdGetBool ( Tftp_HighSpeed )) {
pPacket->TxTime = GetPerformanceCounter ( );
}
//
// Send the TFTP packet
//
pContext->PacketsInWindow += 1;
LengthInBytes = sendto ( pContext->SocketFd,
&pPacket->TxBuffer[ 0 ],
pPacket->TxBytes,
0,
(struct sockaddr *)&pContext->RemoteAddress,
pContext->RemoteAddress.sin6_len );
if ( -1 == LengthInBytes ) {
DEBUG (( DEBUG_ERROR | DEBUG_TX,
"ERROR - Transmit failure, errno: 0x%08x\r\n",
errno ));
pContext->PacketsInWindow -= 1;
Status = EFI_DEVICE_ERROR;
}
}
else {
//
// Too many retries
//
Status = EFI_NO_RESPONSE;
DEBUG (( DEBUG_WARN | DEBUG_WINDOW,
"WARNING - No response from TFTP client\r\n" ));
}
//
// Return the operation status
//
DBG_EXIT_STATUS ( Status );
return Status;
}
/**
Process the work for the sockets.
@param [in] pTftpServer Address of the ::TSDT_TFTP_SERVER structure
@param [in] pIndex Address of an index into the pollfd array
**/
VOID
PortWork (
IN TSDT_TFTP_SERVER * pTftpServer,
IN int * pIndex
)
{
int Index;
TSDT_CONNECTION_CONTEXT * pContext;
struct pollfd * pTftpPort;
socklen_t RemoteAddressLength;
int revents;
DBG_ENTER ( );
//
// Locate the port
//
Index = *pIndex;
if ( -1 != Index ) {
pTftpPort = &pTftpServer->TftpPort[ *pIndex ];
//
// Handle input events
//
revents = pTftpPort->revents;
pTftpPort->revents = 0;
if ( 0 != ( revents & POLLRDNORM )) {
//
// Receive the message from the remote system
//
RemoteAddressLength = sizeof ( pTftpServer->RemoteAddress );
pTftpServer->RxBytes = recvfrom ( pTftpPort->fd,
&pTftpServer->RxBuffer[ 0 ],
sizeof ( pTftpServer->RxBuffer ),
0,
(struct sockaddr *) &pTftpServer->RemoteAddress,
&RemoteAddressLength );
if ( -1 != pTftpServer->RxBytes ) {
if ( PcdGetBool ( Tftp_HighSpeed )) {
pTftpServer->RxTime = GetPerformanceCounter ( );
}
if ( AF_INET == pTftpServer->RemoteAddress.v4.sin_family ) {
DEBUG (( DEBUG_TFTP_PORT,
"Received %d bytes from %d.%d.%d.%d:%d\r\n",
pTftpServer->RxBytes,
pTftpServer->RemoteAddress.v4.sin_addr.s_addr & 0xff,
( pTftpServer->RemoteAddress.v4.sin_addr.s_addr >> 8 ) & 0xff,
( pTftpServer->RemoteAddress.v4.sin_addr.s_addr >> 16 ) & 0xff,
( pTftpServer->RemoteAddress.v4.sin_addr.s_addr >> 24 ) & 0xff,
htons ( pTftpServer->RemoteAddress.v4.sin_port )));
}
else {
DEBUG (( DEBUG_TFTP_PORT,
"Received %d bytes from [%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x]:%d\r\n",
pTftpServer->RxBytes,
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 0 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 1 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 2 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 3 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 4 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 5 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 6 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 7 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 8 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 9 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 10 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 11 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 12 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 13 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 14 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 15 ],
htons ( pTftpServer->RemoteAddress.v6.sin6_port )));
}
//
// Lookup connection context using the remote system address and port
// to determine if an existing connection to this remote
// system exists
//
pContext = ContextFind ( pTftpServer );
//
// Process the received message
//
TftpProcessRequest ( pTftpServer, pContext, pTftpPort->fd );
}
else {
//
// Receive error on the TFTP server port
// Close the server socket
//
DEBUG (( DEBUG_ERROR,
"ERROR - Failed receive on TFTP server port, errno: 0x%08x\r\n",
errno ));
revents |= POLLHUP;
}
}
//
// Handle the close event
//
if ( 0 != ( revents & POLLHUP )) {
//
// Close the port
//
close ( pTftpPort->fd );
pTftpPort->fd = -1;
*pIndex = -1;
pTftpServer->Entries -= 1;
ASSERT ( 0 <= pTftpServer->Entries );
}
}
DBG_EXIT ( );
}
/**
Build and send an error packet
@param [in] pContext Address of a ::TSDT_CONNECTION_CONTEXT structure
@param [in] Error Error number for the packet
@param [in] pError Zero terminated error string address
@retval EFI_SUCCESS Message processed successfully
**/
EFI_STATUS
SendError (
IN TSDT_CONNECTION_CONTEXT * pContext,
IN UINT16 Error,
IN UINT8 * pError
)
{
UINT8 Character;
UINT8 * pBuffer;
TFTP_PACKET * pPacket;
EFI_STATUS Status;
DBG_ENTER ( );
//
// Build the error packet
//
pPacket = &pContext->ErrorPacket;
pBuffer = &pPacket->TxBuffer[ 0 ];
pBuffer[ 0 ] = 0;
pBuffer[ 1 ] = TFTP_OP_ERROR;
pBuffer[ 2 ] = (UINT8)( Error >> 8 );
pBuffer[ 3 ] = (UINT8)Error;
//
// Copy the zero terminated string into the buffer
//
pBuffer += 4;
do {
Character = *pError++;
*pBuffer++ = Character;
} while ( 0 != Character );
//
// Send the error message
//
pPacket->TxBytes = pBuffer - &pPacket->TxBuffer[ 0 ];
Status = PacketTx ( pContext, pPacket );
//
// Return the operation status
//
DBG_EXIT_STATUS ( Status );
return Status;
}
/**
Scan the list of sockets and process any pending work
@param [in] pTftpServer Address of the ::TSDT_TFTP_SERVER structure
**/
VOID
SocketPoll (
IN TSDT_TFTP_SERVER * pTftpServer
)
{
int FDCount;
DEBUG (( DEBUG_SOCKET_POLL, "Entering SocketPoll\r\n" ));
//
// Determine if any ports are active
//
if ( 0 != pTftpServer->Entries ) {
FDCount = poll ( &pTftpServer->TftpPort[ 0 ],
pTftpServer->Entries,
CLIENT_POLL_DELAY );
if ( 0 < FDCount ) {
//
// Process this port
//
PortWork ( pTftpServer, &pTftpServer->Udpv4Index );
PortWork ( pTftpServer, &pTftpServer->Udpv6Index );
}
}
DEBUG (( DEBUG_SOCKET_POLL, "Exiting SocketPoll\r\n" ));
}
/**
Process the ACK
@param [in] pTftpServer Address of the ::TSDT_TFTP_SERVER structure
@param [in] pContext Connection context structure address
@retval TRUE if the context should be closed
**/
BOOLEAN
TftpAck (
IN TSDT_TFTP_SERVER * pTftpServer,
IN TSDT_CONNECTION_CONTEXT * pContext
)
{
INTN AckNumber;
BOOLEAN bCloseContext;
UINT16 BlockNumber;
UINT8 * pBuffer;
TFTP_PACKET * pPacket;
EFI_STATUS Status;
DBG_ENTER ( );
//
// Use break instead of goto
//
bCloseContext = FALSE;
for ( ; ; ) {
//
// Validate the parameters
//
if ( NULL == pContext ) {
if ( AF_INET == pTftpServer->RemoteAddress.v4.sin_family ) {
DEBUG (( DEBUG_ERROR,
"ERROR - File not open for %d.%d.%d.%d:%d\r\n",
(UINT8)pTftpServer->RemoteAddress.v4.sin_addr.s_addr,
(UINT8)( pTftpServer->RemoteAddress.v4.sin_addr.s_addr >> 8 ),
(UINT8)( pTftpServer->RemoteAddress.v4.sin_addr.s_addr >> 16 ),
(UINT8)( pTftpServer->RemoteAddress.v4.sin_addr.s_addr >> 24 ),
htons ( pTftpServer->RemoteAddress.v4.sin_port )));
}
else {
DEBUG (( DEBUG_ERROR,
"ERROR - File not open for [%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x]:%d\r\n",
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 0 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 1 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 2 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 3 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 4 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 5 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 6 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 7 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 8 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 9 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 10 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 11 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 12 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 13 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 14 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 15 ],
htons ( pTftpServer->RemoteAddress.v6.sin6_port )));
}
break;
}
//
// Verify that the ACK was expected
//
pPacket = pContext->pTxHead;
if ( NULL == pPacket ) {
//
// ACK not expected!
//
DEBUG (( DEBUG_ERROR,
"ERROR - Expecting data not ACKs for pContext 0x%08x\r\n",
pContext ));
break;
}
//
// Get the ACKed block number
//
pBuffer = &pTftpServer->RxBuffer[ 0 ];
BlockNumber = HTONS ( *(UINT16 *)&pBuffer[ 2 ]);
//
// Determine if this is the correct ACK
//
DEBUG (( DEBUG_TFTP_ACK,
"ACK for block 0x%04x received\r\n",
BlockNumber ));
AckNumber = BlockNumber - pPacket->BlockNumber;
if (( 0 > AckNumber ) || ( AckNumber >= (INTN)pContext->PacketsInWindow )){
DEBUG (( DEBUG_WARN | DEBUG_TFTP_ACK,
"WARNING - Expecting ACK 0x%0x4 not received ACK 0x%08x\r\n",
pPacket->BlockNumber,
BlockNumber ));
break;
}
//
// Release the ACKed packets
//
do {
//
// Remove the packet from the transmit list and window
//
pPacket = PacketRemove ( pContext );
//
// Get the block number of this packet
//
AckNumber = pPacket->BlockNumber;
//
// Increase the size of the transmit window
//
if ( PcdGetBool ( Tftp_HighSpeed )
&& ( AckNumber == BlockNumber )) {
WindowAck ( pTftpServer, pContext, pPacket );
}
//
// Free this packet
//
PacketFree ( pContext, pPacket );
} while (( NULL != pContext->pTxHead ) && ( AckNumber != BlockNumber ));
//
// Fill the window with packets
//
pPacket = pContext->pTxHead;
while (( NULL != pPacket )
&& ( pContext->PacketsInWindow < pContext->WindowSize )
&& ( !bCloseContext )) {
Status = PacketTx ( pContext, pPacket );
bCloseContext = (BOOLEAN)( EFI_ERROR ( Status ));
pPacket = pPacket->pNext;
}
//
// Get more packets ready for transmission
//
PacketFill ( pContext );
//
// Close the context when the last packet is ACKed
//
if ( 0 == pContext->PacketsInWindow ) {
bCloseContext = TRUE;
//
// Display the bandwidth
//
if ( PcdGetBool ( Tftp_Bandwidth )) {
UINT64 Bandwidth;
UINT64 DeltaTime;
UINT64 NanoSeconds;
UINT32 Value;
//
// Compute the download time
//
DeltaTime = GetPerformanceCounter ( );
if ( pTftpServer->Time2 > pTftpServer->Time1 ) {
DeltaTime = DeltaTime - pContext->TimeStart;
}
else {
DeltaTime = pContext->TimeStart - DeltaTime;
}
NanoSeconds = GetTimeInNanoSecond ( DeltaTime );
Bandwidth = pContext->LengthInBytes;
DEBUG (( DEBUG_WINDOW,
"File Length %Ld, Transfer Time: %d.%03d Sec\r\n",
Bandwidth,
DivU64x32 ( NanoSeconds, 1000 * 1000 * 1000 ),
((UINT32)DivU64x32 ( NanoSeconds, 1000 * 1000 )) % 1000 ));
//
// Display the round trip time
//
Bandwidth = MultU64x32 ( Bandwidth, 8 * 1000 * 1000 );
Bandwidth /= NanoSeconds;
if ( 1000 > Bandwidth ) {
Value = (UINT32)Bandwidth;
Print ( L"Bandwidth: %d Kbits/Sec\r\n",
Value );
}
else if (( 1000 * 1000 ) > Bandwidth ) {
Value = (UINT32)Bandwidth;
Print ( L"Bandwidth: %d.%03d Mbits/Sec\r\n",
Value / 1000,
Value % 1000 );
}
else {
Value = (UINT32)DivU64x32 ( Bandwidth, 1000 );
Print ( L"Bandwidth: %d.%03d Gbits/Sec\r\n",
Value / 1000,
Value % 1000 );
}
}
}
break;
}
//
// Return the operation status
//
DBG_EXIT ( );
return bCloseContext;
}
/**
Get the next TFTP option
@param [in] pOption Address of a zero terminated option string
@param [in] pEnd End of buffer address
@param [in] ppNextOption Address to receive the address of the next
zero terminated option string
@retval EFI_SUCCESS Message processed successfully
**/
EFI_STATUS
TftpOptionGet (
IN UINT8 * pOption,
IN UINT8 * pEnd,
IN UINT8 ** ppNextOption
)
{
UINT8 * pNextOption;
EFI_STATUS Status;
//
// Locate the end of the option
//
pNextOption = pOption;
while (( pEnd > pNextOption ) && ( 0 != *pNextOption )) {
pNextOption += 1;
}
if ( pEnd <= pNextOption ) {
//
// Error - end of buffer reached
//
DEBUG (( DEBUG_ERROR | DEBUG_TFTP_REQUEST,
"ERROR - Option without zero termination received!\r\n" ));
Status = EFI_INVALID_PARAMETER;
}
else {
//
// Zero terminated option found
//
pNextOption += 1;
//
// Display the zero terminated ASCII option string
//
DEBUG (( DEBUG_TFTP_REQUEST,
"Option: %a\r\n",
pOption ));
Status = EFI_SUCCESS;
}
//
// Return the next option address
//
*ppNextOption = pNextOption;
//
// Return the operation status
//
return Status;
}
/**
Place an option value into the option acknowledgement
@param [in] pOack Option acknowledgement address
@param [in] Value Value to translate into ASCII decimal
@return Option acknowledgement address
**/
UINT8 *
TftpOptionSet (
IN UINT8 * pOack,
IN UINT64 Value
)
{
UINT64 NextValue;
//
// Determine the next value
//
NextValue = Value / 10;
//
// Supress leading zeros
//
if ( 0 != NextValue ) {
pOack = TftpOptionSet ( pOack, NextValue );
}
//
// Output this digit
//
*pOack++ = (UINT8)( Value - ( NextValue * 10 ) + '0' );
//
// Return the next option acknowledgement location
//
return pOack;
}
/**
Process the TFTP request
@param [in] pContext Address of a ::TSDT_CONNECTION_CONTEXT structure
@param [in] pOption Address of the first zero terminated option string
@param [in] pEnd End of buffer address
**/
VOID
TftpOptions (
IN TSDT_CONNECTION_CONTEXT * pContext,
IN UINT8 * pOption,
IN UINT8 * pEnd
)
{
UINT8 * pNextOption;
UINT8 * pOack;
TFTP_PACKET * pPacket;
UINT8 * pTemp;
UINT8 * pValue;
EFI_STATUS Status;
INT32 Value;
//
// Get a packet
//
pPacket = PacketGet ( pContext );
//
// Start the OACK packet
// Let the OACK handle the parsing errors
// See http://tools.ietf.org/html/rfc2347
//
pOack = &pPacket->TxBuffer[ 0 ];
*pOack++ = 0;
*pOack++ = TFTP_OP_OACK;
pPacket->TxBytes = 2;
pPacket->BlockNumber = 0;
//
// Walk the list of options
//
do {
//
// Get the next option, skip junk at end of message
//
Status = TftpOptionGet ( pOption, pEnd, &pNextOption );
if ( !EFI_ERROR ( Status )) {
//
// Process the option
//
//
// blksize - See http://tools.ietf.org/html/rfc2348
//
pValue = pNextOption;
if ( 0 == strcasecmp ((char *)pOption, "blksize" )) {
//
// Get the value
//
Status = TftpOptionGet ( pValue, pEnd, &pNextOption );
if ( !EFI_ERROR ( Status )) {
//
// Validate the block size, skip non-numeric block sizes
//
Status = TftpOptionValue ( pValue, &Value );
if ( !EFI_ERROR ( Status )) {
//
// Propose a smaller block size if necessary
//
if ( Value > TFTP_MAX_BLOCK_SIZE ) {
Value = TFTP_MAX_BLOCK_SIZE;
}
//
// Set the new block size
//
pContext->BlockSize = Value;
DEBUG (( DEBUG_TFTP_REQUEST,
"Using block size of %d bytes\r\n",
pContext->BlockSize ));
//
// Update the OACK
//
pTemp = pOack;
*pOack++ = 'b';
*pOack++ = 'l';
*pOack++ = 'k';
*pOack++ = 's';
*pOack++ = 'i';
*pOack++ = 'z';
*pOack++ = 'e';
*pOack++ = 0;
pOack = TftpOptionSet ( pOack, pContext->BlockSize );
*pOack++ = 0;
pPacket->TxBytes += pOack - pTemp;
}
}
}
//
// timeout - See http://tools.ietf.org/html/rfc2349
//
else if ( 0 == strcasecmp ((char *)pOption, "timeout" )) {
//
// Get the value
//
Status = TftpOptionGet ( pValue, pEnd, &pNextOption );
if ( !EFI_ERROR ( Status )) {
Status = TftpOptionValue ( pValue, &Value );
if ( !EFI_ERROR ( Status )) {
//
// Set the timeout value
//
pContext->MaxTimeout = Value;
DEBUG (( DEBUG_TFTP_REQUEST,
"Using timeout of %d seconds\r\n",
pContext->MaxTimeout ));
//
// Update the OACK
//
pTemp = pOack;
*pOack++ = 't';
*pOack++ = 'i';
*pOack++ = 'm';
*pOack++ = 'e';
*pOack++ = 'o';
*pOack++ = 'u';
*pOack++ = 't';
*pOack++ = 0;
pOack = TftpOptionSet ( pOack, pContext->MaxTimeout );
*pOack++ = 0;
pPacket->TxBytes += pOack - pTemp;
}
}
}
//
// tsize - See http://tools.ietf.org/html/rfc2349
//
else if ( 0 == strcasecmp ((char *)pOption, "tsize" )) {
//
// Get the value
//
Status = TftpOptionGet ( pValue, pEnd, &pNextOption );
if ( !EFI_ERROR ( Status )) {
Status = TftpOptionValue ( pValue, &Value );
if ( !EFI_ERROR ( Status )) {
//
// Return the file size
//
DEBUG (( DEBUG_TFTP_REQUEST,
"Returning file size of %Ld bytes\r\n",
pContext->LengthInBytes ));
//
// Update the OACK
//
pTemp = pOack;
*pOack++ = 't';
*pOack++ = 's';
*pOack++ = 'i';
*pOack++ = 'z';
*pOack++ = 'e';
*pOack++ = 0;
pOack = TftpOptionSet ( pOack, pContext->LengthInBytes );
*pOack++ = 0;
pPacket->TxBytes += pOack - pTemp;
}
}
}
else {
//
// Unknown option - Ignore it
//
DEBUG (( DEBUG_WARN | DEBUG_TFTP_REQUEST,
"WARNING - Skipping unknown option: %a\r\n",
pOption ));
}
}
//
// Set the next option
//
pOption = pNextOption;
} while ( pEnd > pOption );
//
// Transmit the OACK if necessary
//
if ( 2 < pPacket->TxBytes ) {
PacketQueue ( pContext, pPacket );
}
else {
PacketFree ( pContext, pPacket );
}
}
/**
Process the TFTP request
@param [in] pOption Address of the first zero terminated option string
@param [in] pValue Address to receive the value
@retval EFI_SUCCESS Option translated into a value
**/
EFI_STATUS
TftpOptionValue (
IN UINT8 * pOption,
IN INT32 * pValue
)
{
UINT8 Digit;
EFI_STATUS Status;
INT32 Value;
//
// Assume success
//
Status = EFI_SUCCESS;
//
// Walk the characters in the option
//
Value = 0;
while ( 0 != *pOption ) {
//
// Convert the next digit to binary
//
Digit = *pOption++;
if (( '0' <= Digit ) && ( '9' >= Digit )) {
Value *= 10;
Value += Digit - '0';
}
else {
DEBUG (( DEBUG_ERROR | DEBUG_TFTP_REQUEST,
"ERROR - Invalid character '0x%02x' in the value\r\n",
Digit ));
Status = EFI_INVALID_PARAMETER;
break;
}
}
//
// Return the value
//
*pValue = Value;
//
// Return the conversion status
//
return Status;
}
/**
Process the TFTP request
@param [in] pTftpServer Address of the ::TSDT_TFTP_SERVER structure
@param [in] pContext Address of a ::TSDT_CONNECTION_CONTEXT structure
@param [in] SocketFd Socket file descriptor
**/
VOID
TftpProcessRequest (
IN TSDT_TFTP_SERVER * pTftpServer,
IN TSDT_CONNECTION_CONTEXT * pContext,
IN int SocketFd
)
{
BOOLEAN bCloseContext;
UINT16 Opcode;
DBG_ENTER ( );
//
// Get the opcode
//
Opcode = HTONS ( *(UINT16 *)&pTftpServer->RxBuffer[ 0 ]);
DEBUG (( DEBUG_TFTP_REQUEST,
"TFTP Opcode: 0x%08x\r\n",
Opcode ));
//
// Validate the parameters
//
bCloseContext = FALSE;
switch ( Opcode ) {
default:
DEBUG (( DEBUG_TFTP_REQUEST,
"ERROR - Unknown TFTP opcode: %d\r\n",
Opcode ));
break;
case TFTP_OP_ACK:
bCloseContext = TftpAck ( pTftpServer, pContext );
break;
case TFTP_OP_READ_REQUEST:
bCloseContext = TftpRead ( pTftpServer, pContext, SocketFd );
break;
case TFTP_OP_DATA:
if ( NULL == pContext ) {
if ( AF_INET == pTftpServer->RemoteAddress.v4.sin_family ) {
DEBUG (( DEBUG_ERROR,
"ERROR - File not open for %d.%d.%d.%d:%d\r\n",
(UINT8)pTftpServer->RemoteAddress.v4.sin_addr.s_addr,
(UINT8)( pTftpServer->RemoteAddress.v4.sin_addr.s_addr >> 8 ),
(UINT8)( pTftpServer->RemoteAddress.v4.sin_addr.s_addr >> 16 ),
(UINT8)( pTftpServer->RemoteAddress.v4.sin_addr.s_addr >> 24 ),
htons ( pTftpServer->RemoteAddress.v4.sin_port )));
}
else {
DEBUG (( DEBUG_ERROR,
"ERROR - File not open for [%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x]:%d\r\n",
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 0 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 1 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 2 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 3 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 4 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 5 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 6 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 7 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 8 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 9 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 10 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 11 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 12 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 13 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 14 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 15 ],
htons ( pTftpServer->RemoteAddress.v6.sin6_port )));
}
break;
}
if ( 0 != pContext->PacketsInWindow ) {
DEBUG (( DEBUG_ERROR,
"ERROR - Expecting ACKs not data for pContext 0x%08x\r\n",
pContext ));
break;
}
if ( pTftpServer->RxBytes > (ssize_t)( pContext->BlockSize + 2 + 2 )) {
DEBUG (( DEBUG_ERROR,
"ERROR - Receive data length of %d > %d bytes (maximum block size) for pContext 0x%08x\r\n",
pTftpServer->RxBytes - 2 - 2,
pContext->BlockSize,
pContext ));
break;
}
break;
case TFTP_OP_ERROR:
if ( NULL == pContext ) {
if ( AF_INET == pTftpServer->RemoteAddress.v4.sin_family ) {
DEBUG (( DEBUG_ERROR,
"ERROR - File not open for %d.%d.%d.%d:%d\r\n",
(UINT8)pTftpServer->RemoteAddress.v4.sin_addr.s_addr,
(UINT8)( pTftpServer->RemoteAddress.v4.sin_addr.s_addr >> 8 ),
(UINT8)( pTftpServer->RemoteAddress.v4.sin_addr.s_addr >> 16 ),
(UINT8)( pTftpServer->RemoteAddress.v4.sin_addr.s_addr >> 24 ),
htons ( pTftpServer->RemoteAddress.v4.sin_port )));
}
else {
DEBUG (( DEBUG_ERROR,
"ERROR - File not open for [%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x]:%d\r\n",
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 0 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 1 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 2 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 3 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 4 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 5 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 6 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 7 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 8 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 9 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 10 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 11 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 12 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 13 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 14 ],
pTftpServer->RemoteAddress.v6.sin6_addr.__u6_addr.__u6_addr8[ 15 ],
htons ( pTftpServer->RemoteAddress.v6.sin6_port )));
}
}
break;
}
//
// Determine if the context should be closed
//
if ( bCloseContext ) {
ContextRemove ( pTftpServer, pContext );
}
DBG_EXIT ( );
}
/**
Process the read request
@param [in] pTftpServer Address of the ::TSDT_TFTP_SERVER structure
@param [in] pContext Address of a ::TSDT_CONNECTION_CONTEXT structure
@param [in] SocketFd Socket file descriptor
@retval TRUE if the context should be closed
**/
BOOLEAN
TftpRead (
IN TSDT_TFTP_SERVER * pTftpServer,
IN TSDT_CONNECTION_CONTEXT * pContext,
IN int SocketFd
)
{
BOOLEAN bCloseContext;
struct stat FileStatus;
UINT8 * pBuffer;
UINT8 * pEnd;
UINT8 * pFileName;
UINT8 * pMode;
UINT8 * pOption;
CHAR8 * pReadMode;
UINT64 TimeStart;
DBG_ENTER ( );
//
// Log the receive time
//
TimeStart = 0;
if ( PcdGetBool ( Tftp_Bandwidth )) {
TimeStart = GetPerformanceCounter ( );
}
//
// Close the context if necessary
//
bCloseContext = FALSE;
if ( NULL != pContext ) {
ContextRemove ( pTftpServer, pContext );
}
//
// Use break instead of goto
//
for ( ; ; ) {
//
// Create the connection context
//
pContext = ContextAdd ( pTftpServer, SocketFd );
if ( NULL == pContext ) {
break;
}
//
// Set the start time
//
if ( PcdGetBool ( Tftp_Bandwidth )) {
pContext->TimeStart = TimeStart;
}
//
// Locate the mode
//
pBuffer = &pTftpServer->RxBuffer[ 0 ];
pEnd = &pBuffer[ pTftpServer->RxBytes ];
pFileName = &pBuffer[ 2 ];
pMode = pFileName;
while (( pEnd > pMode ) && ( 0 != *pMode )) {
pMode += 1;
}
if ( pEnd <= pMode ) {
//
// Mode not found
//
DEBUG (( DEBUG_ERROR | DEBUG_RX,
"ERROR - File mode not found\r\n" ));
//
// Tell the client of the error
//
SendError ( pContext,
TFTP_ERROR_SEE_MSG,
(UINT8 *)"File open mode not found" );
break;
}
pMode += 1;
DEBUG (( DEBUG_TFTP_REQUEST,
"TFTP - FileName: %a\r\n",
pFileName ));
//
// Locate the options
//
pOption = pMode;
while (( pEnd > pOption ) && ( 0 != *pOption )) {
pOption += 1;
}
if ( pEnd <= pOption ) {
//
// End of mode not found
//
DEBUG (( DEBUG_ERROR | DEBUG_RX,
"ERROR - File mode not valid\r\n" ));
//
// Tell the client of the error
//
SendError ( pContext,
TFTP_ERROR_SEE_MSG,
(UINT8 *)"File open mode not valid" );
break;
}
pOption += 1;
DEBUG (( DEBUG_TFTP_REQUEST,
"TFTP - Mode: %a\r\n",
pMode ));
//
// Verify the mode is supported
//
pReadMode = "r";
if ( 0 == strcasecmp ((char *)pMode, "octet" )) {
//
// Read the file as binary input
//
pReadMode = "rb";
}
//
// Determine the file length
//
pContext->File = fopen ((const char *)pFileName, pReadMode );
if (( NULL == pContext->File )
|| ( -1 == stat ((const char *)pFileName, &FileStatus ))) {
//
// File not found
//
DEBUG (( DEBUG_ERROR | DEBUG_TFTP_REQUEST,
( NULL == pContext->File )
? "ERROR - File not found!\r\n"
: "ERROR - Unable to determine file %a size!\r\n",
pFileName ));
//
// Tell the client of the error
//
SendError ( pContext,
TFTP_ERROR_NOT_FOUND,
(UINT8 *)"File not found" );
break;
}
pContext->LengthInBytes = FileStatus.st_size;
pContext->BytesRemaining = pContext->LengthInBytes;
pContext->BytesToSend = pContext->LengthInBytes;
//
// Display the file size
//
DEBUG_CODE_BEGIN ( );
UINT32 Value;
if ( 1024 > pContext->LengthInBytes ) {
Value = (UINT32)pContext->LengthInBytes;
DEBUG (( DEBUG_FILE_BUFFER,
"%a size: %d Bytes\r\n",
pFileName,
Value ));
}
else if (( 1024 * 1024 ) > pContext->LengthInBytes ) {
Value = (UINT32)pContext->LengthInBytes;
DEBUG (( DEBUG_FILE_BUFFER,
"%a size: %d.%03d KiBytes (%Ld Bytes)\r\n",
pFileName,
Value / 1024,
(( Value % 1024 ) * 1000 ) / 1024,
pContext->LengthInBytes ));
}
else if (( 1024 * 1024 * 1024 ) > pContext->LengthInBytes ) {
Value = (UINT32)DivU64x32 ( pContext->LengthInBytes, 1024 );
DEBUG (( DEBUG_FILE_BUFFER,
"%a size: %d.%03d MiBytes (%Ld Bytes)\r\n",
pFileName,
Value / 1024,
(( Value % 1024 ) * 1000 ) / 1024,
pContext->LengthInBytes ));
}
else {
Value = (UINT32)DivU64x32 ( pContext->LengthInBytes, 1024 * 1024 );
DEBUG (( DEBUG_FILE_BUFFER,
"%a size: %d.%03d GiBytes (%Ld Bytes)\r\n",
pFileName,
Value / 1024,
(( Value % 1024 ) * 1000 ) / 1024,
pContext->LengthInBytes ));
}
DEBUG_CODE_END ( );
//
// Process the options
//
if ( pEnd > pOption ) {
TftpOptions ( pContext, pOption, pEnd );
}
else {
//
// Skip the open ACK
//
pContext->BlockNumber = 1;
}
//
// Send the first packet (OACK or data block)
//
bCloseContext = PacketFill ( pContext );
break;
}
//
// Return the close status
//
DBG_EXIT ( );
return bCloseContext;
}
/**
Create the port for the TFTP server
This routine polls the network layer to create the TFTP port for the
TFTP server. More than one attempt may be necessary since it may take
some time to get the IP address and initialize the upper layers of
the network stack.
@param [in] pTftpServer Address of the ::TSDT_TFTP_SERVER structure
@param [in] AddressFamily The address family to use for the conection.
@param [in] pIndex Address of the index into the port array
**/
VOID
TftpServerSocket (
IN TSDT_TFTP_SERVER * pTftpServer,
IN sa_family_t AddressFamily,
IN int * pIndex
)
{
int SocketStatus;
struct pollfd * pTftpPort;
UINT16 TftpPort;
union {
struct sockaddr_in v4;
struct sockaddr_in6 v6;
} TftpServerAddress;
DEBUG (( DEBUG_SERVER_TIMER, "Entering TftpServerListen\r\n" ));
//
// Determine if the socket is already initialized
//
if ( -1 == *pIndex ) {
//
// Attempt to create the socket for the TFTP server
//
pTftpPort = &pTftpServer->TftpPort[ pTftpServer->Entries ];
pTftpPort->fd = socket ( AddressFamily,
SOCK_DGRAM,
IPPROTO_UDP );
if ( -1 != pTftpPort->fd ) {
//
// Initialize the poll structure
//
pTftpPort->events = POLLRDNORM | POLLHUP;
pTftpPort->revents = 0;
//
// Set the socket address
//
TftpPort = 69;
ZeroMem ( &TftpServerAddress, sizeof ( TftpServerAddress ));
TftpServerAddress.v4.sin_port = htons ( TftpPort );
if ( AF_INET == AddressFamily ) {
TftpServerAddress.v4.sin_len = sizeof ( TftpServerAddress.v4 );
TftpServerAddress.v4.sin_family = AF_INET;
}
else {
TftpServerAddress.v6.sin6_len = sizeof ( TftpServerAddress.v6 );
TftpServerAddress.v6.sin6_family = AF_INET6;
}
//
// Bind the socket to the TFTP port
//
SocketStatus = bind ( pTftpPort->fd,
(struct sockaddr *) &TftpServerAddress,
TftpServerAddress.v6.sin6_len );
if ( -1 != SocketStatus ) {
DEBUG (( DEBUG_TFTP_PORT,
"0x%08x: Socket bound to port %d\r\n",
pTftpPort->fd,
TftpPort ));
//
// Account for this connection
//
*pIndex = pTftpServer->Entries;
pTftpServer->Entries += 1;
ASSERT ( DIM ( pTftpServer->TftpPort ) >= pTftpServer->Entries );
}
//
// Release the socket if necessary
//
if ( -1 == SocketStatus ) {
close ( pTftpPort->fd );
pTftpPort->fd = -1;
}
}
}
DEBUG (( DEBUG_SERVER_TIMER, "Exiting TftpServerListen\r\n" ));
}
/**
Update the window due to the ACK
@param [in] pTftpServer Address of the ::TSDT_TFTP_SERVER structure
@param [in] pContext Address of a ::TSDT_CONNECTION_CONTEXT structure
@param [in] pPacket Address of a ::TFTP_PACKET structure
**/
VOID
WindowAck (
IN TSDT_TFTP_SERVER * pTftpServer,
IN TSDT_CONNECTION_CONTEXT * pContext,
IN TFTP_PACKET * pPacket
)
{
if ( PcdGetBool ( Tftp_HighSpeed )) {
UINT64 DeltaTime;
UINT64 NanoSeconds;
DBG_ENTER ( );
//
// Compute the round trip time
//
if ( pTftpServer->Time2 > pTftpServer->Time1 ) {
DeltaTime = pTftpServer->RxTime - pPacket->TxTime;
}
else {
DeltaTime = pPacket->TxTime - pTftpServer->RxTime;
}
//
// Adjust the round trip time
//
NanoSeconds = GetTimeInNanoSecond ( DeltaTime );
DeltaTime = RShiftU64 ( pContext->Rtt2x, ACK_SHIFT );
pContext->Rtt2x += NanoSeconds + NanoSeconds - DeltaTime;
if ( pContext->Rtt2x > pContext->MaxTimeout ) {
pContext->Rtt2x = pContext->MaxTimeout;
}
//
// Account for the ACK
//
if ( pContext->WindowSize < MAX_PACKETS ) {
pContext->AckCount -= 1;
if ( 0 == pContext->AckCount ) {
//
// Increase the window
//
pContext->WindowSize += 1;
//
// Set the ACK count
//
if ( pContext->WindowSize < pContext->Threshold ) {
pContext->AckCount = pContext->WindowSize * PcdGet32 ( Tftp_AckMultiplier );
}
else {
pContext->AckCount = PcdGet32 ( Tftp_AckLogBase ) << pContext->WindowSize;
}
//
// Display the round trip time
//
DEBUG_CODE_BEGIN ( );
UINT32 Value;
DeltaTime = RShiftU64 ( pContext->Rtt2x, 1 );
if ( 1000 > DeltaTime ) {
DEBUG (( DEBUG_WINDOW,
"WindowSize: %d, Threshold: %d, AckCount: %4d, RTT: %Ld nSec\r\n",
pContext->WindowSize,
pContext->Threshold,
pContext->AckCount,
DeltaTime ));
}
else if (( 1000 * 1000 ) > DeltaTime ) {
Value = (UINT32)DeltaTime;
DEBUG (( DEBUG_WINDOW,
"WindowSize: %d, Threshold: %d, AckCount: %4d, RTT: %d.%03d uSec\r\n",
pContext->WindowSize,
pContext->Threshold,
pContext->AckCount,
Value / 1000,
Value % 1000 ));
}
else if (( 1000 * 1000 * 1000 ) > DeltaTime ) {
Value = (UINT32)DivU64x32 ( DeltaTime, 1000 );
DEBUG (( DEBUG_WINDOW,
"WindowSize: %d, Threshold: %d, AckCount: %4d, RTT: %d.%03d mSec\r\n",
pContext->WindowSize,
pContext->Threshold,
pContext->AckCount,
Value / 1000,
Value % 1000 ));
}
else {
Value = (UINT32)DivU64x32 ( DeltaTime, 1000 * 1000 );
DEBUG (( DEBUG_WINDOW,
"WindowSize: %d, Threshold: %d, AckCount: %4d, RTT: %d.%03d Sec\r\n",
pContext->WindowSize,
pContext->Threshold,
pContext->AckCount,
Value / 1000,
Value % 1000 ));
}
DEBUG_CODE_END ( );
}
}
DBG_EXIT ( );
}
}
/**
A timeout has occurred, close the window
@param [in] pContext Address of a ::TSDT_CONNECTION_CONTEXT structure
**/
VOID
WindowTimeout (
IN TSDT_CONNECTION_CONTEXT * pContext
)
{
if ( PcdGetBool ( Tftp_HighSpeed )) {
TFTP_PACKET * pPacket;
DBG_ENTER ( );
//
// Set the threshold at half the previous window size
//
pContext->Threshold = ( pContext->WindowSize + 1 ) >> 1;
//
// Close the transmit window
//
pContext->WindowSize = 1;
pContext->PacketsInWindow = 0;
//
// Double the round trip time
//
pContext->Rtt2x = LShiftU64 ( pContext->Rtt2x, 1 );
if ( pContext->Rtt2x > pContext->MaxTimeout ) {
pContext->Rtt2x = pContext->MaxTimeout;
}
//
// Set the ACK count
//
if ( pContext->WindowSize < pContext->Threshold ) {
pContext->AckCount = pContext->WindowSize * PcdGet32 ( Tftp_AckMultiplier );
}
else {
pContext->AckCount = PcdGet32 ( Tftp_AckLogBase ) << pContext->WindowSize;
}
//
// Display the round trip time
//
DEBUG_CODE_BEGIN ( );
UINT64 DeltaTime;
UINT32 Value;
DeltaTime = RShiftU64 ( pContext->Rtt2x, 1 );
if ( 1000 > DeltaTime ) {
DEBUG (( DEBUG_WINDOW,
"WindowSize: %d, Threshold: %d, AckCount: %4d, RTT: %Ld nSec\r\n",
pContext->WindowSize,
pContext->Threshold,
pContext->AckCount,
DeltaTime ));
}
else if (( 1000 * 1000 ) > DeltaTime ) {
Value = (UINT32)DeltaTime;
DEBUG (( DEBUG_WINDOW,
"WindowSize: %d, Threshold: %d, AckCount: %4d, RTT: %d.%03d uSec\r\n",
pContext->WindowSize,
pContext->Threshold,
pContext->AckCount,
Value / 1000,
Value % 1000 ));
}
else if (( 1000 * 1000 * 1000 ) > DeltaTime ) {
Value = (UINT32)DivU64x32 ( DeltaTime, 1000 );
DEBUG (( DEBUG_WINDOW,
"WindowSize: %d, Threshold: %d, AckCount: %4d, RTT: %d.%03d mSec\r\n",
pContext->WindowSize,
pContext->Threshold,
pContext->AckCount,
Value / 1000,
Value % 1000 ));
}
else {
Value = (UINT32)DivU64x32 ( DeltaTime, 1000 * 1000 );
DEBUG (( DEBUG_WINDOW,
"WindowSize: %d, Threshold: %d, AckCount: %4d, RTT: %d.%03d Sec\r\n",
pContext->WindowSize,
pContext->Threshold,
pContext->AckCount,
Value / 1000,
Value % 1000 ));
}
DEBUG_CODE_END ( );
//
// Retransmit the first packet in the window
//
pPacket = pContext->pTxHead;
if ( NULL != pPacket ) {
PacketTx ( pContext, pPacket );
}
DBG_EXIT ( );
}
}
/**
Entry point for the TFTP server application.
@param [in] Argc The number of arguments
@param [in] Argv The argument value array
@retval 0 The application exited normally.
@retval Other An error occurred.
**/
int
main (
IN int Argc,
IN char **Argv
)
{
UINTN Index;
TSDT_TFTP_SERVER * pTftpServer;
EFI_STATUS Status;
UINT64 TriggerTime;
//
// Get the performance counter characteristics
//
pTftpServer = &mTftpServer;
if ( PcdGetBool ( Tftp_HighSpeed )
|| PcdGetBool ( Tftp_Bandwidth )) {
pTftpServer->ClockFrequency = GetPerformanceCounterProperties ( &pTftpServer->Time1,
&pTftpServer->Time2 );
}
//
// Create a timer event to start TFTP port
//
Status = gBS->CreateEvent ( EVT_TIMER,
TPL_TFTP_SERVER,
NULL,
NULL,
&pTftpServer->TimerEvent );
if ( !EFI_ERROR ( Status )) {
//
// Compute the poll interval
//
TriggerTime = TFTP_PORT_POLL_DELAY * ( 1000 * 10 );
Status = gBS->SetTimer ( pTftpServer->TimerEvent,
TimerPeriodic,
TriggerTime );
if ( !EFI_ERROR ( Status )) {
DEBUG (( DEBUG_TFTP_PORT, "TFTP port timer started\r\n" ));
//
// Run the TFTP server forever
//
pTftpServer->Udpv4Index = -1;
pTftpServer->Udpv6Index = -1;
do {
//
// Poll the network layer to create the TFTP port
// for the tftp server. More than one attempt may
// be necessary since it may take some time to get
// the IP address and initialize the upper layers
// of the network stack.
//
if ( DIM ( pTftpServer->TftpPort ) != pTftpServer->Entries ) {
do {
//
// Wait a while before polling for a connection
//
if ( EFI_SUCCESS != gBS->CheckEvent ( pTftpServer->TimerEvent )) {
if ( 0 == pTftpServer->Entries ) {
break;
}
gBS->WaitForEvent ( 1, &pTftpServer->TimerEvent, &Index );
}
//
// Poll for a network connection
//
TftpServerSocket ( pTftpServer,
AF_INET,
&pTftpServer->Udpv4Index );
TftpServerSocket ( pTftpServer,
AF_INET6,
&pTftpServer->Udpv6Index );
} while ( 0 == pTftpServer->Entries );
}
//
// Poll the socket for activity
//
do {
SocketPoll ( pTftpServer );
//
// Normal TFTP lets the client request the retransmit by
// sending another ACK for the previous packet
//
if ( PcdGetBool ( Tftp_HighSpeed )) {
UINT64 CurrentTime;
UINT64 ElapsedTime;
TSDT_CONNECTION_CONTEXT * pContext;
TFTP_PACKET * pPacket;
//
// High speed TFTP uses an agressive retransmit to
// get the TFTP client moving again when the ACK or
// previous data packet was lost.
//
// Get the current time
//
CurrentTime = GetPerformanceCounter ( );
//
// Walk the list of contexts
//
pContext = pTftpServer->pContextList;
while ( NULL != pContext )
{
//
// Check for a transmit timeout
//
pPacket = pContext->pTxHead;
if ( NULL != pPacket ) {
//
// Compute the elapsed time
//
if ( pTftpServer->Time2 > pTftpServer->Time1 ) {
ElapsedTime = CurrentTime - pPacket->TxTime;
}
else {
ElapsedTime = pPacket->TxTime - CurrentTime;
}
ElapsedTime = GetTimeInNanoSecond ( ElapsedTime );
//
// Determine if a retransmission is necessary
//
if ( ElapsedTime >= pContext->Rtt2x ) {
DEBUG (( DEBUG_WINDOW,
"0x%08x: Context TX timeout for packet 0x%08x, Window: %d\r\n",
pContext,
pPacket,
pContext->WindowSize ));
WindowTimeout ( pContext );
}
}
//
// Set the next context
//
pContext = pContext->pNext;
}
}
} while ( DIM ( pTftpServer->TftpPort ) == pTftpServer->Entries );
} while ( !mbTftpServerExit );
//
// Done with the timer event
//
gBS->SetTimer ( pTftpServer->TimerEvent,
TimerCancel,
0 );
}
gBS->CloseEvent ( pTftpServer->TimerEvent );
}
//
// Return the final status
//
DBG_EXIT_STATUS ( Status );
return Status;
}