/***********************************************************************
    Mark Butcher    Bsc (Hons) MPhil MIET

    M.J.Butcher Consulting
    Birchstrasse 20f,    CH-5406, Rütihof
    Switzerland

    www.uTasker.com    Skype: M_J_Butcher

    ---------------------------------------------------------------------
    File:        FTP.c
    Project:     Single Chip Embedded Internet
    ---------------------------------------------------------------------
    Copyright (C) M.J.Butcher Consulting 2004..2013
    *********************************************************************
    08.05.2007 Change a routine array initialisation to avoid library memcpy {1}
    22.05.2007 Add sub-file write support for devices with large FLASH segments {2}
    01.06.2007 Improved FTP login when not anonymous                     {3}
    03.06.2007 Add optional checking of data port in active mode         {4}
    03.06.2007 Activate passive mode (see define FTP_PASV_SUPPORT)
    03.06.2007 Pass FTP timeout when starting FTP server plus operating mode {5}
    03.06.2007 Added clean QUIT handling                                 {6}
    06.06.2007 Changed empty directory display file to include -r at beginning - without this FireFTP can not work correctly (it doesn't display anthing though) {7}
    03.09.2007 Add optional retrigger of control socket idle timer on data port activity (DATA_PORT_TRIGGERS_CONTROL) {8}
    17.11.2007 Correct dependency FTP_SUPPORTS_DELETE rather than FTP_SUPPORTS_NAME_DISPLAY {9}
    17.11.2007 Add define FILE_NAMES_PER_FTP_FRAME to correct operation without file names and adapt for compatibility {10}
    23.04.2008 Add optional DEL *.* support                              {11}
    23.02.2009 Add fast download support with FTP_DATA_WINDOWS           {12}
    18.05.2009 Add user files to DIR and return deny when delete attempted{13}
    01.07.2009 Adapt for compatibility with STRING_OPTIMISATION (fnDebugDec() replaced by fnBufferDec()) {14}
    14.10.2009 Improve simulation support for embedded user files in file system {15}
    14.10.2009 Close connection on password failure [_CLOSE_ERROR]       {16}
    04.12.2009 Add optional utFAT support                                {17}
    09.12.2009 Close the data connection immediately if no directory content is returned {18}
    31.01.2010 FTP SD card file open for rename uses attribute UTFAT_OPEN_FOR_RENAME rather than UTFAT_OPEN_FOR_WRITE {19}
    10.05.2010 Add optional MANAGED_FILES control of deletes             {20}
    27.08.2011 Add EXTENDED_UFILESYSTEM support                          {21}
    07.09.2011 Correction for EXTENDED_UFILESYSTEM support               {22}
    22.12.2011 Correct display listing over multiple frames and extend to 64k files when working with extended file option {23}
    02.04.2012 Add EPRT and EPSV for IPv6 use                            {24}
    02.04.2012 Add control of utFAT access                               {25}
    03.06.2012 Add return value when sending MSG_FTP_QUITTING to avoid an unnecessary TCP ACK {26}
    03.06.2012 Cause the FTP server to always close the connection after responding to the quit command {27}
    27.11.2012 Correct MKD directory name                                {28}
    17.12.2012 Allow FTP server to work on multiple networks             {29}
    10.07.2013 Support PWD with FAT sub-directories                      {30}

*/

// FTP server with support for only one user - we want to use it for file uploads

#include "config.h"

#define _CLOSE_ERROR                                                     // test option

#if defined USE_FTP

/* =================================================================== */
/*                          local definitions                          */
/* =================================================================== */

#ifdef STRING_OPTIMISATION                                               // {14}
    #define _fnBufferDec(x, y, z) fnBufferDec(x, y, z)                   // new decimal string routine
#else
    #define _fnBufferDec(x, y, z) fnDebugDec(x, y, z)                    // original decimal string routine
#endif

#if !defined FTP_DISPLAY_USER_FILES
    #undef INTERNAL_USER_FILES
#endif

#define OWN_TASK                    TASK_FTP

#define FTP_STATE_CLOSED            0                                    // FTP command states
#define FTP_STATE_CONNECTED         1
#define FTP_STATE_LOGIN             2
#define FTP_STATE_ACTIVE_USER       3
#define FTP_STATE_PREPARE_CLOSE     4

#define MSG_DO_NOTHING              0                                    // Message states
#define MSG_REPEAT_DATA             1
#define MSG_REPEAT_CONTROL          2

#define MSG_SERVER_READY            10                                   // Command messages
#define MSG_DIR_OK                  11
#define MSG_ENTER_PASS              12
#define MSG_LOG_SUCCESS             13
#define MSG_LOG_FAILED              14
#define MSG_FTP_DATA                15
#define MSG_FTP_TYPE                16
#define MSG_FTP_DENIED              17
#define MSG_FTP_DIR                 18
#define MSG_FTP_OK                  19
#define MSG_DIR_CHANGED             20
#define MSG_FTP_UNKNOWN             21
#define MSG_DEL_OK                  22
#define MSG_FILE_LENGTH             23
#define MSG_PASV_OK                 24
#define MSG_BAD_DATA_CONNECTION     25
#define MSG_NOT_SUPPORTED           26
#define MSG_FTP_QUITTING            27
#define MSG_FTP_READY_FOR_RENAME    28
#define MSG_FTP_RENAME_SUCCESSFUL   29
#define MSG_EPSV_OK                 30

#define MSG_DIR                     40                                   // Data messages
#define MSG_UPLOAD                  41

#define FIRST_DATA_MESSAGE          MSG_DIR

#ifdef FTP_SUPPORTS_NAME_DISPLAY
    static const CHAR cFileType[]    = {' ', '1', ' ', '5', '0', '2', ' ', '5', '0', '2', ' '};
    static const CHAR cFileDate[]    = {' ', 'O', 'c', 't', ' ', '1', ' ', '2', '0', '1', '0', ' '}; // {13}
    static const CHAR cFileRights[]  = {'-', 'r', 'w', 'x', 'r', 'w', 'x', 'r', 'w', 'x'};

    #define LENGTH_OF_FILE_TYPE       sizeof(cFileType)
    #define LENGTH_OF_FILE_LENGTH     6                                  // 999k is max possible file size
    #define LENGTH_OF_FILE_DATE       sizeof(cFileDate)
    #ifdef INTERNAL_USER_FILES                                           // {13}
        #define LENGTH_OF_FILE_NAME   MAX_FILE_NAME_LENGTH
    #else
        #define LENGTH_OF_FILE_NAME   7                                  // {13} reduced by 1
    #endif
    #ifdef EXTENDED_UFILESYSTEM                                          // {22}
        #define LENGTH_OF_FILE_INFO   (EXTENDED_UFILESYSTEM + sizeof(cFileRights) + LENGTH_OF_FILE_TYPE + LENGTH_OF_FILE_LENGTH + LENGTH_OF_FILE_DATE + LENGTH_OF_FILE_NAME)
    #else
        #define LENGTH_OF_FILE_INFO   (sizeof(cFileRights) + LENGTH_OF_FILE_TYPE + LENGTH_OF_FILE_LENGTH + LENGTH_OF_FILE_DATE + LENGTH_OF_FILE_NAME)
    #endif

    #ifndef FILE_NAMES_PER_FTP_FRAME
        #define FILE_NAMES_PER_FTP_FRAME ((LAN_BUFFER_SIZE - ETH_HEADER_LEN - IP_MIN_HLEN - MIN_TCP_HLEN - 22) / LENGTH_OF_FILE_INFO)
    #endif
    #define FTP_DATA_BUFFER_LENGTH    (FILE_NAMES_PER_FTP_FRAME * LENGTH_OF_FILE_INFO) // space for 6 files to be displayed in 1 message
#else
    #define FTP_DATA_BUFFER_LENGTH    100
    #define FILE_NAMES_PER_FTP_FRAME  FTP_DATA_BUFFER_LENGTH             // {10}
#endif

#define FTP_MODE_ASCII  0
#define FTP_MODE_BINARY 1

#define DATA_INACTIVE   0
#define DO_LIST         1
#define DO_SAVE         2
#define DOING_SAVE      3
#define SAVE_COMPLETE   4
#define WAIT_COMPLETE   5
#define DO_UPLOAD       6
#define DOING_UPLOAD    7

#ifdef SUB_FILE_SIZE
    #define SUBFILE_WRITE  , ucSubFileInProgress
    #define SUB_FILE_ON    ,SUB_FILE_TYPE
#else
    #define SUBFILE_WRITE
    #define SUB_FILE_ON
#endif

#if !defined UT_FTP_PATH_LENGTH                                          // {30}
    #define UT_FTP_PATH_LENGTH    0                                      // if the user has not specified a path buffer length the PWD command will always return "/" - therefore this string length should be allocated to allow the deepest directory to be displayed
#endif

/* =================================================================== */
/*                      local structure definitions                    */
/* =================================================================== */


typedef struct stTCP_FTP_MESSAGE                                         // definition of a data frame structure
{
    TCP_HEADER     tTCP_Header;                                          // reserve header space
    unsigned char  ucTCP_Message[FTP_DATA_BUFFER_LENGTH];                // space for content
} TCP_FTP_MESSAGE;


/* =================================================================== */
/*                 local function prototype declarations               */
/* =================================================================== */

static int  fnFTPListener(USOCKET cSocket, unsigned char ucEvent, unsigned char *ucIp_Data, unsigned short usPortLen);
static int  fnFTP_Data_Listener(USOCKET cSocket, unsigned char ucEvent, unsigned char *ucIp_Data, unsigned short usPortLen);
static int  fnGetDataPort(CHAR *ucIP_Port, int iIPv6);
static signed short fnSendFTP(unsigned char ucMsg);


