blob: 0e93f77c0a539fb6c4098dbb2b38412b048f947c [file] [log] [blame]
/*
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"
/* FreeRTOS+FAT includes. */
#include "ff_stdio.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 ( 10000 )
#endif
#ifndef ipconfigTFTP_MAX_RETRIES
#define ipconfigTFTP_MAX_RETRIES ( 6 )
#endif
/* Standard/expected TFTP port number. */
#define tftpPORT_NUMBER ( ( uint16_t ) 69 )
/* Offset to the file name within the frame. */
#define tftpFILE_NAME_OFFSET ( 2 )
/* Number of bytes in the Ack message. */
#define tftpACK_MESSAGE_LENGTH 4
/* Files are sent in fixed length blocks of 512 (the original maximum). */
#define tftpMAX_DATA_LENGTH ( ( size_t ) 512 )
/* Standard TFTP opcodes. */
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;
/* Header used in data transfer packets. */
#include "pack_struct_start.h"
struct DataPacketHeader
{
uint16_t usOpcode;
uint16_t usBlockNumber;
}
#include "pack_struct_end.h"
typedef struct DataPacketHeader TFTPBlockNumberHeader_t;
/*
* 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( FF_FILE *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 FF_FILE* 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;
FF_FILE *pxFile;
/* 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. */
if( ( pucUDPPayloadBuffer[ 0 ] == ( uint8_t ) 0 ) && ( pucUDPPayloadBuffer[ 1 ] == ( uint8_t ) eWriteRequest ) )
{
/* If the write request is valid pcFileName will get set to
point to the file name within pucWriteRequestBuffer - otherwise
an appropriate error will be sent on xTFTPListeningSocket. */
pcFileName = prvValidateWriteRequest( xTFTPListeningSocket, &xClient, pucUDPPayloadBuffer );
if( pcFileName != NULL )
{
/* If the file does not already exist, and can be created,
then xFile will get set to the file's open handle.
Otherwise an appropriate error will be sent on
xTFTPListeningSocket. */
pxFile = prvValidateFileToWrite( xTFTPListeningSocket, &xClient, pcFileName );
if( pxFile != NULL )
{
/* Manage reception of the file. */
prvReceiveFile( pxFile, &xClient );
}
}
}
else
{
/* Not a transfer ID handled by this server. */
prvSendTFTPError( xTFTPListeningSocket, &xClient, eUnknownTransferID );
}
/* The buffer was received using zero copy, so *must* be freed. */
FreeRTOS_ReleaseUDPPayloadBuffer( pucUDPPayloadBuffer );
}
}
}
/*-----------------------------------------------------------*/
static BaseType_t prvReceiveFile( FF_FILE *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 )
{
/* 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;
do
{
/* The acknowledgment sent here may be a duplicate if the last call
to FreeRTOS_recvfrom() timee out. */
prvSendAcknowledgement( xTFTPRxSocket, pxClient, usExpectedBlockNumber );
/* 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. */
FreeRTOS_printf( ( "Error: Timeout.\n" ) );
xRetries++;
if( xRetries > ipconfigTFTP_MAX_RETRIES )
{
FreeRTOS_printf( ( "Error: Retry limit exceeded.\n" ) );
xReturn = pdFAIL;
}
}
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 ) );
/* Ack the data then write the data to the file. */
prvSendAcknowledgement( xTFTPRxSocket, pxClient, usExpectedBlockNumber );
/* The data is located by jumping over the header. */
/*_RB_ Is it ok to write 0 bytes? */
xBlocksWritten = ff_fwrite( pucFileBuffer + sizeof( TFTPBlockNumberHeader_t ),
xBytesOfFileDataReceived,
xBlocksToWrite,
pxFile );
if( xBlocksWritten != xBlocksToWrite )
{
/* File could not be written. */
prvSendTFTPError( xTFTPRxSocket, pxClient, eDiskFull );
xReturn = pdFAIL;
}
/* Start to receive the next block. */
xRetries = 0;
}
else
{
prvSendTFTPError( xTFTPRxSocket, pxClient, eIllegalTFTPOperation );
xReturn = pdFAIL;
}
/* pucFileBuffer was obtained using zero copy mode, so the
buffer must be freed now its contents have been written to the
disk. */
FreeRTOS_ReleaseUDPPayloadBuffer( pucFileBuffer );
}
/* 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" ) );
}
ff_fclose( pxFile );
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 FF_FILE* prvValidateFileToWrite( Socket_t xSocket, struct freertos_sockaddr *pxClient, const char *pcFileName )
{
FF_FILE *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. */
pxFile = ff_fopen( pcFileName, "r" );
if( pxFile != NULL )
{
/* Can't receive a new file without deleting the old one first. */
ff_fclose( pxFile );
pxFile = NULL;
prvSendTFTPError( xSocket, pxClient, eFileAlreadyExists );
}
else
{
/* The file does not already exist. Attempt to open the file in write
mode, which will cause it to be created. */
pxFile = ff_fopen( pcFileName, "w" );
if( pxFile == NULL )
{
/* The file cannot be created. */
prvSendTFTPError( xSocket, pxClient, eAccessViolation );
}
}
return pxFile;
}
/*-----------------------------------------------------------*/
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 ] );
/* 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( strcmpi( 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. */
}
}
}