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/

Dependents:   TFTPServerTest

Fork of TFTPServer by Jaap Vermaas

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);
}