/* =================================================================== */
/*                             constants                               */
/* =================================================================== */


#ifdef SUPPORT_MIME_IDENTIFIER
   static unsigned char ucMimeType;
#endif

#ifdef FTP_UTFAT                                                         // {17}
    static UTDIRECTORY *ptr_utDirectory = 0;                             // directory object for a single user
    static UTLISTDIRECTORY utListDirectory;                              // list directory object for a single user
    static UTLISTDIRECTORY utListLastDirectory;                          // backup in case of need to repeat
    static FILE_LISTING fileList = {0};
    static UTFILE utFile = {0};
#endif

static const CHAR   cFTPServerReady[]     = FTP_WELCOME_RESPONSE;
static const CHAR   cFTPBadPass[]         = "530 Log BAD.\r\n";
static const CHAR   cFTPEnterPass[]       = "331 Enter pass.\r\n";
static const CHAR   cFTPloginSuccessful[] = "230 Log OK.\r\n";
static const CHAR   cFTPUnknownCommand[]  = "500 What?.\r\n";
static const CHAR   cFTPType[]            = "215 UNIX Type: L8\r\n";     // pretend to be UNIX...
static const CHAR   cFTPDenied[]          = "550 Denied.\r\n";
static const CHAR   cFTPDir[]             = "257 \x22/\x22\r\n";         // path name
static const CHAR   cFTPOK[]              = "200 OK.\r\n";
static const CHAR   cFTPDirChanged[]      = "250 Dir changed.\r\n";
static const CHAR   cFTP_Data[]           = "150 Data.\r\n";
static const CHAR   cFTP_DIR_OK[]         = "226 OK.\r\n";
static const CHAR   cFTPTerminating[]     = "221 Bye\r\n";
#ifndef FTP_PASV_SUPPORT
    static const CHAR cFTP_Not_Supported[]= "502 No do.\r\n";
#endif
#ifdef FTP_VERIFY_DATA_PORT
    static const CHAR cFTP_Bad_Port[]     = "425 Bad data port\r\n";     // {4}
#endif
#ifdef FTP_SUPPORTS_DELETE                                               // {9}
    static const CHAR cFTPDelOK[]         = "250 Del OK.\r\n";
#endif
#ifdef FTP_UTFAT                                                         // {17}
    static const CHAR cFTP_Rename_OK[]    = "350 Rdy\r\n";
    static const CHAR cFTP_Rename_Success[] = "250 OK\r\n";
#endif
static const CHAR cFTP_DIR[]              = "-r Empty\r\n";              // we display this when a directory is empty {7}

/* =================================================================== */
/*                      local variable definitions                     */
/* =================================================================== */

#ifdef EXTENDED_UFILESYSTEM                                              // {23}
    typedef unsigned short  MAX_FILES;                                   // up to 64k files possible
#else
    typedef unsigned char   MAX_FILES;                                   // up to 256 files possible
#endif
static MAX_FILES            LastFrameFileCount;                          // {23} total number of files displayed before the last frame
static unsigned short       usFTP_DataPort;                              // data port number
static unsigned char        ucFTP_state = FTP_STATE_CLOSED;
static USOCKET              FTP_TCP_socket = -1;
static USOCKET              FTP_TCP_Data_socket = -1;
static unsigned char        ucFTP_action;
#if defined USE_IPV6                                                     // {24}
    static unsigned long    ulIP_flags = 0;
    static unsigned char    ipFTP_data[IPV6_LENGTH];                     // data port IP address
#else
    #define ulIP_flags      0
    static unsigned char    ipFTP_data[IPV4_LENGTH];                     // data port IP address
#endif
static unsigned char        ucLastControl = MSG_DO_NOTHING;
static unsigned char        ucControlQueue = MSG_DO_NOTHING;
static MAX_FILE_SYSTEM_OFFSET FileOffset;
#ifdef FTP_SUPPORTS_DOWNLOAD
    static unsigned short   usLastSentSize;
#endif
static MAX_FILES            FilesSent;
static MEMORY_RANGE_POINTER ptrFile;
#ifdef SUB_FILE_SIZE
    static unsigned char    ucSubFileInProgress = 0;
#endif
#ifdef FTP_USER_LOGIN
    static signed char      cPasswordAccess;                             // {3}
    static unsigned char    ucFTP_mode;
#endif

#ifdef FTP_PASV_SUPPORT
    #define MODE_ACTIVE_FTP   0x00
    #define MODE_PASV         0x01
    #define DATA_CONNECTED    0x02
    static unsigned char    ucMode = MODE_ACTIVE_FTP;
#endif

#ifdef FTP_DATA_WINDOWS                                                  // {12}
    extern TCP_CONTROL *present_tcp;                                     // global pointer to present tcp socket's control structure
#endif
#ifdef _WINDOWS
    extern int user_files_not_in_code;                                   // {15}
#endif

#ifdef INTERNAL_USER_FILES                                               // {13}
    extern USER_FILE *ptrUserFiles;                                      // global pointer to user file set
#endif

extern void fnStartFtp(unsigned short usFTPTimeout, unsigned char ucFTP_operating_mode) // {5}
{
    if (FTP_TCP_socket != -1) {
        return;
    }
#ifdef FTP_UTFAT                                                         // {17}
    if (ptr_utDirectory == 0) {
        ptr_utDirectory = utAllocateDirectory(DISK_D, UT_FTP_PATH_LENGTH); // {30} allocate a directory for use by this module associated with D: - no path string
        if (!(ucFTP_operating_mode & FTP_UTFAT_OFF)) {                   // {25}
            utServer(0, UTFAT_FTP_SERVER_ON);                            // enable ftp server operation with utFAT
        }
    }
#endif
    FTP_TCP_socket = fnGetTCP_Socket(TOS_MINIMISE_DELAY, usFTPTimeout, fnFTPListener); // get TCP Socket and listen on FTP control port
    fnTCP_Listen(FTP_TCP_socket, FTP_CONTROL_PORT, 0);
    ucFTP_state = FTP_STATE_CLOSED;                                      // ensure we start in closed state
#ifdef FTP_USER_LOGIN
    ucFTP_mode = ucFTP_operating_mode;                                   // save operating mode
#endif
    FTP_TCP_Data_socket = fnGetTCP_Socket(TOS_MAXIMISE_THROUGHPUT, TCP_DEFAULT_TIMEOUT, fnFTP_Data_Listener); // reserve FTP data socket
}

extern void fnStopFtp(void)
{
    fnReleaseTCP_Socket(FTP_TCP_socket);                                 // we close the sockets so that FTP is effectively dead.
    fnReleaseTCP_Socket(FTP_TCP_Data_socket);
    FTP_TCP_socket = -1;
}


static unsigned char fnGetQueue(void)
{
    unsigned char ucMsg;
    if (ucLastControl != MSG_DO_NOTHING) {                               // if FTP is still waiting for an ACK don't release anything from queue
        return MSG_DO_NOTHING;
    }
    ucMsg = ucControlQueue;
    ucControlQueue = MSG_DO_NOTHING;
    return ucMsg;
}


// The data socket generates some messages for the control socket. Since the data socket has no information
// about the state of the control socket it uses a small queue to send messages (only one place in queue)
//
static void fnQueueSendFTP(unsigned char ucControlMsg)
{
    if (ucLastControl == MSG_DO_NOTHING) {                               // if the control socket is not waiting for an ack it can send the message immediately
        fnSendFTP(ucControlMsg);                                         // immediately send control message
        ucControlQueue = MSG_DO_NOTHING;                                 // nothing else in the queue
    }
    else {
        ucControlQueue = ucControlMsg;                                   // we queue the message to be sent once the control socket can
    }
}

#if defined MANAGED_FILES && defined MANAGED_FTP_DELETES                 // {20}
static void fnDelete_terminated(int iHandle, int iResult)
{
    fnSendFTP(MSG_DEL_OK);                                               // delete terminated
}
#endif

// Set passive mode if this is supported
//
static int fnPreparePassive(int iIPv6)
{
#if defined FTP_PASV_SUPPORT
    if (MODE_PASV & ucMode) {
        fnTCP_close(FTP_TCP_socket);
        if (DATA_CONNECTED & ucMode) {
            fnTCP_close(FTP_TCP_Data_socket);
        }
        ucMode = 0;
        return APP_REJECT_DATA;                                          // we don't allow multiple data ports
    }
    usFTP_DataPort = fnGetFreeTCP_Port();                                // get a port to listen on
    ucMode = MODE_PASV;                                                  // move to passive mode
    fnTCP_Listen(FTP_TCP_Data_socket, usFTP_DataPort, 0);                // start listening
    #if defined USE_IPV6
    if (iIPv6 != 0) {
        return (fnSendFTP(MSG_EPSV_OK));                                 // IPv6 response
    }
    else {
        return (fnSendFTP(MSG_PASV_OK));                                 // IPv4 response
    }
    #else
    return (fnSendFTP(MSG_PASV_OK));
    #endif
#else
     return (fnSendFTP(MSG_NOT_SUPPORTED));
#endif
}


