This is a simple but complete TFTP server capable to run on MBED-OS5. It can receive and send files and listens on port 69. Please notice that it supports only octet (raw 8 bit bytes) mode transfers. It is part of the LaOS (Laser Open Source) project: http://www.laoslaser.org/
Fork of TFTPServer by
Import programTFTPServerTest
This is an example code for using the TFTPServer library.
A simple TFTP server. NOTE: Supports only octet (raw 8 bit bytes) mode transfers.
TFTPServer.cpp
- Committer:
- hudakz
- Date:
- 2018-03-20
- Revision:
- 2:f7c0fbc8c5aa
- Parent:
- 1:9c973065a97e
File content as of revision 2:f7c0fbc8c5aa:
/*
* TFTPServer.cpp
* Simple TFTP server
*
* Copyright (c) 2011 Jaap Vermaas
* Modified by Zoltan Hudak 2018 for MBED-OS5
*
* This file is part of the LaOS project (see: http://wiki.laoslaser.org)
*
* LaOS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LaOS 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. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LaOS. If not, see <http://www.gnu.org/licenses/>.
*
* Minimal TFTP Server
* * Receive and send files via TFTP
* * Server handles only one transfer at a time
* * Supports only octet (raw 8 bit bytes) mode transfers
* * fixed block size: 512 bytes
*
*/
#include "TFTPServer.h"
#include "EthernetInterface.h"
//#define TFTP_DEBUG
/**
* @brief Creates a new TFTP server listening on myPort.
* @note
* @param net A pointer to EthernetInterface object.
* @param port A port to listen on (defaults to 69).
* @retval
*/
TFTPServer::TFTPServer(EthernetInterface* net, uint16_t myPort /* = 69 */ )
{
port = myPort;
#ifdef TFTP_DEBUG
printf("TFTPServer(): port=%d\r\n", myPort);
#endif
socket = new UDPSocket(net);
state = LISTENING;
if (socket->bind(port))
{
socketAddr = SocketAddress(0, port);
state = ERROR;
}
#ifdef TFTP_DEBUG
printf("FTP server state = %d\r\n", getState());
#endif
socket->set_blocking(false);
fileCounter = 0;
}
/**
* @brief Destroys this instance of the TFTP server.
* @note
* @param
* @retval
*/
TFTPServer::~TFTPServer()
{
socket->close();
delete(socket);
strcpy(remoteIP, "");
state = DELETED;
}
/**
* @brief Resets the TFTP server.
* @note
* @param
* @retval
*/
void TFTPServer::reset()
{
socket->close();
delete(socket);
strcpy(remoteIP, "");
socket = new UDPSocket();
state = LISTENING;
if (socket->bind(port))
{
socketAddr = SocketAddress(0, port);
state = ERROR;
}
socket->set_blocking(false);
strcpy(fileName, "");
fileCounter = 0;
}
/**
* @brief Gets current TFTP status.
* @note
* @param
* @retval
*/
TFTPServer::State TFTPServer::getState()
{
return state;
}
/**
* @brief Temporarily disables incoming TFTP connections.
* @note
* @param
* @retval
*/
void TFTPServer::suspend()
{
state = SUSPENDED;
}
/**
* @brief Resumes incoming TFTP connection after suspension.
* @note
* @param
* @retval
*/
void TFTPServer::resume()
{
if (state == SUSPENDED)
state = LISTENING;
}
/**
* @brief Polls for data or new connection.
* @note
* @param
* @retval
*/
void TFTPServer::poll()
{
if ((state == SUSPENDED) || (state == DELETED) || (state == ERROR))
return;
socket->set_blocking(false);
char buff[516];
int len = socket->recvfrom(&socketAddr, buff, sizeof(buff));
if (len <= 0)
return;
#ifdef TFTP_DEBUG
printf("Got block with size %d.\n\r", len);
#endif
switch (state) {
case LISTENING:
{
switch (buff[1]) {
case 0x01: // RRQ
connectRead(buff);
break;
case 0x02: // WRQ
connectWrite(buff);
break;
case 0x03: // DATA before connection established
sendError("No data expected.\r\n");
break;
case 0x04: // ACK before connection established
sendError("No ack expected.\r\n");
break;
case 0x05: // ERROR packet received
#ifdef TFTP_DEBUG
printf("TFTP Eror received.\r\n");
#endif
break;
default: // unknown TFTP packet type
sendError("Unknown TFTP packet type.\r\n");
break;
} // switch buff[1]
break; // case listening
}
case READING:
{
if (cmpHost())
{
switch (buff[1]) {
case 0x01:
// if this is the receiving host, send first packet again
if (blockCounter == 1)
{
ack(0);
dupCounter++;
}
if (dupCounter > 10)
{ // too many dups, stop sending
sendError("Too many dups");
fclose(file);
state = LISTENING;
strcpy(remoteIP, "");
}
break;
case 0x02:
// this should never happen, ignore
sendError("WRQ received on open read socket");
fclose(file);
state = LISTENING;
strcpy(remoteIP, "");
break;
case 0x03:
// we are the sending side, ignore
sendError("Received data package on sending socket");
fclose(file);
state = LISTENING;
strcpy(remoteIP, "");
break;
case 0x04:
// last packet received, send next if there is one
dupCounter = 0;
if (blockSize == 516)
{
getBlock();
sendBlock();
}
else
{ //EOF
fclose(file);
state = LISTENING;
strcpy(remoteIP, "");
}
break;
default: // this includes 0x05 errors
sendError("Received 0x05 error message");
fclose(file);
state = LISTENING;
strcpy(remoteIP, "");
break;
} // switch (buff[1])
}
#ifdef TFTP_DEBUG
else
printf("Ignoring package from other remote client during RRQ.\r\n");
#endif
break; // reading
}
case WRITING:
{
if (cmpHost())
{
switch (buff[1]) {
case 0x02:
{
// if this is a returning host, send ack again
ack(0);
#ifdef TFTP_DEBUG
TFTP_DEBUG("Resending Ack on WRQ.\r\n");
#endif
break; // case 0x02
}
case 0x03:
{
int block = (buff[2] << 8) + buff[3];
if ((blockCounter + 1) == block)
{
ack(block);
// new packet
char* data = &buff[4];
fwrite(data, 1, len - 4, file);
blockCounter++;
dupCounter = 0;
}
else
{ // mismatch in block nr
if ((blockCounter + 1) < block)
{ // too high
sendError("Packet count mismatch");
fclose(file);
state = LISTENING;
remove(fileName);
strcpy(remoteIP, "");
}
else
{ // duplicate packet, send ACK again
if (dupCounter > 10)
{
sendError("Too many dups");
fclose(file);
remove(fileName);
state = LISTENING;
}
else
{
ack(blockCounter);
dupCounter++;
}
}
}
if (len < 516)
{
ack(blockCounter);
fclose(file);
state = LISTENING;
strcpy(remoteIP, "");
fileCounter++;
#ifdef TFTP_DEBUG
printf("File receive finished.\r\n");
#endif
}
break; // case 0x03
}
default:
{
sendError("No idea why you're sending me this!");
break; // default
}
} // switch (buff[1])
}
#ifdef TFTP_DEBUG
else
printf("Ignoring packege from other remote client during WRQ.\r\n");
#endif
break; // writing
}
case ERROR:
case SUSPENDED:
case DELETED:
default:
{ }
} // state
}
/**
* @brief Gets the file name during read and write.
* @note
* @param name A pointer to C-style string to be filled with file name.
* @retval
*/
void TFTPServer::getFileName(char* name)
{
sprintf(name, "%s", fileName);
}
/**
* @brief Returns number of received files.
* @note
* @param
* @retval
*/
int TFTPServer::fileCount()
{
return fileCounter;
}
/**
* @brief Creates a new connection reading a file from server.
* @note Sends the file to the remote client.
* Sends en error message to the remote client in case of failure.
* @param buff A char array to pass data.
* @retval
*/
void TFTPServer::connectRead(char* buff)
{
remoteIP = const_cast<char*>(socketAddr.get_ip_address());
remotePort = socketAddr.get_port();
blockCounter = 0;
dupCounter = 0;
sprintf(fileName, "%s", &buff[2]);
if (modeOctet(buff))
file = fopen(fileName, "rb");
else
file = fopen(fileName, "r");
if (!file)
{
state = LISTENING;
char msg[123] = { "Could not read file: " };
strcat(msg, fileName);
strcat(msg, "\r\n");
sendError(msg);
}
else
{
// file ready for reading
state = READING;
#ifdef TFTP_DEBUG
printf
(
"Listening: Requested file %s from TFTP connection %d.%d.%d.%d port %d\r\n",
fileName,
clientIp[0],
clientIp[1],
clientIp[2],
clientIp[3],
clientPort
);
#endif
getBlock();
sendBlock();
}
}
/**
* @brief Creates a new connection for writing a file to the server.
* @note Sends the file to the TFTP server.
* Sends error message to the remote client in case of failure.
* @param buff A char array to pass data.
* @retval
*/
void TFTPServer::connectWrite(char* buff)
{
remoteIP = const_cast<char*>(socketAddr.get_ip_address());
remotePort = socketAddr.get_port();
ack(0);
blockCounter = 0;
dupCounter = 0;
sprintf(fileName, "%s", &buff[2]);
if (modeOctet(buff))
file = fopen(fileName, "wb");
else
file = fopen(fileName, "w");
if (file == NULL)
{
sendError("Could not open file to write.\r\n");
state = LISTENING;
strcpy(remoteIP, "");
}
else
{
// file ready for writing
blockCounter = 0;
state = WRITING;
#ifdef TFTP_DEBUG
printf
(
"Listening: Incoming file %s on TFTP connection from %d.%d.%d.%d clientPort %d\r\n",
fileName,
clientIp[0],
clientIp[1],
clientIp[2],
clientIp[3],
clientPort
);
#endif
}
}
/**
* @brief Gets DATA block from file on disk into memory.
* @note
* @param
* @retval
*/
void TFTPServer::getBlock()
{
blockCounter++;
blockBuff[0] = 0x00;
blockBuff[1] = 0x03;
blockBuff[2] = blockCounter >> 8;
blockBuff[3] = blockCounter & 255;
blockSize = 4 + fread((void*) &blockBuff[4], 1, 512, file);
}
/**
* @brief Sends DATA block to remote client.
* @note
* @param
* @retval
*/
void TFTPServer::sendBlock()
{
socket->sendto(socketAddr, blockBuff, blockSize);
}
/**
* @brief Compares host's IP and Port with connected remote machine.
* @note
* @param
* @retval
*/
int TFTPServer::cmpHost()
{
char ip[17];
strcpy(ip, socketAddr.get_ip_address());
int port = socketAddr.get_port();
return((strcmp(ip, remoteIP) == 0) && (port == remotePort));
}
/**
* @brief Sends ACK to remote client.
* @note
* @param
* @retval
*/
void TFTPServer::ack(int val)
{
char ack[4];
ack[0] = 0x00;
ack[1] = 0x04;
if ((val > 603135) || (val < 0))
val = 0;
ack[2] = val >> 8;
ack[3] = val & 255;
socket->sendto(socketAddr, ack, 4);
}
/**
* @brief Sends ERROR message to remote client.
* @note msg A C-style string with error message to be sent.
* @param
* @retval
*/
void TFTPServer::sendError(const char* msg)
{
errorBuff[0] = 0x00;
errorBuff[1] = 0x05;
errorBuff[2] = 0x00;
errorBuff[3] = 0x00;
errorBuff[4] = '\0'; // termination char
strcat(&errorBuff[4], msg);
int len = 4 + strlen(&errorBuff[4]) + 1;
socket->sendto(socketAddr, errorBuff, len);
#ifdef TFTP_DEBUG
printf("Error: %s\r\n", msg);
#endif
}
/**
* @brief Checks if connection mode of client is octet/binary.
* @note buff A char array.
* @param
* @retval
*/
int TFTPServer::modeOctet(char* buff)
{
int x = 2;
while (buff[x++] != 0);
// get beginning of mode field
int y = x;
while (buff[y] != 0)
{
buff[y] = tolower(buff[y]);
y++;
} // make mode field lowercase
return(strcmp(&buff[x++], "octet") == 0);
}

