TFTPserver improvements

glenenglish wrote on Thursday, September 22, 2016:

attached is an improved TFTP server

I use it to write in the a FLASH ROM for loadng a XIlinx FPGA.
This one works :slight_smile: . There was an inssue with double responses, and buffer returning.
Only a few lines changed

Happy to assist anyone with it, or provide flash writing code .

tftpserver.c


/*
    FreeRTOS V9.0.0 - Copyright (C) 2016 Real Time Engineers Ltd.
    All rights reserved

    VISIT http://www.FreeRTOS.org TO ENSURE YOU ARE USING THE LATEST VERSION.

    This file is part of the FreeRTOS distribution.

    FreeRTOS is free software; you can redistribute it and/or modify it under
    the terms of the GNU General Public License (version 2) as published by the
    Free Software Foundation >>!AND MODIFIED BY!<< the FreeRTOS exception.

    ***************************************************************************
    >>!   NOTE: The modification to the GPL is included to allow you to     !<<
    >>!   distribute a combined work that includes FreeRTOS without being   !<<
    >>!   obliged to provide the source code for proprietary components     !<<
    >>!   outside of the FreeRTOS kernel.                                   !<<
    ***************************************************************************

    FreeRTOS is distributed in the hope that it will be useful, but WITHOUT ANY
    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    FOR A PARTICULAR PURPOSE.  Full license text is available on the following
    link: http://www.freertos.org/a00114.html

    ***************************************************************************
     *                                                                       *
     *    FreeRTOS provides completely free yet professionally developed,    *
     *    robust, strictly quality controlled, supported, and cross          *
     *    platform software that is more than just the market leader, it     *
     *    is the industry's de facto standard.                               *
     *                                                                       *
     *    Help yourself get started quickly while simultaneously helping     *
     *    to support the FreeRTOS project by purchasing a FreeRTOS           *
     *    tutorial book, reference manual, or both:                          *
     *    http://www.FreeRTOS.org/Documentation                              *
     *                                                                       *
    ***************************************************************************

    http://www.FreeRTOS.org/FAQHelp.html - Having a problem?  Start by reading
    the FAQ page "My application does not run, what could be wrong?".  Have you
    defined configASSERT()?

    http://www.FreeRTOS.org/support - In return for receiving this top quality
    embedded software for free we request you assist our global community by
    participating in the support forum.

    http://www.FreeRTOS.org/training - Investing in training allows your team to
    be as productive as possible as early as possible.  Now you can receive
    FreeRTOS training directly from Richard Barry, CEO of Real Time Engineers
    Ltd, and the world's leading authority on the world's leading RTOS.

    http://www.FreeRTOS.org/plus - A selection of FreeRTOS ecosystem products,
    including FreeRTOS+Trace - an indispensable productivity tool, a DOS
    compatible FAT file system, and our tiny thread aware UDP/IP stack.

    http://www.FreeRTOS.org/labs - Where new FreeRTOS products go to incubate.
    Come and try FreeRTOS+TCP, our new open source TCP/IP stack for FreeRTOS.

    http://www.OpenRTOS.com - Real Time Engineers ltd. license FreeRTOS to High
    Integrity Systems ltd. to sell under the OpenRTOS brand.  Low cost OpenRTOS
    licenses offer ticketed support, indemnification and commercial middleware.

    http://www.SafeRTOS.com - High Integrity Systems also provide a safety
    engineered and independently SIL3 certified version for use in safety and
    mission critical applications that require provable dependability.

    1 tab == 4 spaces!
*/

/*
 * A basic TFTP server that can currently only be used to receive files, and not
 * send files.  This is a slim implementation intended for use in boot loaders
 * and other applications that require over the air reception of files.
 */

/* Standard includes. */
#include <stdint.h>
#include <stdio.h>
#include <string.h>

/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h"

/* FreeRTOS+TCP includes. */
#include "FreeRTOS_IP.h"
#include "FreeRTOS_Sockets.h"


#if( ipconfigALLOW_SOCKET_SEND_WITHOUT_BIND != 1 )
	#error ipconfigALLOW_SOCKET_SEND_WITHOUT_BIND must be set to one to use this TFTP server.
#endif

#if( configTICK_RATE_HZ > 1000 )
	#error The TFTP server uses the pdMS_TO_TICKS() macro, so configTICK_RATE_HZ must be less than or equal to 1000
#endif

#ifndef ipconfigTFTP_TIME_OUT_MS
	#define ipconfigTFTP_TIME_OUT_MS			( 30000 )