// Local listener to TCP FTP command port
//
static int fnFTPListener(USOCKET Socket, unsigned char ucEvent, unsigned char *ucIp_Data, unsigned short usPortLen)
{
    unsigned short usNextData;

    if (_TCP_SOCKET_MASK(Socket) != _TCP_SOCKET_MASK(FTP_TCP_socket)) {  // {29}
        return APP_REJECT;                                               // ignore if not our socket handle
    }

    switch (ucEvent) {
    case TCP_EVENT_CONNECTED:
#ifdef FTP_UTFAT                                                         // {17}
      //ptr_utDirectory->usDirectoryFlags &= ~UTDIR_VALID;               // ensure reset on each new connection
      //utOpenDirectory(FTP_ROOT, ptr_utDirectory);                      // open the FTP root directory of disk D on connection (if no disk or no directory this fails and the FTP server falls back to other file systems)
        utServer(ptr_utDirectory, UTFAT_FTP_SERVER_ROOT_RESET);          // {25} if the ftp root exists and ftp server access is allowed
#endif
        ucFTP_state = FTP_STATE_CONNECTED;                               // we send standard response 220 followed by a message
        ucLastControl = MSG_DO_NOTHING;
        return (fnSendFTP(MSG_SERVER_READY));

    case TCP_EVENT_ACK:
        ucLastControl = MSG_DO_NOTHING;
        if (ucFTP_action != DO_LIST) {
//#if defined _CLOSE_ERROR && defined FTP_USER_LOGIN                     // {27}
            if (FTP_STATE_PREPARE_CLOSE == ucFTP_state) {                // {16}
                fnTCP_close(Socket);
                ucFTP_state = FTP_STATE_CONNECTED;
                return APP_REQUEST_CLOSE;
            }
//#endif
            fnSendFTP(fnGetQueue());                                     // if there are queued command, send next one
        }
        break;

    case TCP_EVENT_ABORT:
        // fall through intentional
    case TCP_EVENT_CLOSED:
        ucFTP_state = FTP_STATE_CLOSED;
        ucFTP_action = DATA_INACTIVE;
        fnTCP_Listen(FTP_TCP_socket, FTP_CONTROL_PORT, 0);               // go back to listening state
        break;

    case TCP_EVENT_DATA:                                                 // we have new receive data
        if ((FTP_STATE_CONNECTED == ucFTP_state) && ((usNextData = uStrEquiv((const CHAR *)ucIp_Data, "USER ")) != 0)) {
            usPortLen -= usNextData;
            ucIp_Data += usNextData;
#ifdef FTP_USER_LOGIN
            if (ucFTP_mode & FTP_AUTHENTICATE) {
                cPasswordAccess = (signed char)fnVerifyUser((CHAR *)ucIp_Data, (DO_CHECK_USER_NAME | FTP_PASS_CHECK)); // {3}
            }
#endif
            ucFTP_state = FTP_STATE_LOGIN;                               // we always accept the user name but may reject after password check
            return (fnSendFTP(MSG_ENTER_PASS));                          // request password
        }
        else if ((FTP_STATE_LOGIN == ucFTP_state) && ((usNextData = uStrEquiv((const CHAR *)ucIp_Data, "PASS ")) != 0)) {
#ifdef FTP_USER_LOGIN                                                    // {3}
            if (ucFTP_mode & FTP_AUTHENTICATE) {
                usPortLen -= usNextData;                                 // we verify that the password matches that what we are looking for
                ucIp_Data += usNextData;
                cPasswordAccess |= (signed char)fnVerifyUser((CHAR *)ucIp_Data, (DO_CHECK_PASSWORD | FTP_PASS_CHECK)); // {3}
                if (cPasswordAccess != 0) {
    #ifdef _CLOSE_ERROR
                    ucFTP_state = FTP_STATE_PREPARE_CLOSE;               // {16}
    #else
                    ucFTP_state = FTP_STATE_CONNECTED;
    #endif
                    return (fnSendFTP(MSG_LOG_FAILED));                  // send bad password which causes the client to close
                }
            }
#endif
            ucFTP_state = FTP_STATE_ACTIVE_USER;                         // we accept any old password...
            return (fnSendFTP(MSG_LOG_SUCCESS));
        }
        else if (FTP_STATE_ACTIVE_USER == ucFTP_state) {
#ifdef FTP_UTFAT
            int iCmpLen;                                                 // {28}
#endif
            if (uStrEquiv((const CHAR *)ucIp_Data, "syst")) {
                return (fnSendFTP(MSG_FTP_TYPE));
            }
            else if (uStrEquiv((const CHAR *)ucIp_Data, "site")) {
                return (fnSendFTP(MSG_FTP_DENIED));
            }
            else if (uStrEquiv((const CHAR *)ucIp_Data, "PWD")) {
                return (fnSendFTP(MSG_FTP_DIR));
            }
            else if (uStrEquiv((const CHAR *)ucIp_Data, "PASV")) {       // client requests passive mode of operation
                return (fnPreparePassive(0));
            }
#if defined USE_IPV6                                                     // {24}
            else if (uStrEquiv((const CHAR *)ucIp_Data, "EPSV")) {       // client requests passive IPv6 operation
                return (fnPreparePassive(1));
            }
#endif
            else if (uStrEquiv((const CHAR *)ucIp_Data, "noop")) {
                return (fnSendFTP(MSG_FTP_OK));
            }
            else if (uStrEquiv((const CHAR *)ucIp_Data, "CWD")) {
#if defined FTP_UTFAT                                                    // {17}
                if (ptr_utDirectory->usDirectoryFlags & UTDIR_VALID) {
                    utListDirectory.ptr_utDirObject = ptr_utDirectory;   // the list directory is always referenced to the main directory object
                    if (utChangeDirectory((const CHAR *)(ucIp_Data + 4), ptr_utDirectory) != UTFAT_SUCCESS) { // change the directory location
                        return (fnSendFTP(MSG_FTP_DENIED));              // invalid path
                    }
                }
#endif
                return (fnSendFTP(MSG_DIR_CHANGED));
            }
            else if ((usNextData = uStrEquiv((const CHAR *)ucIp_Data, "TYPE ")) != 0) { // type command can be ASCII (A) or Image (I)
                return (fnSendFTP(MSG_FTP_OK));
            }
            else if ((usNextData = uStrEquiv((const CHAR *)ucIp_Data, "PORT ")) != 0) { // client defines port to be used for active data transfer
                return (fnGetDataPort((CHAR *)(ucIp_Data + usNextData), 0));
            }
#if defined USE_IPV6                                                     // {24}
            else if ((usNextData = uStrEquiv((const CHAR *)ucIp_Data, "EPRT ")) != 0) { // client defines port to be used for active data transfer
                return (fnGetDataPort((CHAR *)(ucIp_Data + usNextData), 1));
            }
#endif
            else if (uStrEquiv((const CHAR *)ucIp_Data, "LIST")) {
#ifdef FTP_UTFAT                                                         // {17}
                if (ptr_utDirectory->usDirectoryFlags & UTDIR_VALID) {
                    utListDirectory.ptr_utDirObject = ptr_utDirectory;   // the list directory is always referenced to the main directory object
                    if (utLocateDirectory((const CHAR *)(ucIp_Data + 5), &utListDirectory) != UTFAT_SUCCESS) { // set to present directory
                        return (fnSendFTP(MSG_FTP_DENIED));              // invalid path
                    }
                    fileList.usMaxItems = 0xffff;                        // the maximum number of entries will be limited by the TCP buffer space
                }
#endif
#ifdef FTP_PASV_SUPPORT
                if (MODE_PASV & ucMode) {
                    int iRtn = (fnSendFTP(MSG_FTP_DATA) > 0);
                    LastFrameFileCount = 0;                              // {23}
                    FilesSent = 0;
                    if (DATA_CONNECTED & ucMode) {
                        fnSendFTP(MSG_DIR);                              // return the data response [FTP-Data] 
                    }
                    ucFTP_action = DO_LIST;
                    return iRtn;
                }
#endif
                _TCP_SOCKET_MASK_ASSIGN(FTP_TCP_Data_socket);            // {29}
                FTP_TCP_Data_socket |= (FTP_TCP_socket & ~(SOCKET_NUMBER_MASK)); // allow the data socket to inherit the network, interface and VLAN properties if the command socket
                if (fnTCP_Connect(FTP_TCP_Data_socket, ipFTP_data, usFTP_DataPort, FTP_DATA_PORT, ulIP_flags) >= 0) {
                   ucFTP_action = DO_LIST;
                }
                else {
                    return (fnSendFTP(MSG_FTP_DENIED));                  // presently not possible to open data connection...
                }
                                                                         // we don't send an ACK here but wait for the data link establishment to do it
            }
            else if (uStrEquiv((const CHAR *)ucIp_Data, "STOR ")) {
                _TCP_SOCKET_MASK_ASSIGN(FTP_TCP_Data_socket);            // {29}
                FTP_TCP_Data_socket |= (FTP_TCP_socket & ~(SOCKET_NUMBER_MASK)); // allow the data socket to inherit the network, interface and VLAN properties if the command socket
#ifdef FTP_PASV_SUPPORT
                if ((MODE_PASV & ucMode) || (fnTCP_Connect(FTP_TCP_Data_socket, ipFTP_data, usFTP_DataPort, FTP_DATA_PORT, ulIP_flags) >= 0))
#else
                if (fnTCP_Connect(FTP_TCP_Data_socket, ipFTP_data, usFTP_DataPort, FTP_DATA_PORT, ulIP_flags) >= 0) 
#endif
                {
#ifdef FTP_UTFAT                                                         // {17}
                    if (ptr_utDirectory->usDirectoryFlags & UTDIR_VALID) {
                        utFile.ptr_utDirObject = ptr_utDirectory;
                        if (utOpenFile((const CHAR *)(ucIp_Data + 5), &utFile, (UTFAT_OPEN_FOR_WRITE | UTFAT_CREATE | UTFAT_TRUNCATE)) != UTFAT_PATH_IS_FILE) { // open a file referenced to the directory object
                            return (fnSendFTP(MSG_FTP_DENIED));          // file can not be created, overwritten
                        }
                    }
                    else {
#endif
                        ptrFile = uOpenFile((CHAR *)(ucIp_Data + 5));    // get file pointer (to new file or file to overwrite)
#ifdef SUPPORT_MIME_IDENTIFIER
                        ucMimeType = fnGetMimeType((CHAR*)(ucIp_Data + 5)); // get the type of file being saved
#endif
#ifdef SUB_FILE_SIZE
                        ucSubFileInProgress = fnGetFileType((CHAR*)(ucIp_Data + 5)); // get file characteristics so that it is later handled correctly
#endif
#ifdef FTP_UTFAT                                                         // {17}
                    }
#endif
#ifdef FTP_PASV_SUPPORT
                    if (MODE_PASV & ucMode)  { 
                        ucFTP_action = DOING_SAVE;
                        return (fnSendFTP(MSG_FTP_DATA));
                    }
#endif
                    ucFTP_action = DO_SAVE;                              // mark that we are doing save
                }
                else {
                    return (fnSendFTP(MSG_FTP_DENIED));                  // presently not possible to open data connection...
                }
                                                                         // we don't send an ACK here but instead wait for the data link establishment to do it
            }
#ifdef FTP_SUPPORTS_DELETE
            else if (uStrEquiv((const CHAR *)ucIp_Data, "DELE ")) {
    #ifdef FTP_UTFAT                                                     // {17}
                if (ptr_utDirectory->usDirectoryFlags & UTDIR_VALID) {
                    if (utDeleteFile((const CHAR *)(ucIp_Data + 5), ptr_utDirectory) != UTFAT_SUCCESS) {
                        return (fnSendFTP(MSG_FTP_DENIED));
                    }
                    else {
                        return (fnSendFTP(MSG_DEL_OK));
                    }
                }
    #endif
    #ifdef FTP_WILDCARD_DEL                                              // {11}
                if (uStrEquiv((const CHAR *)(ucIp_Data + 5), "*.*")) {   // wildcard delete
        #if defined MANAGED_FILES && defined MANAGED_FTP_DELETES         // {20}
		            MANAGED_MEMORY_AREA_BLOCK memory_area;
		            memory_area.ptrStart = uFILE_SYSTEM_START;           // start address of the area to be deleted
		            memory_area.size = FILE_SYSTEM_SIZE;                 // size of area to be deleted
		            memory_area.period = 0;                              // page delete once every scheduling pass
		            memory_area.ucParameters = (AUTO_CLOSE | AUTO_DELETE); // on termination automatically close the file
		            memory_area.fileOperationCallback = fnDelete_terminated; // callback on completion
		            if (uOpenManagedFile(&memory_area, OWN_TASK, (MANAGED_MEMORY_AREA | MANAGED_DELETE)) < 0) { // open the file which will be automatically deleted and closed
	                    return (fnSendFTP(MSG_FTP_DENIED));
		            }
					return APP_ACCEPT;                                   // return OK after the delete has been successful
        #else
            #ifdef ACTIVE_FILE_SYSTEM
                #ifdef EXTENDED_UFILESYSTEM                              // {21}
                    uFileErase(uFILE_SYSTEM_START, (MAX_FILE_LENGTH)(fnGetEndOf_uFileSystem() - uFILE_SYSTEM_START));
                #else
                    uFileErase(uFILE_SYSTEM_START, (MAX_FILE_LENGTH)FILE_SYSTEM_SIZE);
                #endif
            #endif
                    FileOffset = 0;
        #endif
                }
                else {
        #if defined MANAGED_FILES && defined MANAGED_FTP_DELETES         // {20}
		            MANAGED_MEMORY_AREA_BLOCK memory_area;
		            memory_area.ptrStart = uOpenFile((CHAR*)(ucIp_Data + 5));// start address of the area to be deleted
		            memory_area.size = uGetFileLength(memory_area.ptrStart); // size of area to be deleted
		            memory_area.period = 0;                              // page delete once every scheduling pass
		            memory_area.ucParameters = (AUTO_CLOSE | AUTO_DELETE); // on termination automatically close the file
		            memory_area.fileOperationCallback = fnDelete_terminated; // callback on completion
		            if (uOpenManagedFile(&memory_area, OWN_TASK, (MANAGED_MEMORY_AREA | MANAGED_DELETE)) < 0) { // open the file which will be automatically deleted and closed
                        return (fnSendFTP(MSG_FTP_DENIED));
		            }
                    return APP_ACCEPT;                                   // return OK after the delete has been successful
        #else
                    MEMORY_RANGE_POINTER ptrFile = uOpenFile((CHAR*)(ucIp_Data + 5));
                    MAX_FILE_LENGTH Length = uGetFileLength(ptrFile);
            #ifdef INTERNAL_USER_FILES                                   // {13}
                #ifndef ACTIVE_FILE_SYSTEM
                    return (fnSendFTP(MSG_FTP_DENIED));                  // deny delete of internal files
                #else
                    if ((Length == 0) || (ptrFile < uFILE_SYSTEM_START)
                    #ifdef _WINDOWS
                        || (ptrFile >= fnGetEndOf_uFileSystem())
                    #endif
                        ) {
                        return (fnSendFTP(MSG_FTP_DENIED));              // deny delete of internal files
                    }
                #endif
            #endif
            #ifdef ACTIVE_FILE_SYSTEM
                    uFileErase(ptrFile, (MAX_FILE_LENGTH)(Length + FILE_HEADER));
                    FileOffset = ptrFile - uFILE_SYSTEM_START;
            #endif
        #endif
                }
                return (fnSendFTP(MSG_DEL_OK));                          // we always respond with OK
    #else
                MEMORY_RANGE_POINTER ptrFile = uOpenFile((CHAR*)(ucIp_Data + 5));
                MAX_FILE_LENGTH Length = uGetFileLength(ptrFile);
        #ifdef ACTIVE_FILE_SYSTEM
                uFileErase(ptrFile, (MAX_FILE_LENGTH)(Length + FILE_HEADER));
        #endif
                FileOffset = ptrFile - uFILE_SYSTEM_START;
                return (fnSendFTP(MSG_DEL_OK));                          // we always respond with OK
    #endif
            }
#endif

#ifdef FTP_SUPPORTS_DOWNLOAD 
            else if (uStrEquiv((const CHAR *)ucIp_Data, "SIZE ")) {
    #ifdef FTP_UTFAT                                                     // {17}
                if (ptr_utDirectory->usDirectoryFlags & UTDIR_VALID) {
                    if (utOpenFile((const CHAR *)(ucIp_Data + 5), &utFile, UTFAT_OPEN_FOR_READ) != UTFAT_PATH_IS_FILE) { // open a file referenced to the directory object
                        return (fnSendFTP(MSG_FTP_DENIED));              // file not found
                    }
                    FileOffset = utFile.ulFileSize;
                }
                else {
    #endif
    #ifdef FTP_PASV_SUPPORT
                    if (*(ucIp_Data + 5) == '/') {                       // if no file given, return that it is not valid
                          return (fnSendFTP(MSG_FTP_DENIED));
                    }
    #endif
    #ifdef ACTIVE_FILE_SYSTEM
                    FileOffset = (uOpenFile((CHAR*)(ucIp_Data + 5)) - uFILE_SYSTEM_START);
    #endif
    #ifdef FTP_UTFAT                                                     // {17}
                }
    #endif
                return (fnSendFTP(MSG_FILE_LENGTH));                     // return the size of the requested data
            }
            else if (uStrEquiv((const CHAR *)ucIp_Data, "RETR ")) {
                _TCP_SOCKET_MASK_ASSIGN(FTP_TCP_Data_socket);            // {29}
                FTP_TCP_Data_socket |= (FTP_TCP_socket & ~(SOCKET_NUMBER_MASK)); // allow the data socket to inherit the network, interface and VLAN properties if the command socket
    #ifdef FTP_UTFAT                                                     // {17}
                if (ptr_utDirectory->usDirectoryFlags & UTDIR_VALID) {
                    utFile.ptr_utDirObject = ptr_utDirectory;
                    if (utOpenFile((const CHAR *)(ucIp_Data + 5), &utFile, UTFAT_OPEN_FOR_READ) != UTFAT_PATH_IS_FILE) { // open a file referenced to the directory opbject
        #ifdef FTP_PASV_SUPPORT
                        if (DATA_CONNECTED & ucMode) {                   // if we have a data connection waiting to transfer, close it
                            fnTCP_close(FTP_TCP_Data_socket);
                        }
                        ucMode = 0;
        #endif
                        return (fnSendFTP(MSG_FTP_DENIED));              // file not found
                    }
                    FileOffset = 0;                                      // start at beginning of file
                    if (fnTCP_Connect(FTP_TCP_Data_socket, ipFTP_data, usFTP_DataPort, FTP_DATA_PORT, ulIP_flags) >= 0) {
                        ucFTP_action = DO_UPLOAD;
                    }
        #ifdef FTP_PASV_SUPPORT
                    else if (MODE_PASV & ucMode) { 
                        fnSendFTP(MSG_UPLOAD);                           // immediately return the data [FTP-Data] - data connection alread established
                        ucFTP_action = DOING_UPLOAD;
                    }
        #endif
                    else {
                        return (fnSendFTP(MSG_FTP_DENIED));              // presently not possible to open data connection...
                    }
                    break;
                }
    #endif
    #ifdef FTP_PASV_SUPPORT
                if (*(ucIp_Data + 5) == '/') {                           // if no file given, return length of first file
                    if (DATA_CONNECTED & ucMode) {                       // if we have a data connection waiting to transfer, close it
                        fnTCP_close(FTP_TCP_Data_socket);
                    }
                    ucMode = 0;
                    return (fnSendFTP(MSG_FTP_DENIED));
                }
    #endif
                ptrFile = uOpenFile((CHAR *)(ucIp_Data + 5));            // get file pointer
                if ((uGetFileLength(ptrFile)) && (fnTCP_Connect(FTP_TCP_Data_socket, ipFTP_data, usFTP_DataPort, FTP_DATA_PORT, ulIP_flags) >= 0)) {
                    ucFTP_action = DO_UPLOAD;
                    FileOffset = 0;                                      // start at beginning of file
                }
                else {
    #ifdef FTP_PASV_SUPPORT
                    if (MODE_PASV & ucMode) { 
                        FileOffset = 0;                                  // start at beginning of file
                        fnSendFTP(MSG_UPLOAD);                           // immediately return the data [FTP-Data] - data connection alread established
                        ucFTP_action = DOING_UPLOAD;
                        break;
                    }
    #endif
                   return (fnSendFTP(MSG_FTP_DENIED));                   // either no file or presently not possible to open data connection...
                }
            }
#endif
#ifdef FTP_UTFAT                                                         // {17}
            else if ((ptr_utDirectory->usDirectoryFlags & UTDIR_VALID) && (uStrEquiv((const CHAR *)ucIp_Data, "RNFR "))) {
                utFile.ptr_utDirObject = ptr_utDirectory;
                if (utOpenFile((const CHAR *)(ucIp_Data + 5), &utFile, UTFAT_OPEN_FOR_RENAME) < UTFAT_SUCCESS) { // {19}
                   return (fnSendFTP(MSG_FTP_DENIED));
                }
                else {
                   return (fnSendFTP(MSG_FTP_READY_FOR_RENAME));
                }
            }
            else if ((ptr_utDirectory->usDirectoryFlags & UTDIR_VALID) && (uStrEquiv((const CHAR *)ucIp_Data, "RNTO "))) {
                if (utRenameFile((const CHAR *)(ucIp_Data + 5), &utFile) != UTFAT_SUCCESS) {
                   return (fnSendFTP(MSG_FTP_DENIED));
                }
                else {
                   return (fnSendFTP(MSG_FTP_RENAME_SUCCESSFUL));
                }
            }
            else if ((ptr_utDirectory->usDirectoryFlags & UTDIR_VALID) && (((iCmpLen = uStrEquiv((const CHAR *)ucIp_Data, "XMKD ")) != 0) || ((iCmpLen = uStrEquiv((const CHAR *)ucIp_Data, "MKD ")) != 0))) { // {28}
                if (utMakeDirectory((const CHAR *)(ucIp_Data + iCmpLen), ptr_utDirectory) == UTFAT_SUCCESS) { // {28}
                    return (fnSendFTP(MSG_FTP_DIR));
                }
                return (fnSendFTP(MSG_FTP_DENIED));
            }
#endif
            else if (uStrEquiv((const CHAR *)ucIp_Data, "QUIT")) {
                return (fnSendFTP(MSG_FTP_QUITTING));                    // signal that we are terminating {26} add return
            }
            else {
                return (fnSendFTP(MSG_FTP_UNKNOWN));                     // we don't recognise this command so just say we don't know it
            }
        }
        break;

    case TCP_EVENT_REGENERATE:                                           // we must repeat the last control buffer we sent
        return (fnSendFTP(MSG_REPEAT_CONTROL));

    case TCP_EVENT_CONREQ:
        if (ucFTP_state != FTP_STATE_CLOSED) {
             return APP_REJECT;
        }
        FTP_TCP_socket = Socket;                                         // {29}
        break;

    case TCP_EVENT_CLOSE:
        break;

    default:
        break;                                                           // {29} return -1;
    }
    return APP_ACCEPT;
}



