Webserver+3d print
cyclone_tcp/tftp/tftp_server.c
- Committer:
- Sergunb
- Date:
- 2017-02-04
- Revision:
- 0:8918a71cdbe9
File content as of revision 0:8918a71cdbe9:
/** * @file tftp_server.c * @brief TFTP server * * @section License * * Copyright (C) 2010-2017 Oryx Embedded SARL. All rights reserved. * * This file is part of CycloneTCP Open. * * This program 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 2 * of the License, or (at your option) any later version. * * This program 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * @section Description * * TFTP is a very simple protocol used to transfer files. Refer to the * following RFCs for complete details: * - RFC 1123: Requirements for Internet Hosts * - RFC 1350: The TFTP Protocol (Revision 2) * - RFC 1782: TFTP Option Extension * - RFC 1783: TFTP Blocksize Option * - RFC 1784: TFTP Timeout Interval and Transfer Size Options * * @author Oryx Embedded SARL (www.oryx-embedded.com) * @version 1.7.6 **/ //Switch to the appropriate trace level #define TRACE_LEVEL TFTP_TRACE_LEVEL //Dependencies #include "tftp/tftp_server.h" #include "debug.h" //Check TCP/IP stack configuration #if (TFTP_SERVER_SUPPORT == ENABLED) /** * @brief Initialize settings with default values * @param[out] settings Structure that contains TFTP server settings **/ void tftpServerGetDefaultSettings(TftpServerSettings *settings) { //The TFTP server is not bound to any interface settings->interface = NULL; //TFTP port number settings->port = TFTP_PORT; //Open file callback function settings->openFileCallback = NULL; //Write file callback function settings->writeFileCallback = NULL; //Read file callback function settings->readFileCallback = NULL; //Close file callback function settings->closeFileCallback = NULL; } /** * @brief TFTP server initialization * @param[in] context Pointer to the TFTP server context * @param[in] settings TFTP server specific settings * @return Error code **/ error_t tftpServerInit(TftpServerContext *context, const TftpServerSettings *settings) { error_t error; //Debug message TRACE_INFO("Initializing TFTP server...\r\n"); //Ensure the parameters are valid if(context == NULL || settings == NULL) return ERROR_INVALID_PARAMETER; //Clear the TFTP server context memset(context, 0, sizeof(TftpServerContext)); //Save user settings context->settings = *settings; //Create an event object to poll the state of sockets if(!osCreateEvent(&context->event)) { //Failed to create event return ERROR_OUT_OF_RESOURCES; } //Start of exception handling block do { //Open a UDP socket context->socket = socketOpen(SOCKET_TYPE_DGRAM, SOCKET_IP_PROTO_UDP); //Failed to open socket? if(context->socket == NULL) { //Report an error error = ERROR_OPEN_FAILED; //Exit immediately break; } //Associate the socket with the relevant interface error = socketBindToInterface(context->socket, settings->interface); //Unable to bind the socket to the desired interface? if(error) break; //The TFTP server listens for connection requests on port 69 error = socketBind(context->socket, &IP_ADDR_ANY, settings->port); //Unable to bind the socket to the desired port? if(error) break; //End of exception handling block } while(0); //Did we encounter an error? if(error) { //Free previously allocated resources osDeleteEvent(&context->event); //Close socket socketClose(context->socket); } //Return status code return error; } /** * @brief Start TFTP server * @param[in] context Pointer to the TFTP server context * @return Error code **/ error_t tftpServerStart(TftpServerContext *context) { OsTask *task; //Debug message TRACE_INFO("Starting TFTP server...\r\n"); //Make sure the TFTP server context is valid if(context == NULL) return ERROR_INVALID_PARAMETER; //Create the TFTP server task task = osCreateTask("TFTP Server", (OsTaskCode) tftpServerTask, context, TFTP_SERVER_STACK_SIZE, TFTP_SERVER_PRIORITY); //Unable to create the task? if(task == OS_INVALID_HANDLE) return ERROR_OUT_OF_RESOURCES; //Successful processing return NO_ERROR; } /** * @brief TFTP server task * @param[in] context Pointer to the TFTP server context **/ void tftpServerTask(TftpServerContext *context) { error_t error; uint_t i; TftpClientConnection *connection; #if (NET_RTOS_SUPPORT == ENABLED) //Process events while(1) { #endif //Clear event descriptor set memset(context->eventDesc, 0, sizeof(context->eventDesc)); //Specify the events the application is interested in for(i = 0; i < TFTP_SERVER_MAX_CONNECTIONS; i++) { //Point to the structure describing the current connection connection = &context->connection[i]; //Loop through active connections only if(connection->state != TFTP_STATE_CLOSED) { //Wait for a packet to be received context->eventDesc[i].socket = connection->socket; context->eventDesc[i].eventMask = SOCKET_EVENT_RX_READY; } } //The TFTP server listens for connection requests on port 69 context->eventDesc[i].socket = context->socket; context->eventDesc[i].eventMask = SOCKET_EVENT_RX_READY; //Wait for one of the set of sockets to become ready to perform I/O error = socketPoll(context->eventDesc, TFTP_SERVER_MAX_CONNECTIONS + 1, &context->event, TFTP_SERVER_TICK_INTERVAL); //Verify status code if(!error) { //Event-driven processing for(i = 0; i < TFTP_SERVER_MAX_CONNECTIONS; i++) { //Point to the structure describing the current connection connection = &context->connection[i]; //Loop through active connections only if(connection->state != TFTP_STATE_CLOSED) { //Check whether a packet has been received if(context->eventDesc[i].eventFlags & SOCKET_EVENT_RX_READY) { //Process incoming packet tftpServerProcessPacket(context, connection); } } } //Any connection request received on port 69? if(context->eventDesc[i].eventFlags & SOCKET_EVENT_RX_READY) { //Accept connection request tftpServerAcceptRequest(context); } } //Handle periodic operations tftpServerTick(context); #if (NET_RTOS_SUPPORT == ENABLED) } #endif } /** * @brief Handle periodic operations * @param[in] context Pointer to the TFTP server context **/ void tftpServerTick(TftpServerContext *context) { uint_t i; systime_t time; TftpClientConnection *connection; //Get current time time = osGetSystemTime(); //Handle periodic operations for(i = 0; i < TFTP_SERVER_MAX_CONNECTIONS; i++) { //Point to the structure describing the current connection connection = &context->connection[i]; //Check current state if(connection->state == TFTP_STATE_READING || connection->state == TFTP_STATE_WRITING || connection->state == TFTP_STATE_READ_COMPLETE) { //Check current time if(timeCompare(time, connection->timestamp + TFTP_SERVER_TIMEOUT) >= 0) { //Handle retransmissions if(connection->retransmitCount < TFTP_SERVER_MAX_RETRIES) { //Retransmit last packet tftpServerRetransmitPacket(connection); //Save the time at which the packet was sent connection->timestamp = osGetSystemTime(); //Increment retransmission counter connection->retransmitCount++; } else { //Close connection tftpServerCloseConnection(connection); } } } else if(connection->state == TFTP_STATE_WRITE_COMPLETE) { //The host sending the final ACK will wait for a while before terminating //in order to retransmit the final ACK if it has been lost if(timeCompare(time, connection->timestamp + TFTP_SERVER_FINAL_DELAY) >= 0) { //Close connection tftpServerCloseConnection(connection); } } } } /** * @brief Accept connection request * @param[in] context Pointer to the TFTP server context **/ void tftpServerAcceptRequest(TftpServerContext *context) { error_t error; size_t length; uint16_t opcode; IpAddr clientIpAddr; uint16_t clientPort; //Read incoming TFTP packet error = socketReceiveFrom(context->socket, &clientIpAddr, &clientPort, context->packet, TFTP_SERVER_MAX_PACKET_SIZE, &length, 0); //Failed to read packet? if(error) return; //Debug message TRACE_INFO("TFTP Server: Accepting connection from %s port %" PRIu16 "...\r\n", ipAddrToString(&clientIpAddr, NULL), clientPort); //Sanity check if(length < sizeof(uint16_t)) return; //Retrieve TFTP packet type opcode = LOAD16BE(context->packet); //Read request received? if(opcode == TFTP_OPCODE_RRQ) { //Process RRQ packet tftpServerProcessRrqPacket(context, &clientIpAddr, clientPort, (TftpRrqPacket *) context->packet, length); } //Write request received? else if(opcode == TFTP_OPCODE_WRQ) { //Process WRQ packet tftpServerProcessWrqPacket(context, &clientIpAddr, clientPort, (TftpWrqPacket *) context->packet, length); } //Invalid request received? else { //Discard incoming packet } } /** * @brief Process incoming packet * @param[in] context Pointer to the TFTP server context * @param[in] connection Pointer to the client connection **/ void tftpServerProcessPacket(TftpServerContext *context, TftpClientConnection *connection) { error_t error; size_t length; uint16_t opcode; IpAddr clientIpAddr; uint16_t clientPort; //Read incoming TFTP packet error = socketReceiveFrom(connection->socket, &clientIpAddr, &clientPort, context->packet, TFTP_SERVER_MAX_PACKET_SIZE, &length, 0); //Failed to read packet? if(error) return; //Sanity check if(length < sizeof(uint16_t)) return; //Retrieve TFTP packet type opcode = LOAD16BE(context->packet); //Data packet received? if(opcode == TFTP_OPCODE_DATA) { //Process DATA packet tftpServerProcessDataPacket(connection, (TftpDataPacket *) context->packet, length); } //Acknowledgment packet received? else if(opcode == TFTP_OPCODE_ACK) { //Process ACK packet tftpServerProcessAckPacket(connection, (TftpAckPacket *) context->packet, length); } //Error packet received? else if(opcode == TFTP_OPCODE_ERROR) { //Process ERROR packet tftpServerProcessErrorPacket(connection, (TftpErrorPacket *) context->packet, length); } //Invalid packet received? else { //Discard incoming packet } } /** * @brief Process incoming RRQ packet * @param[in] context Pointer to the TFTP server context * @param[in] clientIpAddr IP address of the client * @param[in] clientPort Port number used by the client * @param[in] rrqPacket Pointer to the RRQ packet * @param[in] length Length of the packet, in bytes **/ void tftpServerProcessRrqPacket(TftpServerContext *context, const IpAddr *clientIpAddr, uint16_t clientPort, const TftpRrqPacket *rrqPacket, size_t length) { const char_t *mode; TftpClientConnection *connection; //Debug message TRACE_DEBUG("TFTP Server: RRQ packet received (%" PRIuSIZE " bytes)...\r\n", length); //Make sure the length of the RRQ packet is acceptable if(length <= sizeof(TftpRrqPacket)) return; //Compute the number of bytes that follows the 2-byte opcode length -= sizeof(TftpRrqPacket); //Point to the incoming RRQ packet rrqPacket = (TftpRrqPacket *) context->packet; //Malformed RRQ packet? if(rrqPacket->filename[length - 1] != '\0') return; //Compute the length of the mode string length -= strlen(rrqPacket->filename) + 1; //Malformed RRQ packet? if(length == 0) return; //Point to the mode string mode = rrqPacket->filename + strlen(rrqPacket->filename) + 1; //Debug message TRACE_DEBUG(" Opcode = %u\r\n", ntohs(rrqPacket->opcode)); TRACE_DEBUG(" Filename = %s\r\n", rrqPacket->filename); TRACE_DEBUG(" Mode = %s\r\n", mode); //Create a new connection connection = tftpServerOpenConnection(context, clientIpAddr, clientPort); //Any error to report? if(connection == NULL) return; //Open the specified file for reading if(context->settings.openFileCallback != NULL) { //Invoke user callback function connection->file = context->settings.openFileCallback(rrqPacket->filename, mode, FALSE); } else { //No callback function defined connection->file = NULL; } //Check if the file was successfully opened if(connection->file != NULL) { //The read operation is in progress... connection->state = TFTP_STATE_READING; //Initialize block number connection->block = 1; //Send the first DATA packet tftpServerSendDataPacket(connection); } else { //If the reply is an error packet, then the request has been denied tftpServerSendErrorPacket(connection, TFTP_ERROR_NOT_DEFINED, "Failed to open file"); //Close the connection tftpServerCloseConnection(connection); } } /** * @brief Process incoming WRQ packet * @param[in] context Pointer to the TFTP server context * @param[in] clientIpAddr IP address of the client * @param[in] clientPort Port number used by the client * @param[in] wrqPacket Pointer to the WRQ packet * @param[in] length Length of the packet, in bytes **/ void tftpServerProcessWrqPacket(TftpServerContext *context, const IpAddr *clientIpAddr, uint16_t clientPort, const TftpWrqPacket *wrqPacket, size_t length) { const char_t *mode; TftpClientConnection *connection; //Debug message TRACE_DEBUG("TFTP Server: WRQ packet received (%" PRIuSIZE " bytes)...\r\n", length); //Make sure the length of the WRQ packet is acceptable if(length <= sizeof(TftpWrqPacket)) return; //Compute the number of bytes that follows the 2-byte opcode length -= sizeof(TftpWrqPacket); //Point to the incoming WRQ packet wrqPacket = (TftpWrqPacket *) context->packet; //Malformed WRQ packet? if(wrqPacket->filename[length - 1] != '\0') return; //Compute the length of the mode string length -= strlen(wrqPacket->filename) + 1; //Malformed WRQ packet? if(length == 0) return; //Point to the mode string mode = wrqPacket->filename + strlen(wrqPacket->filename) + 1; //Debug message TRACE_DEBUG(" Opcode = %u\r\n", ntohs(wrqPacket->opcode)); TRACE_DEBUG(" Filename = %s\r\n", wrqPacket->filename); TRACE_DEBUG(" Mode = %s\r\n", mode); //Create a new connection connection = tftpServerOpenConnection(context, clientIpAddr, clientPort); //Any error to report? if(connection == NULL) return; //Open the specified file for writing if(context->settings.openFileCallback != NULL) { //Invoke user callback function connection->file = context->settings.openFileCallback(wrqPacket->filename, mode, TRUE); } else { //No callback function defined connection->file = NULL; } //Check if the file was successfully opened if(connection->file != NULL) { //The write operation is in progress... connection->state = TFTP_STATE_WRITING; //Initialize block number connection->block = 0; //The positive response to a write request is an acknowledgment //packet with block number zero tftpServerSendAckPacket(connection); //Increment block number connection->block++; } else { //If the reply is an error packet, then the request has been denied tftpServerSendErrorPacket(connection, TFTP_ERROR_NOT_DEFINED, "Failed to open file"); //Close the connection tftpServerCloseConnection(connection); } } /** * @brief Process incoming DATA packet * @param[in] connection Pointer to the client connection * @param[in] dataPacket Pointer to the DATA packet * @param[in] length Length of the packet, in bytes **/ void tftpServerProcessDataPacket(TftpClientConnection *connection, const TftpDataPacket *dataPacket, size_t length) { error_t error; size_t offset; //Debug message TRACE_DEBUG("TFTP Server: DATA packet received (%" PRIuSIZE " bytes)...\r\n", length); //Make sure the length of the DATA packet is acceptable if(length < sizeof(TftpDataPacket)) return; //Calculate the length of the data length -= sizeof(TftpDataPacket); //Debug message TRACE_DEBUG(" Opcode = %" PRIu16 "\r\n", ntohs(dataPacket->opcode)); TRACE_DEBUG(" Block = %" PRIu16 "\r\n", ntohs(dataPacket->block)); //Check current state if(connection->state == TFTP_STATE_WRITING) { //Check block number if(ntohs(dataPacket->block) == connection->block) { //Write data to the output file if(connection->settings->writeFileCallback != NULL) { //Calculate the offset relative to the beginning of the file offset = (connection->block - 1) * TFTP_SERVER_BLOCK_SIZE; //Invoke user callback function error = connection->settings->writeFileCallback(connection->file, offset, dataPacket->data, length); } else { //No callback function defined error = ERROR_WRITE_FAILED; } //Check status code if(!error) { //Acknowledge the DATA packet tftpServerSendAckPacket(connection); //Increment block number connection->block++; //A data packet of less than 512 bytes signals termination of the transfer if(length < TFTP_SERVER_BLOCK_SIZE) { //Properly close the file if(connection->settings->closeFileCallback != NULL) { //Invoke user callback function connection->settings->closeFileCallback(connection->file); } //Mark the file as closed connection->file = NULL; //The host sending the final ACK will wait for a while before terminating //in order to retransmit the final ACK if it has been lost connection->state = TFTP_STATE_WRITE_COMPLETE; //Save current time connection->timestamp = osGetSystemTime(); } } else { //An error occurs during the transfer tftpServerSendErrorPacket(connection, TFTP_ERROR_NOT_DEFINED, "Failed to write file"); //A TFTP server may terminate after sending an error message tftpServerCloseConnection(connection); } } else { //Retransmit ACK packet tftpServerRetransmitPacket(connection); } } else if(connection->state == TFTP_STATE_WRITE_COMPLETE) { //The acknowledger will know that the ACK has been lost if it //receives the final DATA packet again tftpServerRetransmitPacket(connection); } } /** * @brief Process incoming ACK packet * @param[in] connection Pointer to the client connection * @param[in] ackPacket Pointer to the ACK packet * @param[in] length Length of the packet, in bytes **/ void tftpServerProcessAckPacket(TftpClientConnection *connection, const TftpAckPacket *ackPacket, size_t length) { //Debug message TRACE_DEBUG("TFTP Server: ACK packet received (%" PRIuSIZE " bytes)...\r\n", length); //Make sure the length of the ACK packet is acceptable if(length < sizeof(TftpAckPacket)) return; //Debug message TRACE_DEBUG(" Opcode = %" PRIu16 "\r\n", ntohs(ackPacket->opcode)); TRACE_DEBUG(" Block = %" PRIu16 "\r\n", ntohs(ackPacket->block)); //Check current state if(connection->state == TFTP_STATE_READING) { //Make sure the ACK is not a duplicate if(ntohs(ackPacket->block) == connection->block) { //The block number increases by one for each new block of data connection->block++; //Send DATA packet tftpServerSendDataPacket(connection); } } else if(connection->state == TFTP_STATE_READ_COMPLETE) { //Make sure the ACK is not a duplicate if(ntohs(ackPacket->block) == connection->block) { //The host sending the last DATA must retransmit it until the packet is //acknowledged or the sending host times out. If the response is an ACK, //the transmission was completed successfully tftpServerCloseConnection(connection); } } } /** * @brief Process incoming ERROR packet * @param[in] connection Pointer to the client connection * @param[in] errorPacket Pointer to the ERROR packet * @param[in] length Length of the packet, in bytes **/ void tftpServerProcessErrorPacket(TftpClientConnection *connection, const TftpErrorPacket *errorPacket, size_t length) { //Debug message TRACE_DEBUG("TFTP Server: ERROR packet received (%" PRIuSIZE " bytes)...\r\n", length); //Make sure the length of the ERROR packet is acceptable if(length < sizeof(TftpErrorPacket)) return; //Debug message TRACE_DEBUG(" Opcode = %" PRIu16 "\r\n", ntohs(errorPacket->opcode)); TRACE_DEBUG(" Error Code = %" PRIu16 "\r\n", ntohs(errorPacket->errorCode)); //Compute the length of the error message length -= sizeof(TftpErrorPacket); //Make sure the error message is terminated with a zero byte if(length > 1 && errorPacket->errorMsg[length - 1] == '\0') { //Debug message TRACE_DEBUG(" Error Msg = %s\r\n", errorPacket->errorMsg); } //Close connection tftpServerCloseConnection(connection); } /** * @brief Send DATA packet * @param[in] connection Pointer to the client connection * @return Error code **/ error_t tftpServerSendDataPacket(TftpClientConnection *connection) { error_t error; size_t offset; TftpDataPacket *dataPacket; //Point the buffer where to format the packet dataPacket = (TftpDataPacket *) connection->packet; //Format DATA packet dataPacket->opcode = HTONS(TFTP_OPCODE_DATA); dataPacket->block = htons(connection->block); //Read more data from the input file if(connection->settings->readFileCallback != NULL) { //Calculate the offset relative to the beginning of the file offset = (connection->block - 1) * TFTP_SERVER_BLOCK_SIZE; //Invoke user callback function error = connection->settings->readFileCallback(connection->file, offset, dataPacket->data, TFTP_SERVER_BLOCK_SIZE, &connection->packetLen); } else { //No callback function defined error = ERROR_READ_FAILED; } //End of file? if(error == ERROR_END_OF_FILE || error == ERROR_END_OF_STREAM) { //Catch exception error = NO_ERROR; //This is the the last block of data connection->packetLen = 0; } //Check status code if(!error) { //A data packet of less than 512 bytes signals termination of the transfer if(connection->packetLen < TFTP_SERVER_BLOCK_SIZE) { //Properly close the file if(connection->settings->closeFileCallback != NULL) { //Invoke user callback function connection->settings->closeFileCallback(connection->file); } //Mark the file as closed connection->file = NULL; //The host sending the last DATA must retransmit it until the packet //is acknowledged or the sending host times out connection->state = TFTP_STATE_READ_COMPLETE; } //Length of the DATA packet connection->packetLen += sizeof(TftpAckPacket); //Debug message TRACE_DEBUG("TFTP Server: Sending DATA packet (%" PRIuSIZE " bytes)...\r\n", connection->packetLen); TRACE_DEBUG(" Opcode = %" PRIu16 "\r\n", ntohs(dataPacket->opcode)); TRACE_DEBUG(" Block = %" PRIu16 "\r\n", ntohs(dataPacket->block)); //Send DATA packet error = socketSend(connection->socket, connection->packet, connection->packetLen, NULL, 0); //Save the time at which the packet was sent connection->timestamp = osGetSystemTime(); //Reset retransmission counter connection->retransmitCount = 0; } else { //An error occurs during the transfer tftpServerSendErrorPacket(connection, TFTP_ERROR_NOT_DEFINED, "Failed to read file"); //A TFTP server may terminate after sending an error message tftpServerCloseConnection(connection); } //Return status code return error; } /** * @brief Send ACK packet * @param[in] connection Pointer to the client connection * @return Error code **/ error_t tftpServerSendAckPacket(TftpClientConnection *connection) { error_t error; TftpAckPacket *ackPacket; //Point the buffer where to format the packet ackPacket = (TftpAckPacket *) connection->packet; //Format ACK packet ackPacket->opcode = HTONS(TFTP_OPCODE_ACK); ackPacket->block = htons(connection->block); //Length of the ACK packet connection->packetLen = sizeof(TftpAckPacket); //Debug message TRACE_DEBUG("TFTP Server: Sending ACK packet (%" PRIuSIZE " bytes)...\r\n", connection->packetLen); TRACE_DEBUG(" Opcode = %" PRIu16 "\r\n", ntohs(ackPacket->opcode)); TRACE_DEBUG(" Block = %" PRIu16 "\r\n", ntohs(ackPacket->block)); //Send ACK packet error = socketSend(connection->socket, connection->packet, connection->packetLen, NULL, 0); //Save the time at which the packet was sent connection->timestamp = osGetSystemTime(); //Reset retransmission counter connection->retransmitCount = 0; //Return status code return error; } /** * @brief Send ERROR packet * @param[in] connection Pointer to the client connection * @param[in] errorCode Integer indicating the nature of the error * @param[in] errorMsg Error message intended for human consumption * @return Error code **/ error_t tftpServerSendErrorPacket(TftpClientConnection *connection, uint16_t errorCode, const char_t *errorMsg) { error_t error; TftpErrorPacket *errorPacket; //Check the length of the error message if(strlen(errorMsg) >= TFTP_SERVER_BLOCK_SIZE) return ERROR_INVALID_PARAMETER; //Point the buffer where to format the packet errorPacket = (TftpErrorPacket *) connection->packet; //Format ERROR packet errorPacket->opcode = HTONS(TFTP_OPCODE_ERROR); errorPacket->errorCode = htons(errorCode); //Copy error message strcpy(errorPacket->errorMsg, errorMsg); //Length of the ERROR packet connection->packetLen = sizeof(TftpErrorPacket) + strlen(errorMsg) + 1; //Debug message TRACE_DEBUG("TFTP Server: Sending ERROR packet (%" PRIuSIZE " bytes)...\r\n", connection->packetLen); TRACE_DEBUG(" Opcode = %" PRIu16 "\r\n", ntohs(errorPacket->opcode)); TRACE_DEBUG(" Error Code = %" PRIu16 "\r\n", ntohs(errorPacket->errorCode)); TRACE_DEBUG(" Error Msg = %s\r\n", errorPacket->errorMsg); //Send ERROR packet error = socketSend(connection->socket, connection->packet, connection->packetLen, NULL, 0); //Save the time at which the packet was sent connection->timestamp = osGetSystemTime(); //Reset retransmission counter connection->retransmitCount = 0; //Return status code return error; } /** * @brief Retransmit the last packet * @param[in] connection Pointer to the client connection * @return Error code **/ error_t tftpServerRetransmitPacket(TftpClientConnection *connection) { error_t error; //Debug message TRACE_DEBUG("TFTP Server: Retransmitting packet (%" PRIuSIZE " bytes)...\r\n", connection->packetLen); //Retransmit the last packet error = socketSend(connection->socket, connection->packet, connection->packetLen, NULL, 0); //Return status code return error; } /** * @brief Create client connection * @param[in] context Pointer to the TFTP server context * @param[in] clientIpAddr IP address of the client * @param[in] clientPort Port number used by the client * @return Pointer to the structure describing the connection **/ TftpClientConnection *tftpServerOpenConnection(TftpServerContext *context, const IpAddr *clientIpAddr, uint16_t clientPort) { error_t error; uint_t i; TftpClientConnection *connection; TftpClientConnection *oldestConnection; //Keep track of the oldest connection that is waiting to //retransmit the final ACK oldestConnection = NULL; //Loop through the connection table for(i = 0; i < TFTP_SERVER_MAX_CONNECTIONS; i++) { //Point to the current entry connection = &context->connection[i]; //Check the state of the current connection if(connection->state == TFTP_STATE_CLOSED) { //The current entry is available break; } else if(connection->state == TFTP_STATE_WRITE_COMPLETE) { //Keep track of the oldest connection that is waiting to //retransmit the final ACK if(oldestConnection == NULL) { //Save current connection oldestConnection = connection; } else if(timeCompare(connection->timestamp, oldestConnection->timestamp) < 0) { //Save current connection oldestConnection = connection; } } } //The oldest connection that is waiting to retransmit the final ACK //can be reused when the connection table runs out of space if(i >= TFTP_SERVER_MAX_CONNECTIONS) { //Close the oldest connection tftpServerCloseConnection(oldestConnection); //Reuse the connection connection = oldestConnection; } //Failed to create a new connection? if(connection == NULL) return NULL; //Clear the structure describing the connection memset(connection, 0, sizeof(TftpClientConnection)); //Open a UDP socket connection->socket = socketOpen(SOCKET_TYPE_DGRAM, SOCKET_IP_PROTO_UDP); //Failed to open socket? if(connection->socket == NULL) return NULL; //Associate the socket with the relevant interface error = socketBindToInterface(connection->socket, context->settings.interface); //Any error to report? if(error) { //Clean up side effects socketClose(connection->socket); connection->socket = NULL; //Exit immediately return NULL; } //Connect the socket to the remote TFTP client error = socketConnect(connection->socket, clientIpAddr, clientPort); //Any error to report? if(error) { //Clean up side effects socketClose(connection->socket); connection->socket = NULL; //Exit immediately return NULL; } //Reference to the TFTP server settings connection->settings = &context->settings; //Update connection state connection->state = TFTP_STATE_OPEN; //Pointer to the structure describing the connection return connection; } /** * @brief Close client connection * @param[in] connection Pointer to the client connection **/ void tftpServerCloseConnection(TftpClientConnection *connection) { //Debug message TRACE_INFO("TFTP Server: Closing connection...\r\n"); //Any active connection? if(connection->socket != NULL) { //Close UDP socket socketClose(connection->socket); connection->socket = NULL; } //Check whether a read or write operation is in progress... if(connection->file != NULL) { //Properly close the file before closing the connection if(connection->settings->closeFileCallback != NULL) { //Invoke user callback function connection->settings->closeFileCallback(connection->file); } //Mark the file as closed connection->file = NULL; } //Mark the connection as closed connection->state = TFTP_STATE_CLOSED; } #endif