#endif

#ifndef ipconfigTFTP_MAX_RETRIES
	#define ipconfigTFTP_MAX_RETRIES			( 6 )
#endif

#define tftpPORT_NUMBER 	( ( uint16_t ) 69 )

#define tftpFILE_NAME_OFFSET	( 2 )
#define tftpACK_MESSAGE_LENGTH 4
#define tftpMAX_DATA_LENGTH	( ( size_t ) 512 )

typedef enum
{
	eReadRequest = 1,
	eWriteRequest,
	eData,
	eAck,
	eError
} eTFTPOpcode_t;

/* Error codes from the RFC. */
typedef enum
{
	eFileNotFound = 1,
	eAccessViolation,
	eDiskFull,
	eIllegalTFTPOperation,
	eUnknownTransferID,
	eFileAlreadyExists
} eTFTPErrorCode_t;



#include "pack_struct_start.h"
struct DataPacketHeader
{
	uint16_t usOpcode;
	uint16_t usBlockNumber;
}
#include "pack_struct_end.h"
typedef struct DataPacketHeader TFTPBlockNumberHeader_t;

/* this will be the flash write routine */
extern size_t ff_fwrite(void * data, size_t nBytes, size_t blocksToWrite, uint32_t blocknumber);


/*
 * Manages a single TFTP connection at a time.
 */
static void prvSimpleTFTPServerTask( void *pvParameters );

/*
 * Manage the reception of a file.  If the file is received correctly then
 * return pdPASS, otherwise return pdFAIL.
 */
static BaseType_t prvReceiveFile( void *pxFile, struct freertos_sockaddr *pxClient  );

/*
 * Send an error frame to the client.
 */
static void prvSendTFTPError( Socket_t xSocket, struct freertos_sockaddr *pxClient, eTFTPErrorCode_t eErrorCode );

/*
 * Check a received write request contains a potentially valid file name string,
 * and is a binary mode transfer.  If so return a pointer to the file name with
 * the write request packet received from the network, otherwise return NULL.
 */
static const char* prvValidateWriteRequest( Socket_t xSocket, struct freertos_sockaddr *pxClient, uint8_t *pucUDPPayloadBuffer );

/*
 * Called after a valid write request has been received to first check the file
 * does not already exist, and if the file does not exist, create the file ready
 * to be written.  If the file did already exist, or if the file could not be
 * created, then NULL is returned - otherwise a handle to the created file is
 * returned.
 */
static void* prvValidateFileToWrite( Socket_t xSocket, struct freertos_sockaddr *pxClient, const char *pcFileName );

/*
 * Send an acknowledgement packet to pxClient with block number usBlockNumber.
 */
static void prvSendAcknowledgement( Socket_t xSocket, struct freertos_sockaddr *pxClient, uint16_t usBlockNumber );

/* The index for the error string below MUST match the value of the applicable
eTFTPErrorCode_t error code value. */
static const char *cErrorStrings[] =
{
	NULL, /* Not valid. */
	"File not found.",
	"Access violation.",
	"Disk full or allocation exceeded.",
	"Illegal TFTP operation.",
	"Unknown transfer ID.",
	"File already exists.",
	"No such user."
};

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

void vStartTFTPServerTask( uint16_t usStackSize, UBaseType_t uxPriority )
{
	/* A single server task is created.  Currently this is only capable of
	managing one TFTP transfer at a time. */
	xTaskCreate( prvSimpleTFTPServerTask, "TFTPd", usStackSize, NULL, uxPriority, NULL );
}
/*-----------------------------------------------------------*/