// Local listener to TCP FTP data port
//
static int fnFTP_Data_Listener(USOCKET cSocket, unsigned char ucEvent, unsigned char *ucIp_Data, unsigned short usPortLen)
{
    if (_TCP_SOCKET_MASK(cSocket) != _TCP_SOCKET_MASK(FTP_TCP_Data_socket)) { // {29}
        return APP_REJECT;                                                // ignore if not our socket handle
    }

#ifdef DATA_PORT_TRIGGERS_CONTROL                                        // {8}
    fnTCP_Activity(FTP_TCP_socket);                                      // retrigger control socket on data socket activity
#endif

    switch (ucEvent) {
    case TCP_EVENT_CONNECTED:
#ifdef FTP_PASV_SUPPORT
        if (MODE_PASV & ucMode) {
            ucMode |= DATA_CONNECTED;                                    // mark that we are connected
        }
        else {
            fnQueueSendFTP(MSG_FTP_DATA);                                // acknowledge command since data connection has been established
        }
#else
        fnQueueSendFTP(MSG_FTP_DATA);                                    // acknowledge command since data connection has been established
#endif
        if (DO_LIST == ucFTP_action) {                                   // we must send a directory listing
            LastFrameFileCount = 0;                                      // {23}
            FilesSent = 0;
            if (!fnSendFTP(MSG_DIR)) {                                   // now return the data [FTP-Data] {18}
                fnQueueSendFTP(MSG_DIR_OK);
                ucFTP_action = DATA_INACTIVE;
                fnTCP_close(FTP_TCP_Data_socket);
                return APP_REQUEST_CLOSE;
            }
        }
        else if (DO_SAVE == ucFTP_action) {                              // we must receive a file and save it
            ucFTP_action = DOING_SAVE;
        }
#ifdef FTP_SUPPORTS_DOWNLOAD
        else if (DO_UPLOAD == ucFTP_action) {
            fnSendFTP(MSG_UPLOAD);                                       // now return the data [FTP-Data]
            ucFTP_action = DOING_UPLOAD;
        }
#endif
        break;

    case TCP_EVENT_ACK:                                                  // all outstanding data transmission acknowledged
        if (DO_LIST == ucFTP_action) {
            if (!(fnSendFTP(MSG_DIR))) {                                 // continue sending file names or terminate
                fnQueueSendFTP(MSG_DIR_OK);
                ucFTP_action = DATA_INACTIVE;
                fnTCP_close(FTP_TCP_Data_socket);
                return APP_REQUEST_CLOSE;
            }
        }
#ifdef FTP_SUPPORTS_DOWNLOAD
        else if (DOING_UPLOAD == ucFTP_action) {
    #ifdef FTP_DATA_WINDOWS                                              // {12}
            present_tcp->usOpenCnt = 0;                                  // no outstanding data to be acked
    #endif
            FileOffset += usLastSentSize;                                // block was successfully received so we can send subsequent ones
            if (!fnSendFTP(MSG_UPLOAD)) {                                // send next block
                fnQueueSendFTP(MSG_DIR_OK);                              // completely transmitted
                ucFTP_action = DATA_INACTIVE;
                fnTCP_close(FTP_TCP_Data_socket);
                return APP_REQUEST_CLOSE;
            }
        }
#endif
        break;

    case TCP_EVENT_CLOSED:
        if (DOING_SAVE == ucFTP_action) {
#ifdef FTP_UTFAT                                                         // {17}
            if (ptr_utDirectory->usDirectoryFlags & UTDIR_VALID) {
                utCloseFile(&utFile);
            }
            else {
#endif
#ifdef ACTIVE_FILE_SYSTEM
    #ifdef SUPPORT_MIME_IDENTIFIER
                uFileCloseMime(ptrFile, &ucMimeType);                    // this will cause the file length and type to be written in the file
    #else
                uFileClose(ptrFile);                                     // this will cause the file length to be written in the file
    #endif
#endif
#ifdef FTP_UTFAT                                                         // {17}
            }
#endif
            fnQueueSendFTP(MSG_DIR_OK);
            ucFTP_action = DATA_INACTIVE;
        }
        else {
            fnSendFTP(fnGetQueue());                                     // data socket has closed so kick any waiting commands
        }
#ifdef FTP_PASV_SUPPORT
        ucMode = MODE_ACTIVE_FTP;                                        // go to active mode after data connection has close
#endif
        break;

    case TCP_EVENT_DATA:                                                 // we have new receive data
        if (DOING_SAVE == ucFTP_action) {
#ifdef FTP_UTFAT                                                         // {17}
            if (ptr_utDirectory->usDirectoryFlags & UTDIR_VALID) {
                utWriteFile(&utFile, ucIp_Data, usPortLen);              // it is assumed that this will not fail
                break;
            }
#endif
#ifdef ACTIVE_FILE_SYSTEM
            uFileWrite(ptrFile, ucIp_Data, usPortLen SUBFILE_WRITE);     // save the received data to file. Existing files will automatically be deleted
#endif
        }
        break;

    case TCP_EVENT_REGENERATE:                                           // we must repeat the last data buffer we sent
#ifdef FTP_UTFAT                                                         // {17}
        if (ptr_utDirectory->usDirectoryFlags & UTDIR_VALID) {           // if using SD-card
            if (FilesSent != 0) {
                uMemcpy(&utListDirectory, &utListLastDirectory, sizeof(utListDirectory)); // return to last list position in order to repeat
                fileList.usMaxItems = 0xffff;                            // the maximum number of entries will be limited by the TCP buffer space
            }
            else {
                utSeek(&utFile, -usLastSentSize, UTFAT_SEEK_CUR);                
            }
        }
        else {
            FilesSent = LastFrameFileCount;                              // return listing position to last frame start
        }
#else
        FilesSent = LastFrameFileCount;                                  // return listing position to last frame start
#endif
#ifdef FTP_DATA_WINDOWS
        present_tcp->usOpenCnt = 0;
#endif
        return (fnSendFTP(MSG_REPEAT_DATA));

    case TCP_EVENT_CLOSE:                                                // remote client wants to close
    case TCP_EVENT_ABORT:
        break;

    case TCP_EVENT_CONREQ:
#ifdef FTP_PASV_SUPPORT
        if (MODE_PASV & ucMode) {
            FTP_TCP_Data_socket = cSocket;                               // {29}
            break;                                                       // allow PASV connection
        }
#endif
        return APP_REJECT;                                               // we never accept requests

    default:
        break;                                                           // {29} return -1;
    }
    return APP_ACCEPT;
}