static void prvSimpleTFTPServerTask( void *pvParameters )
{
int32_t lBytes;
uint8_t *pucUDPPayloadBuffer;
struct freertos_sockaddr xClient, xBindAddress;
uint32_t xClientLength = sizeof( xClient ), ulIPAddress;
Socket_t xTFTPListeningSocket;
const char *pcFileName;
void *pxFile;
int HaveReleasedBuffer = 0;

	/* Just to prevent compiler warnings. */
	( void ) pvParameters;

	/* Attempt to open the socket.  The receive block time defaults to the max
	delay, so there is no need to set that separately. */
	xTFTPListeningSocket = FreeRTOS_socket( FREERTOS_AF_INET, FREERTOS_SOCK_DGRAM, FREERTOS_IPPROTO_UDP );
	configASSERT( xTFTPListeningSocket != FREERTOS_INVALID_SOCKET );

	/* Bind to the standard TFTP port. */
	FreeRTOS_GetAddressConfiguration( &ulIPAddress, NULL, NULL, NULL );
	xBindAddress.sin_addr = ulIPAddress;
	xBindAddress.sin_port = FreeRTOS_htons( tftpPORT_NUMBER );
	FreeRTOS_bind( xTFTPListeningSocket, &xBindAddress, sizeof( xBindAddress ) );

	for( ;; )
	{
		/* Look for the start of a new transfer on the TFTP port.  ulFlags has
		the zero copy bit set (FREERTOS_ZERO_COPY) indicating to the stack that
		a reference to the received data should be passed out to this task using
		the second parameter to the FreeRTOS_recvfrom() call.  When this is done
		the IP stack is no longer responsible for releasing the buffer, and the
		task *must* return the buffer to the stack when it is no longer
		needed. */
		lBytes = FreeRTOS_recvfrom( xTFTPListeningSocket, ( void * ) &pucUDPPayloadBuffer, 0, FREERTOS_ZERO_COPY, &xClient, &xClientLength );

		if( lBytes > 0 ) {

			/* Could this be a new write request?  The opcode is contained in
			the first two bytes of the received data. */

			HaveReleasedBuffer=0;

			if( ( pucUDPPayloadBuffer[ 0 ] == ( uint8_t ) 0 ) && ( pucUDPPayloadBuffer[ 1 ] == ( uint8_t ) eWriteRequest ) ) {
				pcFileName = prvValidateWriteRequest( xTFTPListeningSocket, &xClient, pucUDPPayloadBuffer );

				if( pcFileName != NULL )	{
					pxFile = prvValidateFileToWrite( xTFTPListeningSocket, &xClient, pcFileName );
					FreeRTOS_ReleaseUDPPayloadBuffer( pucUDPPayloadBuffer );/* to do - better structure */
                    HaveReleasedBuffer=1;
					if( pxFile != NULL ) {
						/* Manage reception of the reset of the file. */
						prvReceiveFile( pxFile, &xClient );
					}
					else {
						/* bad file name */
                        prvSendTFTPError( xTFTPListeningSocket, &xClient, eFileNotFound );
					}
				}
				else {
					prvSendTFTPError( xTFTPListeningSocket, &xClient, eFileNotFound );

				}
			}
			else {

				/* Not a transfer ID handled by this server. */
				prvSendTFTPError( xTFTPListeningSocket, &xClient, eUnknownTransferID );
			}

			if (!HaveReleasedBuffer) FreeRTOS_ReleaseUDPPayloadBuffer( pucUDPPayloadBuffer );/* to do - better structure */
		}
		else {
			/* socket error  ?*/
		}
	}
}
/*-----------------------------------------------------------*/