// Extract IP address and data port from the input message
// format 192,168,0,33,4,46 when IPv4 is in operation
// format is |2|2001:470:21:105::10|4234| when IPv6 is in operation
//
static int fnGetDataPort(CHAR *cIP_Port, int iIPv6)                        // {24}
{
    int iCnt = 0;
#if defined USE_IPV6
    CHAR cDelimiter = 0;
    if (iIPv6 != 0) {
        CHAR *cPort = cIP_Port;
        CHAR cDelimiter = *cPort;                                        // delimiter used (range 33..126 but typically '|' [124])
        cPort += 3;
        while (*cPort != cDelimiter) {
            cPort++;
        }
        *cPort = 0;                                                      // replace IPv6 end delimiter with null teminator
        ulIP_flags = TCP_CONNECT_IPV6;                                   // flag which type of connection the data will be transferred over
        cIP_Port = (CHAR *)fnStrIPV6((cIP_Port + 3), ipFTP_data);        // extract the IPv6 address (assume |2| preceeds)
        usFTP_DataPort = 0;
        goto _IPv6_Port;
    }
    else {
        ulIP_flags = 0;
    }
#endif
    cIP_Port = (CHAR *)fnStrIP(cIP_Port, ipFTP_data);                    // extract the IP address

    while (*cIP_Port >= '0') {
        iCnt++;
        cIP_Port++;
    }
    *cIP_Port = '+';                                                     // terminate (we use this due to internet use)
    usFTP_DataPort = (unsigned short)fnDecStrHex(cIP_Port - iCnt);
    usFTP_DataPort <<= 8;
    cIP_Port++;

    iCnt = 0;
#if defined USE_IPV6
_IPv6_Port:
    while ((*cIP_Port >= '0') && (*cIP_Port != cDelimiter)) {
        iCnt++;
        cIP_Port++;
    }
#else
    while (*cIP_Port >= '0') {
        iCnt++;
        cIP_Port++;
    }
#endif
    *cIP_Port = 0;                                                       // terminate
    usFTP_DataPort += (unsigned short)fnDecStrHex(cIP_Port - iCnt);
#ifdef FTP_VERIFY_DATA_PORT                                              // {4}
    if ((iIPv6 == 0) && (!(uMemcmp(cucNullMACIP, ipFTP_data, IPV4_LENGTH)))) { // check for IPv4 address 0.0.0.0
        return (fnSendFTP(MSG_BAD_DATA_CONNECTION));                     // bad data port - this has been seen to happen with FTP from DOS command line
    }
#endif
    return (fnSendFTP(MSG_FTP_OK));
}


#ifdef FTP_PASV_SUPPORT
static unsigned short fnDoGetPasvPort(CHAR *cMessage, int iIPv6)
{
    static const unsigned char ucPasvResponse[] = {'2', '2', '7', ' ', 'P', 'A', 'S', 'V', ' ', 'M', 'O', 'D', 'E', ' ', '('};
    CHAR *ptr = cMessage;
    int i;

    uMemcpy(cMessage, ucPasvResponse, sizeof(ucPasvResponse));
    #if defined USE_IPV6
    if (iIPv6 != 0) {
        cMessage[2] = '9';                                               // modify response code to 229 (extended passive mode entered)
        cMessage[4] = 'E';
        cMessage[5] = 'P';                                               // modify text to EPSV MODE
        cMessage += sizeof(ucPasvResponse);
        *cMessage++ = '|';
        *cMessage++ = '|';
        *cMessage++ = '|';
        cMessage = _fnBufferDec((unsigned long)(usFTP_DataPort), 0, cMessage); // add port
        *cMessage++ = '|';
        *cMessage++ = ')';
    }
    else {
        cMessage += sizeof(ucPasvResponse);
        for (i = 0; i < sizeof(network[DEFAULT_NETWORK].ucOurIP); i++) {
            cMessage = _fnBufferDec((unsigned long)network[DEFAULT_NETWORK].ucOurIP[i], 0, cMessage); // add IP address
            *cMessage++ = ',';
        }
        cMessage = _fnBufferDec((unsigned long)(usFTP_DataPort/256), 0, cMessage); // add port
        *cMessage++ = ',';
        cMessage = _fnBufferDec((unsigned long)((unsigned char)(usFTP_DataPort)), 0, cMessage); // add port
        *cMessage++ = ')';
        *cMessage++ = '.';
    }
    #else
    cMessage += sizeof(ucPasvResponse);
    for (i = 0; i < sizeof(network[DEFAULT_NETWORK].ucOurIP); i++) {
        cMessage = _fnBufferDec((unsigned long)network[DEFAULT_NETWORK].ucOurIP[i], 0, cMessage); // add IP address
        *cMessage++ = ',';
    }
    cMessage = _fnBufferDec((unsigned long)(usFTP_DataPort/256), 0, cMessage); // add port
    *cMessage++ = ',';
    cMessage = _fnBufferDec((unsigned long)((unsigned char)(usFTP_DataPort)), 0, cMessage); // add port
    *cMessage++ = ')';
    *cMessage++ = '.';
    #endif
    *cMessage++ = '\r';
    *cMessage++ = '\n';
    return (unsigned short)(cMessage - ptr);
}
#endif

#ifdef FTP_SUPPORTS_DOWNLOAD
static unsigned short fnDoGetFileLen(CHAR *cMessage)
{
    #ifdef ACTIVE_FILE_SYSTEM
    static const unsigned char ucSizeResponse[] = {'2', '1', '3', ' '};
    CHAR *ptr = cMessage;
    MAX_FILE_LENGTH FileLength = uGetFileLength((uFILE_SYSTEM_START + FileOffset));
    uMemcpy(cMessage, ucSizeResponse, sizeof(ucSizeResponse));
    cMessage += sizeof(ucSizeResponse);
    cMessage = _fnBufferDec(FileLength, 0, cMessage);                    // add file length
    *cMessage++ = '\r';
    *cMessage++ = '\n';
    return (unsigned short)(cMessage - ptr);
    #else
    return 0;
    #endif
}

    #ifdef FTP_UTFAT                                                     // {17}
static unsigned short fnGetFileData(MEMORY_RANGE_POINTER ptrFile, MAX_FILE_SYSTEM_OFFSET FileOffset, unsigned char *ptrBuffer, unsigned short usTxLength)
{
    if ((ptr_utDirectory->usDirectoryFlags & UTDIR_VALID) && (utFile.ulFileSize != 0)) {
        utReadFile(&utFile, ptrBuffer, usTxLength);
        return utFile.usLastReadWriteLength;
    }
    return ((unsigned short)uGetFileData(ptrFile, FileOffset, ptrBuffer, usTxLength)); // when not working with SD-card use standard uFileSystem call
}
    #endif
#endif                                                                   // end FTP_SUPPORTS_DOWNLOAD

#ifdef INTERNAL_USER_FILES                                               // {13}
static USER_FILE *fnNextUserFile(USER_FILE *ptrFiles, MAX_FILE_LENGTH *FileLength, unsigned char *ucMimeType)
{
    while ((ptrFiles != 0) && (ptrFiles->fileName != 0)) {
        if (ptrFiles->ucProperties & FILE_INVISIBLE) {
            ptrFiles++;                                                  // jump the invisible file
            continue;
        }
        *FileLength = ptrFiles->file_length;
        *ucMimeType = ptrFiles->ucMimeType;
        return ptrFiles;
    }
    return 0;                                                            // no more user files
}
#endif