static BaseType_t prvReceiveFile( void *pxFile, struct freertos_sockaddr *pxClient )
{
BaseType_t xReturn = pdPASS, xRetries = 0;
uint16_t usExpectedBlockNumber;
Socket_t xTFTPRxSocket = FREERTOS_INVALID_SOCKET;
TickType_t xRxTimeout = pdMS_TO_TICKS( ipconfigTFTP_TIME_OUT_MS );
int32_t lBytes;
uint8_t *pucFileBuffer;
struct freertos_sockaddr xClient;
uint32_t xClientLength = sizeof( xClient );
TFTPBlockNumberHeader_t *pxHeader;
size_t xBlocksWritten, xBytesOfFileDataReceived = tftpMAX_DATA_LENGTH;

const size_t xBlocksToWrite = 1;


	/* The file is open for writing, now create the socket on which the file
	will be received from the client.  Note the socket is not bound	here - so
	will be automatically bound to a port number selected by the IP stack when
	it is used for the first time. */
	xTFTPRxSocket = FreeRTOS_socket( FREERTOS_AF_INET, FREERTOS_SOCK_DGRAM, FREERTOS_IPPROTO_UDP );



	if((xTFTPRxSocket != FREERTOS_INVALID_SOCKET )&&( xTFTPRxSocket != FREERTOS_INVALID_SOCKET )) {
		/* The socket's Rx block time is set to the user configurable timeout
		value. */
		FreeRTOS_setsockopt( xTFTPRxSocket, 0, FREERTOS_SO_RCVTIMEO, &xRxTimeout, sizeof( xRxTimeout ) );

		/* Acknowledge the write request so the client starts to send the file.
		The first acknowledgment does not have a corresponding block number so
		the special case block number 0 is used. */
		usExpectedBlockNumber = 0;

		/*
		after first return, seems that some TFTP clients can't turnaround fast enough for this embedded
		so we just delay a little more.....
		*/
		vTaskDelay(pdMS_TO_TICKS( 50 ));

		prvSendAcknowledgement( xTFTPRxSocket, pxClient, usExpectedBlockNumber );/* acknowledge block 0 (no data)  */


		do {
			/* Wait for next data packet.  Zero copy is used so it is the
			responsibility of this task to free the received data once it is no
			longer required. */
			lBytes = FreeRTOS_recvfrom( xTFTPRxSocket, ( void * ) &pucFileBuffer, 0, FREERTOS_ZERO_COPY, &xClient, &xClientLength );

			if( lBytes <= 0 ) {
				/* timed out ???? really - there is a 30 second timeout- it should have recovered.. give up now*/

				FreeRTOS_printf( ( "Error: Timeout.\n" ) );
#if 1
				xRetries++;
				if( xRetries > ipconfigTFTP_MAX_RETRIES ) 	{
					FreeRTOS_printf( ( "Error: Retry limit exceeded.\n" ) );
					xReturn = pdFAIL;
				}
#else
				xReturn = pdFAIL;
#endif
			}

			else {
				/* Data received.  It is expected to be the next sequential
				block. */
				usExpectedBlockNumber++;
				pxHeader = ( TFTPBlockNumberHeader_t * ) pucFileBuffer;
				pxHeader->usOpcode = FreeRTOS_ntohs( pxHeader->usOpcode );
				pxHeader->usBlockNumber = FreeRTOS_ntohs( pxHeader->usBlockNumber );

				/* Is the data as expected and from the expected IP address and
				port? */
				if( ( pxHeader->usOpcode == ( uint16_t ) eData ) 		 &&
					( pxHeader->usBlockNumber == usExpectedBlockNumber ) &&
					( pxClient->sin_addr == xClient.sin_addr ) 			 &&
					( pxClient->sin_port == xClient.sin_port ) )
					{
					/* Everything in the packet other than the header is file
					data. */
					xBytesOfFileDataReceived = ( size_t ) lBytes - sizeof( TFTPBlockNumberHeader_t );
					FreeRTOS_printf( ( "Received %d bytes of file data.\n", ( int ) xBytesOfFileDataReceived ) );

#if 1
					xReturn = CheckWriteVerifyRecvTFTPBlock( pucFileBuffer + sizeof( TFTPBlockNumberHeader_t ),
												xBytesOfFileDataReceived,
												(pxHeader->usBlockNumber)-1);
#else
xReturn = 0;	/* dry run without hitting flash */
#endif
					FreeRTOS_ReleaseUDPPayloadBuffer( pucFileBuffer );

					if( xReturn != 0 ) {
						/* File could not be written. */
						prvSendTFTPError( xTFTPRxSocket, pxClient, eAccessViolation );
						xReturn = pdFAIL;
					}
					else {
       					/* Ack the data then write the data to the file. */
						prvSendAcknowledgement( xTFTPRxSocket, pxClient, usExpectedBlockNumber );
						xReturn = pdPASS;
						/* Start to receive the next block. */
						xRetries = 0;
					}
				}
				else 	{
					FreeRTOS_ReleaseUDPPayloadBuffer( pucFileBuffer );
					prvSendTFTPError( xTFTPRxSocket, pxClient, eIllegalTFTPOperation );
					xReturn = pdFAIL;
				}
			}

		  /* Until a disk write fails, or the maximum number of retries is
		  exceeded, or fewer bytes than tftpMAX_DATA_LENGTH are received (which
		  indicates the end of the file). */
		} while( ( xReturn != pdFAIL ) && ( xBytesOfFileDataReceived == tftpMAX_DATA_LENGTH ) );

		FreeRTOS_printf( ( "Closing connection.\n" ) );
		FreeRTOS_closesocket( xTFTPRxSocket );

	}
	else
	{
		/* An error could be returned here, but it is probably cleaner to just
		time out as the error would have to be sent via the listening socket
		outside of this function. */
		FreeRTOS_printf( ( "Could not create socket to receive file.\n" ) );
	}

	return xReturn;
}
/*-----------------------------------------------------------*/

void prvSendAcknowledgement( Socket_t xSocket, struct freertos_sockaddr *pxClient, uint16_t usBlockNumber )
{
/* Small fixed size buffer, so not much to be gained by using the zero copy
interface, just send the buffer directly. */
TFTPBlockNumberHeader_t xAckMessage;

	xAckMessage.usOpcode = FreeRTOS_htons( ( ( uint16_t ) eAck ) );
	xAckMessage.usBlockNumber = FreeRTOS_htons( usBlockNumber );

	FreeRTOS_sendto( xSocket, ( void * ) &xAckMessage, tftpACK_MESSAGE_LENGTH, 0, pxClient, sizeof( struct freertos_sockaddr ) );
}
/*-----------------------------------------------------------*/

static void* prvValidateFileToWrite( Socket_t xSocket, struct freertos_sockaddr *pxClient, const char *pcFileName )
{
void  *pxFile;

	FreeRTOS_printf( ( "Write request for %s received\n", pcFileName ) );

	/* The file cannot be received if it already exists.  Attempt to open the
	file in read mode to see if it exists. */
	#if 0
	if (strcasecmp(pcFileName,"fpga2_k70t") == 0) return (void *)1;
	else return (void *)0;
	#else
		return (void *)1;/* any file... */
	#endif


}
/*-----------------------------------------------------------*/

static const char* prvValidateWriteRequest( Socket_t xSocket, struct freertos_sockaddr *pxClient, uint8_t *pucUDPPayloadBuffer )
{
char *pcFileName;
BaseType_t x;
const char *pcOctedMode = "octet";

	/* pcFileName is set to point to the file name which is inside the write
	request frame, so its important not to free the frame until the operation is
	over.  The start of the file name string is after the opcode, so two bytes
	into the packet. */
	pcFileName = ( char * ) &( pucUDPPayloadBuffer[ tftpFILE_NAME_OFFSET ] );
	const int ffconfigMAX_FILENAME = 16;
	/* Sanity check the file name. */
	for( x = 0; x < ffconfigMAX_FILENAME; x++ )
	{
		if( pcFileName[ x ] == 0x00 )
		{
			/* The end of the string was located. */
			break;
		}
		else if( ( pcFileName[ x ] < ' ' ) || ( pcFileName[ x ] > '~' ) )
		{
			/* Not a valid file name character. */
			pcFileName = NULL;
			break;
		}
		else
		{
			/* Just a character in the file name. */
		}
	}

	if( pcFileName != NULL )
	{
		/* Only binary transfers are supported, indicated by an 'octet' mode
		string following the file name. +1 to move past the null terminator to
		the start of the next string. */
		x++;
		if( strcasecmp( pcOctedMode, ( const char * ) &( pucUDPPayloadBuffer[ tftpFILE_NAME_OFFSET + x ] ) ) != 0 )
		{
			/* Not the expected mode. */
			prvSendTFTPError( xSocket, pxClient, eIllegalTFTPOperation );
			pcFileName = NULL;
		}
	}
	else
	{
		prvSendTFTPError( xSocket, pxClient, eFileNotFound );
	}

	return pcFileName;
}
/*-----------------------------------------------------------*/