// Create a file listing message - putting the contents directly to buffer
//
static unsigned short fnDoDir(CHAR *cMessage)
{
#ifdef FTP_SUPPORTS_NAME_DISPLAY
    int iFileNameLength;
    #ifdef INTERNAL_USER_FILES                                           // {13}
    USER_FILE *ptrFiles = ptrUserFiles;
    int iNewFileNames = 0;                                               // the file names added by this call
    #endif
    #ifdef EXTENDED_UFILESYSTEM                                          // {21}
    CHAR cFileName[7 + EXTENDED_UFILESYSTEM]; // = {'0', 'x', 'x', 'x', '.', 'H', 'T', 'M', '\r', '\n'};
    #else
    CHAR cFileName[7]; // = {'0', '.', 'H', 'T', 'M', '\r', '\n'}; {1}
    #endif
    MEMORY_RANGE_POINTER ucFileAddress;
    unsigned short usTotalLen = 0;
    CHAR *ptr;
    MAX_FILE_LENGTH FileLength;
    MAX_FILES FileCnt = 0;                                               // count of number of files added in this frame
    #ifdef SUPPORT_MIME_IDENTIFIER
        #define MIME_TYPE , &ucMimeType
        #define UOPENNEXTFILE uOpenNextMimeFile
        unsigned char ucMimeType;
    #else
        #define UOPENNEXTFILE uOpenNextFile
        #define MIME_TYPE
    #endif
    #ifndef EXTENDED_UFILESYSTEM
    uMemcpy(cFileName, "0.HTM\r\n", sizeof(cFileName));                  // {1}{13} removed leading space
    #endif
    #ifdef INTERNAL_USER_FILES                                           // {13}
        #ifdef _WINDOWS
    if (user_files_not_in_code != 0) {                                   // {15}
        ptrFiles = (USER_FILE *)fnGetFlashAdd((unsigned char *)ptrFiles);
    }
        #endif
    if ((ptrFiles = fnNextUserFile(ptrFiles, &FileLength, &ucMimeType)) != 0) {
        ucFileAddress = 0;                                               // first process the user files
    }
    else {
        ucFileAddress = UOPENNEXTFILE(0, &FileLength MIME_TYPE  SUB_FILE_ON);// open the first file in the directory and get its length
    }
    #else
    ucFileAddress = UOPENNEXTFILE(0, &FileLength MIME_TYPE  SUB_FILE_ON);// open the first file in the directory and get its length
    #endif
    while (FileLength) {                                                 // for each file present
        if (FileCnt++ >= FilesSent) {                                    // if this file is valid for this frame
    #ifdef EXTENDED_UFILESYSTEM                                          // {21}
            uMemcpy(cFileName, "0.HTM\r\n", sizeof(cFileName));          // ensure that the default file name is reset before each new listing
    #endif
            usTotalLen += LENGTH_OF_FILE_INFO;                           // fixed length as used by uFileSystem
    #ifdef INTERNAL_USER_FILES                                           // {13}
            if (iNewFileNames >= FILE_NAMES_PER_FTP_FRAME)
    #else
            if (usTotalLen > FTP_DATA_BUFFER_LENGTH)                     // if there is no room to add the next file
    #endif
            {
                LastFrameFileCount = FilesSent;                          // {23} amount of files listed before this frame, in case we need to repeat
                FilesSent += ((FileCnt - FilesSent) - 1);                // the total number of files listed
                return (usTotalLen - LENGTH_OF_FILE_INFO);               // ensure we don't overwrite available space (return the buffer content length to be sent)
            }
            uMemcpy(cMessage, cFileRights, sizeof(cFileRights));         // now add the file rights
    #ifdef INTERNAL_USER_FILES                                           // {13}
            if (ptrFiles != 0) {                                         // internal file - set read-only
                *(cMessage + 2) = '-';
                *(cMessage + 5) = '-';
                *(cMessage + 8) = '-';
            }
            iNewFileNames++;
    #endif
            cMessage += sizeof(cFileRights);
            uMemcpy(cMessage, cFileType, sizeof(cFileType));
            cMessage += sizeof(cFileType);
            ptr = cMessage + LENGTH_OF_FILE_LENGTH;
            cMessage = _fnBufferDec(FileLength, 0, cMessage);            // add file length
            while (ptr > cMessage) {
                ptr--;
                usTotalLen--;
            }
            uMemcpy(cMessage, cFileDate, sizeof(cFileDate));             // add file date
            cMessage += sizeof(cFileDate);
    #ifdef SUPPORT_MIME_IDENTIFIER
            if (ucMimeType > UNKNOWN_MIME) {      
                ucMimeType = UNKNOWN_MIME;
            }
    #endif
    #ifdef INTERNAL_USER_FILES                                           // {13}
            if (ptrFiles != 0) {                                         // this entry is an internal file
                CHAR *ptrStart = cMessage;
        #ifdef _WINDOWS
                if (user_files_not_in_code != 0) {                       // {15}
                    cMessage = uStrcpy(cMessage, fnGetFlashAdd((unsigned char *)ptrFiles->fileName)); // add the file name                    
                }
                else {
                    cMessage = uStrcpy(cMessage, ptrFiles->fileName);    // add the file name
                }
        #else
                cMessage = uStrcpy(cMessage, ptrFiles->fileName);        // add the file name
        #endif
        #ifdef STRING_OPTIMISATION                                       // {14}
                if (ptrFiles->ucProperties & FILE_ADD_EXT) {
                    *cMessage++ = '.';
                    uMemcpy(cMessage, cMimeTable[ucMimeType], 3);
                    cMessage += 3;
                }
                *cMessage++ = '\r';
                *cMessage++ = '\n';
            #ifdef EXTENDED_UFILESYSTEM
                usTotalLen += ((cMessage - ptrStart) - (LENGTH_OF_FILE_NAME + EXTENDED_UFILESYSTEM)); // {22} new frame content length
            #else
                usTotalLen += ((cMessage - ptrStart) - LENGTH_OF_FILE_NAME); // new frame content length
            #endif
        #else
                if (ptrFiles->ucProperties & FILE_ADD_EXT) {
                    *(cMessage - 1) = '.';
                    uMemcpy(cMessage, cMimeTable[ucMimeType], 3);
                    cMessage += 4;
                }
                *(cMessage - 1) = '\r';
                *(cMessage++) = '\n';
            #ifdef EXTENDED_UFILESYSTEM                                  // {22}
                usTotalLen += ((cMessage - ptrStart) - (LENGTH_OF_FILE_NAME + EXTENDED_UFILESYSTEM));
            #else
                usTotalLen += ((cMessage - ptrStart) - LENGTH_OF_FILE_NAME);
            #endif
        #endif
            }
            else {
    #endif
                iFileNameLength = sizeof(cFileName);
    #ifdef SUPPORT_MIME_IDENTIFIER
                uMemcpy(&cFileName[2], cMimeTable[ucMimeType], 3);       // set file extension {13} change location of extension
    #endif
    #ifdef SUB_FILE_SIZE
                cFileName[0] = uGetSubFileName(ucFileAddress);           // copy the file name to the local buffer {13} change location of file name
    #else
        #ifdef EXTENDED_UFILESYSTEM                                      // {21}
                if (uGetFileName(ucFileAddress, cFileName) == 0) {       // copy the file name to the local buffer
                    iFileNameLength -= EXTENDED_UFILESYSTEM;             // not extended so remove additional extended length                
                    usTotalLen -= EXTENDED_UFILESYSTEM;                  // only remove length when not extended
                }
        #else
                cFileName[0] = uGetFileName(ucFileAddress);              // copy the file name to the local buffer {13} change location of file name
        #endif
    #endif
                uMemcpy(cMessage, cFileName, iFileNameLength);           // add file name
                cMessage += iFileNameLength;
    #ifdef INTERNAL_USER_FILES                                           // {13}
                usTotalLen -= (MAX_FILE_NAME_LENGTH - iFileNameLength);
            }
    #endif
        }
    #ifdef INTERNAL_USER_FILES                                           // {13}
        if (ptrFiles != 0) {                                             // this entry is an internal file
            if ((ptrFiles = fnNextUserFile(++ptrFiles, &FileLength, &ucMimeType)) != 0) {
                continue;                                                // next user file to be displayed
            }
        }
    #endif
        ucFileAddress = UOPENNEXTFILE(ucFileAddress, &FileLength MIME_TYPE SUB_FILE_ON);// open next file in the system
    }
    if (FileCnt != 0) {                                                  // if file system is not empty
        LastFrameFileCount = FilesSent;                                  // {23} amount of files listed before this frame, in case we need to repeat
        FilesSent += FileCnt;                                            // total number of files listed till now (last frame)
    }
    return usTotalLen;                                                   // return the buffer content length to be sent
#else                                                                    // not FTP_SUPPORTS_NAME_DISPLAY {10}
    static const CHAR cFileNr[] = {'\r', '\n'};                          // no support - we simply count the number of files
    MEMORY_RANGE_POINTER ucFileAddress;
    unsigned char   ucFiles = 0;
    MAX_FILE_LENGTH FileLength;
    CHAR           *ptrMsg;
    #ifdef SUPPORT_MIME_IDENTIFIER
        #define MIME_TYPE , &ucMimeType
        #define UOPENNEXTFILE uOpenNextMimeFile
        unsigned char ucMimeType;
    #else
        #define UOPENNEXTFILE uOpenNextFile
        #define MIME_TYPE
    #endif

    if (FilesSent != 0) {
        return 0;
    }

    ucFileAddress = UOPENNEXTFILE(0, &FileLength MIME_TYPE  SUB_FILE_ON);// open the first file in the directory and get its length
    while (FileLength) {                                                 // for each file present
        ucFiles++;                                                       // another file found
        ucFileAddress = UOPENNEXTFILE(ucFileAddress, &FileLength MIME_TYPE SUB_FILE_ON);// open next file in the system
    }

    if (!ucFiles) return 0;

    ptrMsg = cMessage;
    cMessage = _fnBufferDec(ucFiles, 0, cMessage);                       // add the number of files found
    uMemcpy(cMessage, cFileNr, sizeof(cFileNr));                         // add terminator
    FilesSent += FILE_NAMES_PER_FTP_FRAME;
    return (sizeof(cFileNr) + (cMessage - ptrMsg));                      // return the buffer contnet length to be sent
#endif                                                                   // end not FTP_SUPPORTS_NAME_DISPLAY
}