static void prvSendTFTPError( Socket_t xSocket, struct freertos_sockaddr *pxClient, eTFTPErrorCode_t eErrorCode )
{
uint8_t *pucUDPPayloadBuffer = NULL;
const size_t xFixedSizePart = ( size_t ) 5; /* 2 byte opcode, plus two byte error code, plus string terminating 0. */
const size_t xNumberOfErrorStrings = sizeof( cErrorStrings ) / sizeof( char * );
size_t xErrorCode = ( size_t ) eErrorCode, xTotalLength = 0; /* Only initialised to keep compiler quiet. */
const char *pcErrorString = NULL;
int32_t lReturned;

	/* The total size of the packet to be sent depends on the length of the
	error string. */
	if( xErrorCode < xNumberOfErrorStrings )
	{
		pcErrorString = cErrorStrings[ xErrorCode ];

		/* This task is going to send using the zero copy interface.  The data
		being sent is therefore written directly into a buffer that is passed
		into, rather than copied into, the FreeRTOS_sendto() function.  First
		obtain a buffer of adequate length from the IP stack into which the
		error packet will be written.  Although a max delay is used, the actual
		delay will be capped to ipconfigMAX_SEND_BLOCK_TIME_TICKS. */
		xTotalLength = strlen( pcErrorString ) + xFixedSizePart;
		pucUDPPayloadBuffer = ( uint8_t * ) FreeRTOS_GetUDPPayloadBuffer( xTotalLength, portMAX_DELAY );
	}

	if( pucUDPPayloadBuffer != NULL )
	{
		FreeRTOS_printf( ( "Error: %s\n", pcErrorString ) );

		/* Create error packet: Opcode. */
		pucUDPPayloadBuffer[ 0 ] = 0;
		pucUDPPayloadBuffer[ 1 ] = ( uint8_t ) eError;

		/* Create error packet: Error code. */
		pucUDPPayloadBuffer[ 2 ] = 0;
		pucUDPPayloadBuffer[ 3 ] = ( uint8_t ) eErrorCode;

		/* Create error packet: Error string. */
		strcpy( ( ( char * ) &( pucUDPPayloadBuffer[ 4 ] ) ), pcErrorString );

		/* Pass the buffer into the send function.  ulFlags has the
		FREERTOS_ZERO_COPY bit set so the IP stack will take control of the
		buffer rather than copy data out of the buffer. */
		lReturned = FreeRTOS_sendto( xSocket,  						/* The socket to which the error frame is sent. */
									( void * ) pucUDPPayloadBuffer, /* A pointer to the the data being sent. */
									xTotalLength, 					/* The length of the data being sent. */
									FREERTOS_ZERO_COPY, 			/* ulFlags with the FREERTOS_ZERO_COPY bit set. */
									pxClient, 			/* Where the data is being sent. */
									sizeof( *pxClient ) );

		if( lReturned == 0 )
		{
			/* The send operation failed, so this task is still responsible
			for the buffer obtained from the IP stack.  To ensure the buffer
			is not lost it must either be used again, or, as in this case,
			returned to the IP stack using FreeRTOS_ReleaseUDPPayloadBuffer(). */
			FreeRTOS_ReleaseUDPPayloadBuffer( ( void * ) pucUDPPayloadBuffer );
		}
		else
		{
			/* The send was successful so the IP stack is now managing the
			buffer pointed to by pucUDPPayloadBuffer, and the IP stack will
			return the buffer once it has been sent. */
		}
	}
}


rtel wrote on Sunday, September 25, 2016:

Great thanks. Sorry it took so long for your post to appear - for some reason it went into moderation and we don’t receive notifications when that happens (it should happen in the first place).

glenenglish wrote on Friday, September 30, 2016:

No sweat. couldnt figure out how to attach a file ?

FYI I am also seeing double ACK’s when receiving a file with code derived from here FreeRTOS/TFTPServer.c at master · 0x4e/FreeRTOS · GitHub I don’t see how the attached improve version fixes it. The problem is there are to two prvSendAcknowledgement()'s in the main do loop of prvReceiveFile(). The first one is sent with each iteration and the second after receiving a block before it is written. I think the intent is to increase performance by ack’ing before the write so the client can start sending the next block but then two ack’s are sent. In some cases this is causing the linux tftp client to resend a block that the server doesn’t expect which causes the server to fail because it is picky about the block it will accept. I think a reasonable sloution is to condition the first prvSendAcknowledgement() on whether it was already sent. Something like this:

BaseType_t xReturn = pdPASS, xRetries = 0, xDontAck = pdFALSE;
...
...
            /* The acknowledgment sent here may be a duplicate if the last call
            to FreeRTOS_recvfrom() timed out. */
            if(!xDontAck)
            {
                prvSendAcknowledgement( xTFTPRxSocket, pxClient, usExpectedBlockNumber );
            }
            xDontAck = pdFALSE;
...
...
                    /* Ack the data then write the data to the file. */
                    prvSendAcknowledgement( xTFTPRxSocket, pxClient, usExpectedBlockNumber );
                    /* Don't Ack in next loop iteration since sent here. */
                    xDontAck = pdTRUE;

Matt

I don’t know anything about mentioned TFTP server, but for those interested, here you find an implementation of FTP, HTTP and NTP.
The FTP server make use of FreeRTOS+FAT.
There is also a version that uses IPv6 and multiple interfaces, but I didn’t upload that yet.
Please use the protocol sources together with the latest release of FreeRTOS+TCP.

TFTP is an order of magnitude less capable than FTP but two orders of magnitude less complex. It’s a convenient protocol to use for some things as opposed to rolling your own. Like the original poster I’m using it to transfer some firmware images. I had used the github implementation before and hadn’t noticed a problem but while testing a new network interface implementation I found that TFTP didn’t work. Wireshark revealed the double-ACK’s and I recalled this thread so I looked for his solution but I don’t see one. Anyway I suggested a solution and maybe will save others some debugging.