// Send a message or regenerate a message
//
static signed short fnSendFTP(unsigned char ucMsg)
{
    static unsigned char ucLastData;
    USOCKET              Socket = FTP_TCP_socket;
    unsigned short       usSize;
    const CHAR           *ptrMsg;
    TCP_FTP_MESSAGE      FTP_Data_Tx;

    if (MSG_DO_NOTHING == ucMsg) {
        return 0;
    }

    if (ucMsg <= MSG_REPEAT_CONTROL) {                                   // handle repetitions
        if (ucMsg == MSG_REPEAT_CONTROL) {
            ucMsg = ucLastControl;                                       // automatically regenerate last data or control message
        }
        else {
            ucMsg = ucLastData;
            Socket = FTP_TCP_Data_socket;
        }
    }
    else if (ucMsg >= FIRST_DATA_MESSAGE) {
        ucLastData = ucMsg;
        Socket = FTP_TCP_Data_socket;
    }
    else {
        ucLastControl = ucMsg;
    }

    switch (ucMsg) {
    case MSG_SERVER_READY:
        usSize = (sizeof(cFTPServerReady) - 1);
        ptrMsg = cFTPServerReady;
        break;

    case MSG_DIR_OK:
        usSize = (sizeof(cFTP_DIR_OK) - 1);
        ptrMsg = cFTP_DIR_OK;
        break;

    case MSG_ENTER_PASS:
        usSize = (sizeof(cFTPEnterPass) - 1);
        ptrMsg = cFTPEnterPass;
        break;

    case MSG_LOG_SUCCESS:
        usSize = (sizeof(cFTPloginSuccessful) - 1);
        ptrMsg = cFTPloginSuccessful;
        break;

    case MSG_LOG_FAILED:
        usSize = (sizeof(cFTPBadPass) - 1);
        ptrMsg = cFTPBadPass;
        break;

    case MSG_FTP_DATA:
        usSize = (sizeof(cFTP_Data) - 1);
        ptrMsg = cFTP_Data;
        break;

    case MSG_FTP_TYPE:
        usSize = (sizeof(cFTPType) - 1);
        ptrMsg = cFTPType;
        break;

    case MSG_FTP_DENIED:
        usSize = (sizeof(cFTPDenied) - 1);
        ptrMsg = cFTPDenied;
        break;

    case MSG_FTP_DIR:
#if defined FTP_UTFAT && UT_FTP_PATH_LENGTH > 0                          // {30}
        if (ptr_utDirectory->usDirectoryFlags & UTDIR_VALID) {
            uMemcpy(FTP_Data_Tx.ucTCP_Message, cFTPDir, 5);              // 257 "
            usSize = uStrlen(ptr_utDirectory->ptrDirectoryPath);
            if (usSize > 2) {
                usSize -= 2;                                             // remove "D:" from front
            }
            if (usSize > (FTP_DATA_BUFFER_LENGTH - 8)) {                 // if the string is too long for the buffer it is cut short
                usSize = (FTP_DATA_BUFFER_LENGTH - 8);
            }
            ptrMsg = uMemcpy(&FTP_Data_Tx.ucTCP_Message[5], &ptr_utDirectory->ptrDirectoryPath[2], usSize);
            ptrMsg += usSize;
            uMemcpy((CHAR *)ptrMsg, &cFTPDir[6], 3);
            usSize = ((unsigned char *)ptrMsg + 3 - FTP_Data_Tx.ucTCP_Message);
            return (fnSendTCP(Socket, (unsigned char *)&FTP_Data_Tx.tTCP_Header, usSize, TCP_FLAG_PUSH) > 0);
        }
#endif
        usSize = (sizeof(cFTPDir) - 1);                                  // return "257 "/" as root directory location
        ptrMsg = cFTPDir;
        break;

    case MSG_FTP_OK:
        usSize = (sizeof(cFTPOK) - 1);
        ptrMsg = cFTPOK;
        break;

    case MSG_DIR_CHANGED:
        usSize = (sizeof(cFTPDirChanged) - 1);
        ptrMsg = cFTPDirChanged;
        break;

#if defined FTP_VERIFY_DATA_PORT                                          // {4}
    case MSG_BAD_DATA_CONNECTION:
        usSize = (sizeof(cFTP_Bad_Port) - 1);
        ptrMsg = cFTP_Bad_Port;
        break;
#endif

#if defined FTP_PASV_SUPPORT
    #if defined USE_IPV6
    case MSG_EPSV_OK:
    #endif
    case MSG_PASV_OK:
        usSize = fnDoGetPasvPort((CHAR *)FTP_Data_Tx.ucTCP_Message, (ucMsg == MSG_EPSV_OK)); // build response with IP and listening port 
        return (fnSendTCP(Socket, (unsigned char *)&FTP_Data_Tx.tTCP_Header, usSize, TCP_FLAG_PUSH) > 0);
#else
    case MSG_NOT_SUPPORTED:
        usSize = (sizeof(cFTP_Not_Supported) - 1);
        ptrMsg = cFTP_Not_Supported;
        break;
#endif

#if defined FTP_SUPPORTS_DELETE
    case MSG_DEL_OK:
        usSize = (sizeof(cFTPDelOK) - 1);
        ptrMsg = cFTPDelOK;
        break;
#endif

#if defined FTP_UTFAT                                                    // {17}
    case MSG_FTP_READY_FOR_RENAME:
        usSize = (sizeof(cFTP_Rename_OK) - 1);
        ptrMsg = cFTP_Rename_OK;
        break;

    case MSG_FTP_RENAME_SUCCESSFUL:
        usSize = (sizeof(cFTP_Rename_Success) - 1);
        ptrMsg = cFTP_Rename_Success;
        break;        
#endif

#ifdef FTP_SUPPORTS_DOWNLOAD
    #ifdef FTP_UTFAT                                                     // {17}
        #define _uGetFileData fnGetFileData
    #else
        #define _uGetFileData uGetFileData
    #endif
    case MSG_FILE_LENGTH:
        usSize = fnDoGetFileLen((CHAR *)FTP_Data_Tx.ucTCP_Message);
        return (fnSendTCP(Socket, (unsigned char *)&FTP_Data_Tx.tTCP_Header, usSize, TCP_FLAG_PUSH) > 0);

    case MSG_UPLOAD:
    #ifdef FTP_DATA_WINDOWS                                              // {12}
        {
            unsigned short usTxLength = present_tcp->usTxWindowSize;
            if (usTxLength > FTP_DATA_BUFFER_LENGTH) {                   // if the destination rx buffer can accept a full TCP frame send a full frame
                usTxLength = FTP_DATA_BUFFER_LENGTH;                     // full frame size
            }
            if ((usLastSentSize = (unsigned short)_uGetFileData(ptrFile, FileOffset, FTP_Data_Tx.ucTCP_Message, usTxLength)) != 0) {
                unsigned short usNextSent;
                signed short sSend = fnSendTCP(Socket, (unsigned char *)&FTP_Data_Tx.tTCP_Header, usLastSentSize, TCP_FLAG_PUSH);
                if (sSend <= 0) {                                        // if transmission error
                    return sSend;                                        // return transmission status
                }
                present_tcp->usOpenCnt += usLastSentSize;                // the destination receiver's input buffer size after receiving the previous frame
                usTxLength = (present_tcp->usTxWindowSize - usLastSentSize);
                if (usTxLength > FTP_DATA_BUFFER_LENGTH) {               // if the destination rx buffer can still accept a full TCP frame send a full frame
                    usTxLength = FTP_DATA_BUFFER_LENGTH;                 // full frame size
                }
                usNextSent = (unsigned short)_uGetFileData(ptrFile, (FileOffset + usLastSentSize), FTP_Data_Tx.ucTCP_Message, usTxLength );
                if (usNextSent == 0) {                                   // end of file reached
                    return sSend;
                }
                usLastSentSize += usNextSent;                            // complete outstanding data to be acked
                return (fnSendTCP(Socket, (unsigned char *)&FTP_Data_Tx.tTCP_Header, usNextSent, TCP_FLAG_PUSH)); // send second buffer
            }
            else {
                return 0;                                                // end of file reached
            }
        }
    #else
        if ((usLastSentSize = (unsigned short)_uGetFileData(ptrFile, FileOffset, FTP_Data_Tx.ucTCP_Message, FTP_DATA_BUFFER_LENGTH)) != 0) {
            return (fnSendTCP(Socket, (unsigned char *)&FTP_Data_Tx.tTCP_Header, usLastSentSize, TCP_FLAG_PUSH));
        }
        else {
            return 0;                                                    // end of file reached
        }
    #endif
#endif

    case MSG_FTP_UNKNOWN:
        usSize = (sizeof(cFTPUnknownCommand) - 1);
        ptrMsg = cFTPUnknownCommand;
        break;

    case MSG_FTP_QUITTING:
        usSize = (sizeof(cFTPTerminating) - 1);
        ptrMsg = cFTPTerminating;
        ucFTP_state = FTP_STATE_PREPARE_CLOSE;                           // {27} close the connection when the quit has been acknowledged
        break;

                                                                         // data commands
    case MSG_DIR:                                                        // list files
#ifdef FTP_UTFAT                                                         // {17} utFAT has priority over other file system types
        if (ptr_utDirectory->usDirectoryFlags & UTDIR_VALID) {
            fileList.ptrBuffer = (CHAR *)FTP_Data_Tx.ucTCP_Message;
            fileList.ucStyle = FTP_TYPE_LISTING;
            fileList.usBufferLength = sizeof(FTP_Data_Tx.ucTCP_Message); // the maximum space in a single TCP frame
            uMemcpy(&utListLastDirectory, &utListDirectory, sizeof(utListDirectory)); // backup the original listing location in case of necessity to repeat
            if (utListDir(&utListDirectory, &fileList) == UTFAT_NO_MORE_LISTING_ITEMS_FOUND) { // generate the directory listing content
                fileList.usMaxItems = 0;                                 // mark that this content is the final content
            }
            if (fileList.usItemsReturned != 0) {
                FilesSent = 1;                                           // mark that listing is taking place in case of repetitions
                return (fnSendTCP(Socket, (unsigned char *)&FTP_Data_Tx.tTCP_Header, fileList.usStringLength, TCP_FLAG_PUSH) > 0);
            }
            else {
                FilesSent = 0;
                return 0;                                                // end of directory listing
            }
        }
#endif
        if ((usSize = fnDoDir((CHAR *)FTP_Data_Tx.ucTCP_Message)) != 0) {// create a file listing message
            return (fnSendTCP(Socket, (unsigned char *)&FTP_Data_Tx.tTCP_Header, usSize, TCP_FLAG_PUSH) > 0);
        }
        else if (FilesSent) {                                            // end of list
            return 0;
        }
        else {
            FilesSent += FILE_NAMES_PER_FTP_FRAME;
        }
        usSize = (sizeof(cFTP_DIR) - 1);
        ptrMsg = cFTP_DIR;
        break;

    default:                                                             // unexpected call value - ignore
        return 0;
    }

    uMemcpy(FTP_Data_Tx.ucTCP_Message, ptrMsg, usSize);
    return (fnSendTCP(Socket, (unsigned char *)&FTP_Data_Tx.tTCP_Header, usSize, TCP_FLAG_PUSH) > 0);
}

#endif

