/** * Copyright (c) 2015 - 2019, Nordic Semiconductor ASA * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form, except as embedded into a Nordic * Semiconductor ASA integrated circuit in a product or a software update for * such product, must reproduce the above copyright notice, this list of * conditions and the following disclaimer in the documentation and/or other * materials provided with the distribution. * * 3. Neither the name of Nordic Semiconductor ASA nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * 4. This software, with or without modification, must only be used with a * Nordic Semiconductor ASA integrated circuit. * * 5. Any software provided in binary form under this license must not be reverse * engineered, decompiled, modified and/or disassembled. * * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include "sdk_config.h" #include "iot_tftp.h" #include "iot_common.h" #include "udp_api.h" #include "app_util.h" #if TFTP_CONFIG_LOG_ENABLED #define NRF_LOG_MODULE_NAME tftp #define NRF_LOG_LEVEL TFTP_CONFIG_LOG_LEVEL #define NRF_LOG_INFO_COLOR TFTP_CONFIG_INFO_COLOR #define NRF_LOG_DEBUG_COLOR TFTP_CONFIG_DEBUG_COLOR #include "nrf_log.h" NRF_LOG_MODULE_REGISTER(); #define TFTP_TRC NRF_LOG_DEBUG /**< Used for getting trace of execution in the module. */ #define TFTP_ERR NRF_LOG_ERROR /**< Used for logging errors in the module. */ #define TFTP_DUMP NRF_LOG_HEXDUMP_DEBUG /**< Used for dumping octet information to get details of bond information etc. */ #define TFTP_ENTRY() TFTP_TRC(">> %s", __func__) #define TFTP_EXIT() TFTP_TRC("<< %s", __func__) #else // TFTP_CONFIG_LOG_ENABLED #define TFTP_TRC(...) /**< Disables traces. */ #define TFTP_DUMP(...) /**< Disables dumping of octet streams. */ #define TFTP_ERR(...) /**< Disables error logs. */ #define TFTP_ENTRY(...) #define TFTP_EXIT(...) #endif // TFTP_CONFIG_LOG_ENABLED /** * @defgroup tftp_mutex_lock_unlock Module's Mutex Lock/Unlock Macros. * * @details Macros used to lock and unlock modules. Currently, SDK does not use mutexes but * framework is provided in case need arises to use an alternative architecture. * @{ */ #define TFTP_MUTEX_LOCK() SDK_MUTEX_LOCK(m_tftp_mutex) /**< Lock module using mutex. */ #define TFTP_MUTEX_UNLOCK() SDK_MUTEX_UNLOCK(m_tftp_mutex) /**< Unlock module using mutex. */ /** @} */ #define TFTP_HEADER_SIZE 2 /**< uint16_t opcode number. */ #define TFTP_BLOCK_ID_SIZE 2 /**< uint16_t block id number. */ #define TFTP_ERR_CODE_SIZE 2 /**< uint16_t error code. */ #define TFTP_DEFAULT_BLOCK_SIZE 512 /**< uint16_t default data block size. */ #define TFTP_DEFAULT_PORT 69 /**< uint16_t default TFTP server port number. */ /**@brief Supported TFTP options. */ #define OPTION_MODE_ASCII "netascii" /**< NETASCII mode string defined inside RFC1350. */ #define OPTION_MODE_OCTET "octet" /**< OCTET mode string defined inside RFC1350. */ #define OPTION_BLKSIZE "blksize" /**< Block Size option string defined inside RFC2348. */ #define OPTION_TIMEOUT "timeout" /**< Timeout option string defined inside RFC2349. */ #define OPTION_SIZE "tsize" /**< Transfer Size option string defined inside RFC2348. */ #define NEXT_RETR_MAX_LENGTH 4 /**< Maximum length of TFTP "timeout" option value. */ #define BLKSIZE_MAX_LENGTH 10 /**< Maximum length of TFTP "blksize" option value. */ #define FILE_SIZE_MAX_LENGTH 10 /**< Maximum length of TFTP "tsize" option value. */ #define OPTION_ERROR_MESSAGE "Unsupported option(s) requested" #define UDP_ERROR_MSG "UDP Error!" #define LENGTH_ERROR_MSG "Invalid packet length!" #define UNINT_ERROR_MSG "Connection reset by peer" #define ACCESS_ERROR_MSG "Access denied (cannot read/write from file)" #define OPTION_SIZE_REQUEST_VALUE "0" /**@brief TFTP Error codes. */ #define ERR_UNDEFINED 0 /**< Not defined, see error message (if any). */ #define ERR_FILE_NOT_FOUND 1 /**< File not found. */ #define ERR_ACCESS_ERROR 2 /**< Access violation. */ #define ERR_STORAGE_FULL 3 /**< Disk full or allocation exceeded. */ #define ERR_INVALID_OP 4 /**< Illegal TFTP operation. */ #define ERR_INVALID_TID 5 /**< Unknown transfer ID. */ #define ERR_FILE_EXISTS 6 /**< File already exists. */ #define ERR_BAD_USER 7 /**< No such user. */ #define ERR_OPTION_REJECT 8 /**< Reject proposed options. */ /**@brief TFTP opcode's. This field specifies type of packet. */ #define TYPE_RRQ 1 /**< Read request (RRQ). */ #define TYPE_WRQ 2 /**< Write request (WRQ). */ #define TYPE_DATA 3 /**< Data (DATA). */ #define TYPE_ACK 4 /**< Acknowledgment (ACK). */ #define TYPE_ERR 5 /**< Error (ERROR). */ #define TYPE_OACK 6 /**< Option Acknowledgment (RRQ/WRQ ACK). */ /** * @defgroup api_param_check API Parameters check macros. * * @details Macros that verify parameters passed to the module in the APIs. These macros * could be mapped to nothing in final versions of code to save execution and size. * DNS6_DISABLE_API_PARAM_CHECK should be set to 0 to enable these checks. * * @{ */ #if (TFTP_DISABLE_API_PARAM_CHECK == 0) /**@brief Verify NULL parameters are not passed to API by application. */ #define NULL_PARAM_CHECK(PARAM) \ if ((PARAM) == NULL) \ { \ return (NRF_ERROR_NULL | IOT_TFTP_ERR_BASE); \ } #else // TFTP_DISABLE_API_PARAM_CHECK #define NULL_PARAM_CHECK(PARAM) #endif // DNS6_DISABLE_API_PARAM_CHECK /**@brief Check err_code, free p_buffer and return on error. */ #define PBUFFER_FREE_IF_ERROR(err_code) \ if (err_code != NRF_SUCCESS) \ { \ (void)iot_pbuffer_free(p_buffer, true); \ return err_code; \ } /**@brief Convert TFTP error code into IOT error with appropriate base. */ #define CONVERT_TO_IOT_ERROR(error_code) \ ((IOT_TFTP_ERR_BASE+0x0040) + NTOHS(error_code)) /**@brief Convert IOT error into TFTP error code by removing TFTP error base. */ #define CONVERT_TO_TFTP_ERROR(error_code) \ (HTONS(err_code - (IOT_TFTP_ERR_BASE+0x0040))) /**@brief Iterator for string list delimited with '\0'. */ typedef struct { char * p_start; /**< Pointer to the beginning of a string. */ char * p_end; /**< Pointer to the end of a string. */ struct curr_struct { char * p_key; /**< Pointer to the last, found key string. */ char * p_value; /**< Pointer to the last, found value string. */ } curr; } option_iter_t; /**@brief Allowed states of a single TFTP instance. */ typedef enum { STATE_FREE = 0, /**< Start state, after calling UDP to allocate socket. */ STATE_IDLE, /**< Socket is allocated, but not used. */ STATE_CONNECTING_RRQ, /**< RRQ packet sent. Waiting for response. */ STATE_CONNECTING_WRQ, /**< WRQ packet sent. Waiting for response. */ STATE_SENDING, /**< Sending file and receiving ACK. */ STATE_SEND_HOLD, /**< Sending held. Waiting for resume call. */ STATE_RECEIVING, /**< Receiving file and sending ACK. */ STATE_RECV_HOLD, /**< Receiving held. Waiting for resume call. */ STATE_RECV_COMPLETE /**< State after receiving last DATA, before sending last ACK packet. There won't be another UDP event to emit IOT_TFTP_EVT_TRANSFER_GET_COMPLETE event, so next resume() should emit that event. */ } tftp_state_t; /**@brief Internal TFTP instance structure. */ typedef struct { iot_tftp_trans_params_t init_params; /**< Connection parameters set during initialization. */ iot_tftp_trans_params_t connect_params; /**< Negotiated Connection parameters. */ udp6_socket_t socket; /**< UDP socket assigned to single instance. */ tftp_state_t state; /**< Integer representing current state of an instance. */ iot_tftp_callback_t callback; /**< User defined callback (passed inside initial parameters structure). */ iot_file_t * p_file; /**< Pointer to destination/source file assigned in get/put call. */ const char * p_path; /**< Path of the file on the remote node. */ uint16_t block_id; /**< ID of last received/sent data block. */ uint16_t src_tid; /**< UDP port used for sending information to the server. */ uint16_t dst_tid; /**< UDP port on which all packets will be sent. At first - dst_port (see below), then reassigned. */ uint16_t dst_port; /**< UDP port on which request packets will be sent. Usually DEFAULT_PORT. */ const char * p_password; /**< Pointer to a constant string containing password passed inside Read/Write Requests. */ ipv6_addr_t addr; /**< IPv6 server address. */ iot_pbuffer_t * p_packet; /**< Reference to the temporary packet buffer. */ uint8_t retries; /**< Number of already performed retries. */ volatile iot_timer_time_in_ms_t request_timeout; /**< Number of milliseconds on which last request should be retransmitted. */ } tftp_instance_t; SDK_MUTEX_DEFINE(m_tftp_mutex) /**< Mutex variable. Currently unused, this declaration does not occupy any space in RAM. */ static tftp_instance_t m_instances[TFTP_MAX_INSTANCES]; /**< Array of allowed TFTP instances. */ /**@brief Function for finding free TFTP instance index. * * @param[out] p_index Index being found. * * @retval NRF_SUCCESS if passed instance was found, else NRF_ERROR_NO_MEM error code will * be returned. */ static uint32_t find_free_instance(uint32_t * p_index) { uint32_t index = 0; for (index = 0; index < TFTP_MAX_INSTANCES; index++) { if (m_instances[index].state == STATE_FREE) { *p_index = index; return NRF_SUCCESS; } } return (NRF_ERROR_NO_MEM | IOT_TFTP_ERR_BASE); } /**@brief Function for resolving instance index by passed pointer. * * @param[in] p_tftp Pointer representing TFTP instance in user space. * @param[out] p_index Index of passed TFTP instance. * * @retval NRF_SUCCESS if passed instance was found, else NRF_ERROR_INVALID_PARAM error code * will be returned. */ static uint32_t find_instance(iot_tftp_t * p_tftp, uint32_t * p_index) { if (*p_tftp > TFTP_MAX_INSTANCES) { return (NRF_ERROR_INVALID_PARAM | IOT_TFTP_ERR_BASE); } *p_index = *p_tftp; return NRF_SUCCESS; } /**@brief Function for notifying application of the TFTP events. * * @param[in] p_tftp TFTP instance. * @param[in] p_evt Event description. * * @retval None. */ static void app_notify(iot_tftp_t * p_tftp, iot_tftp_evt_t * p_evt) { uint32_t index; uint32_t err_code; err_code = find_instance(p_tftp, &index); if (err_code != NRF_SUCCESS) { return; } if (m_instances[index].callback) { TFTP_MUTEX_UNLOCK(); // Call handler of user request. m_instances[index].callback(p_tftp, p_evt); TFTP_MUTEX_LOCK(); } } /**@brief Increment option iterator. * * @details The iterator will point to the next option or to p_end if it reaches the end. * * @param[in] p_iter Pointer to option iterator. * * @retval NRF_SUCCESS if iterator successfully moved to next option, else an error code indicating reason * for failure. */ static uint32_t op_get_next(option_iter_t * p_iter) { uint32_t key_length; uint32_t value_length; NULL_PARAM_CHECK(p_iter->p_start); NULL_PARAM_CHECK(p_iter->p_end); NULL_PARAM_CHECK(p_iter->curr.p_key); NULL_PARAM_CHECK(p_iter->curr.p_value); // If reached end. if ((p_iter->curr.p_value == p_iter->p_end) || (p_iter->curr.p_key == p_iter->p_end)) { return (NRF_ERROR_DATA_SIZE | IOT_TFTP_ERR_BASE); } key_length = strlen(p_iter->curr.p_key); value_length = strlen(p_iter->curr.p_value); if ((p_iter->curr.p_value == p_iter->p_start) && (p_iter->curr.p_key == p_iter->p_start)) { // First call. Check if [start] + [string] fits before [end] reached. // This statement just checks if there is '\0' between start and end (passing single string as input). if (p_iter->curr.p_key + key_length < p_iter->p_end) { p_iter->curr.p_value = p_iter->curr.p_key + key_length + 1; return NRF_SUCCESS; } else { return (NRF_ERROR_DATA_SIZE | IOT_TFTP_ERR_BASE); } } else if (p_iter->curr.p_value + value_length < p_iter->p_end) { p_iter->curr.p_key = p_iter->curr.p_value + value_length + 1; p_iter->curr.p_value = p_iter->curr.p_key + strlen(p_iter->curr.p_key) + 1; if ((*p_iter->curr.p_key == '\0') || (*p_iter->curr.p_value == '\0')) // If string list finishes before the end of the buffer. { p_iter->curr.p_key = p_iter->p_end; p_iter->curr.p_value = p_iter->p_end; return (NRF_ERROR_DATA_SIZE | IOT_TFTP_ERR_BASE); } return NRF_SUCCESS; } else { p_iter->curr.p_key = p_iter->p_end; p_iter->curr.p_value = p_iter->p_end; return (NRF_ERROR_DATA_SIZE | IOT_TFTP_ERR_BASE); } } /**@brief Set new (key, value) pair at the end of a string. * * @param[out] p_iter Pointer to iterator, which will be used to add (key, value) pair. * @param[in] p_inp_key Pointer to the new key string. If p_key is NULL, then this function will insert just value. * @param[in] p_inp_value Pointer to the new value string. * * @retval NRF_SUCCESS on successful execution of procedure, else an error code indicating reason * for failure. */ static __INLINE uint32_t op_set(option_iter_t * p_iter, const char * p_inp_key, const char * p_inp_value) { char * p_last_key; char * p_last_value; NULL_PARAM_CHECK(p_iter->p_start); NULL_PARAM_CHECK(p_iter->p_end); NULL_PARAM_CHECK(p_iter->curr.p_key); NULL_PARAM_CHECK(p_iter->curr.p_value); p_last_key = p_iter->curr.p_key; p_last_value = p_iter->curr.p_value; // Print appropriate trace log. if (p_inp_key != NULL) { TFTP_TRC("Set option: %s with value: %s.", p_inp_key, p_inp_value); } else { TFTP_TRC("Set value: %s.", p_inp_value); } // Set key & value pointers. if ((p_iter->curr.p_key == p_iter->p_start) && (p_iter->curr.p_value == p_iter->p_start)) // Start condition. { if (p_inp_key != NULL) { p_iter->curr.p_value = p_iter->curr.p_key + strlen(p_inp_key) + 1; } else { p_iter->curr.p_value = p_iter->curr.p_key; // Insert only passed value. p_iter->curr.p_key = p_iter->curr.p_value + strlen(p_iter->curr.p_value) + 1; // Just assign anything different that p_start and inside buffer. } } else { p_iter->curr.p_key = p_iter->curr.p_value + strlen(p_iter->curr.p_value) + 1; // New key starts where last value ends. if (p_inp_key != NULL) { p_iter->curr.p_value = p_iter->curr.p_key + strlen(p_inp_key) + 1; // If key not null - new value starts where new key ends. } else { p_iter->curr.p_value = p_iter->curr.p_key; // Otherwise - value is placed at the key position. } } // Copy strings into set pointers. if ((p_iter->curr.p_value + strlen(p_inp_value)) < p_iter->p_end) { if (p_inp_key != NULL) { memcpy(p_iter->curr.p_key, p_inp_key, strlen(p_inp_key) + 1); } memcpy(p_iter->curr.p_value, p_inp_value, strlen(p_inp_value) + 1); } else // If it is not possible to insert new key & value pair. { p_iter->curr.p_key = p_last_key; p_iter->curr.p_value = p_last_value; TFTP_ERR("Unable to set option (size error)!"); return (NRF_ERROR_DATA_SIZE | IOT_TFTP_ERR_BASE); } return NRF_SUCCESS; } /**@brief Initializes new option iterator. * * @param[out] p_iter Pointer to iterator, which will be configured. * @param[in] p_buf Pointer to the new string buffer which iterator will be modifying. * @param[in] buf_len Length of passed buffer. * * @retval NRF_SUCCESS on successful execution of procedure, else an error code indicating reason * for failure. */ static __INLINE void op_init(option_iter_t * p_iter, char * p_buf, uint32_t buf_len) { p_iter->p_start = p_buf; p_iter->p_end = p_buf + buf_len; p_iter->curr.p_key = p_buf; p_iter->curr.p_value = p_buf; } /**@brief: Converts string containing unsigned number into uint32_t. * * @param[in] p_str Input string. * * @retval Integer number equal to read value. Reading process skips all non-digit characters. */ static uint32_t str_to_uint(char * p_str) { uint32_t len; uint32_t ret_val = 0; uint32_t mul = 1; if (p_str == NULL) { return 0; } len = strlen(p_str); while (len) { len--; if ((p_str[len] >= '0') && (p_str[len] <= '9')) // Skip unsupported characters. { ret_val += mul * (p_str[len] - '0'); mul *= 10; } } return ret_val; } /**@brief: Converts unsigned number into string. * * @param[in] number Input number. * @param[out] p_str Pointer to the output string. * @param[in] len Length of the passed output string buffer. * * @retval NRF_SUCCESS on successful execution of procedure, else an error code indicating reason * for failure. */ static uint32_t uint_to_str(uint32_t number, char * p_str, uint16_t len) { uint32_t i = 0; uint32_t temp = number; if (len == 0) { return NRF_ERROR_INVALID_LENGTH; } // Check how many characters will be needed. if (temp == 0) { i = 1; } while (temp) { i++; temp /= 10; } // Set null character and check length. if (i + 1 > len) { p_str[0] = '\0'; return NRF_ERROR_INVALID_LENGTH; } p_str[i] = '\0'; // Set digits. while (i--) { p_str[i] = '0' + number % 10; number /= 10; } return NRF_SUCCESS; } /**@brief Compare strings in a case insensitive way. * * @param[in] p_str1 Pointer to the first string. * @param[in] p_str2 Pointer to the second String. * * @retval If strings are equal returns 0, otherwise number of common characters. */ static uint32_t strcmp_ci(char * p_str1, char* p_str2) { uint32_t min_len = 0; uint32_t str1_len; uint32_t str2_len; uint32_t i = 0; str1_len = strlen(p_str1); str2_len = strlen(p_str2); min_len = str1_len; if (str2_len < str1_len) { min_len = str2_len; } for (i = 0; i < min_len; i++) { char c1 = ((p_str1[i] >= 'a' && p_str1[i] <= 'z') ? p_str1[i] + 'A' - 'a' : p_str1[i]); char c2 = ((p_str2[i] >= 'a' && p_str2[i] <= 'z') ? p_str2[i] + 'A' - 'a' : p_str2[i]); if (c1 != c2) { return i + 1; } } if (str1_len != str2_len) { return i + 1; } return 0; } /**@brief Allocates p_buffer and fills in common fields. * * @param[in] type First field describing packet type. * @param[in] id Second field (Block ID / Error Code). * @param[out] pp_buffer Sets pointer to the newly allocated buffer. * @param[in] payload_len Length of payload (additional fields / data). * * @retval NRF_SUCCESS on successful execution of procedure, else an error code indicating reason * for failure. */ static uint32_t compose_packet(uint16_t type, uint16_t id, iot_pbuffer_t ** pp_buffer, uint32_t payload_len) { uint32_t err_code; iot_pbuffer_alloc_param_t buffer_param; iot_pbuffer_t * p_buffer; uint32_t byte_index; memset(&buffer_param, 0, sizeof(iot_pbuffer_alloc_param_t)); buffer_param.length = TFTP_HEADER_SIZE + TFTP_BLOCK_ID_SIZE + payload_len; buffer_param.type = UDP6_PACKET_TYPE; buffer_param.flags = PBUFFER_FLAG_DEFAULT; err_code = iot_pbuffer_allocate(&buffer_param, &p_buffer); if (err_code != NRF_SUCCESS) { return err_code; } memset(p_buffer->p_payload, 0, buffer_param.length); byte_index = 0; // Insert type opcode. byte_index += uint16_encode(HTONS(type), &p_buffer->p_payload[byte_index]); if (type == TYPE_ERR) { // Insert err code. byte_index += uint16_encode(CONVERT_TO_TFTP_ERROR(id), &p_buffer->p_payload[byte_index]); } else { // Insert block ID. byte_index += uint16_encode(HTONS(id), &p_buffer->p_payload[byte_index]); } *pp_buffer = p_buffer; return NRF_SUCCESS; } /**@brief Reset instance request timer. * * @param[in] index Index of pending instance. * * @retval NRF_SUCCESS on successful execution of procedure, else an error code indicating reason * for failure. */ static uint32_t retr_timer_reset(uint32_t index) { uint32_t err_code; iot_timer_time_in_ms_t wall_clock_value; // Get wall clock time. err_code = iot_timer_wall_clock_get(&wall_clock_value); if (err_code == NRF_SUCCESS) { m_instances[index].request_timeout = wall_clock_value + m_instances[index].connect_params.next_retr * 1000; } return err_code; } /**@brief Function for checking if retransmission time of TFTP instance request has been expired. * * @param[in] index Index of pending instance. * * @retval True if timer has been expired, False otherwise. */ static bool instance_timer_is_expired(uint32_t index) { uint32_t err_code; iot_timer_time_in_ms_t wall_clock_value; // Get wall clock time. err_code = iot_timer_wall_clock_get(&wall_clock_value); if (err_code == NRF_SUCCESS) { if (wall_clock_value >= m_instances[index].request_timeout) { return true; } } return false; } /**@brief Sets all instance values to defaults. */ static void instance_reset(uint32_t index) { m_instances[index].state = STATE_FREE; m_instances[index].init_params.next_retr = 0; m_instances[index].init_params.block_size = TFTP_DEFAULT_BLOCK_SIZE; m_instances[index].connect_params.next_retr = 0; m_instances[index].connect_params.block_size = TFTP_DEFAULT_BLOCK_SIZE; m_instances[index].p_file = NULL; m_instances[index].block_id = 0; m_instances[index].p_packet = NULL; m_instances[index].dst_port = TFTP_DEFAULT_PORT; m_instances[index].dst_tid = TFTP_DEFAULT_PORT; m_instances[index].retries = 0; m_instances[index].request_timeout = 0; m_instances[index].callback = NULL; m_instances[index].src_tid = 0; m_instances[index].p_password = NULL; memset(&m_instances[index].addr, 0, sizeof(ipv6_addr_t)); memset(&m_instances[index].socket, 0, sizeof(udp6_socket_t)); } /**@brief This function creates error packet for specified TFTP instance. * * @param[in] index Index of TFTP instance. * @param[in] p_err_evt Event data structure (message and code). * * @retval NRF_SUCCESS on successful execution of procedure, else an error code indicating reason * for failure. */ static uint32_t send_err_msg(uint32_t index, iot_tftp_evt_err_t * p_err_evt) { iot_pbuffer_t * p_buffer; uint8_t * p_resp_packet; uint32_t msg_len = 0; uint16_t byte_index = 0; uint32_t err_code; TFTP_TRC("Send ERROR packet."); if (p_err_evt->p_msg != NULL) { msg_len = strlen(p_err_evt->p_msg) + 1; } err_code = compose_packet(TYPE_ERR, p_err_evt->code, &p_buffer, msg_len); if (err_code != NRF_SUCCESS) { return err_code; } p_resp_packet = p_buffer->p_payload; byte_index = TFTP_HEADER_SIZE + TFTP_ERR_CODE_SIZE; if (p_err_evt->p_msg != NULL) { memcpy(&p_resp_packet[byte_index], p_err_evt->p_msg, msg_len); byte_index += msg_len; } p_buffer->length = byte_index; TFTP_TRC("Send packet to UDP module."); UNUSED_VARIABLE(retr_timer_reset(index)); err_code = udp6_socket_sendto(&m_instances[index].socket, &m_instances[index].addr, m_instances[index].dst_tid, p_buffer); TFTP_TRC("Recv code: %08lx.", err_code); return err_code; } /**@brief This function creeates error packet for TID error, not being found. * * @param[in] p_socket Socket from which error message is sent. * @param[in] p_addr IPv6 Address to where error message is sent. * @param[in] tid Erronous TID from the sender. * * @retval NRF_SUCCESS on successful execution of procedure, else an error code indicating reason * for failure. */ static uint32_t send_err_tid(const udp6_socket_t * p_socket, const ipv6_addr_t * p_addr, uint16_t tid) { iot_pbuffer_t * p_buffer; uint32_t msg_len = 0; uint16_t byte_index = 0; uint32_t err_code; TFTP_TRC("Send TID ERROR packet."); err_code = compose_packet(TYPE_ERR, ERR_INVALID_TID, &p_buffer, msg_len); if (err_code != NRF_SUCCESS) { return err_code; } byte_index = TFTP_HEADER_SIZE + TFTP_ERR_CODE_SIZE; p_buffer->length = byte_index; TFTP_TRC("Send packet to UDP module."); err_code = udp6_socket_sendto(p_socket, p_addr, tid, p_buffer); TFTP_TRC("Recv code: %08lx.", err_code); return err_code; } /**@brief Sends ACK or next data chunk (block) after calling user callback or when hold timer expires. * * @param[in] p_tftp Pointer to the TFTP instance (from user space). * @param[in] p_evt Pointer to the event structure. Used for sending error messages. * * @retval NRF_SUCCESS on successful execution of procedure, else an error code indicating reason * for failure. */ static uint32_t send_response(iot_tftp_t * p_tftp) { uint32_t index; uint32_t err_code; TFTP_TRC("Send packet."); err_code = find_instance(p_tftp, &index); if (err_code != NRF_SUCCESS) { TFTP_ERR("Cannot find instance (send)!"); return err_code; } switch (m_instances[index].state) { case STATE_IDLE: // Server should go to the CONNECTING state (request received). TFTP_TRC("Inside IDLE state (send). "); return NRF_SUCCESS; case STATE_SENDING: case STATE_RECEIVING: case STATE_SEND_HOLD: case STATE_RECV_HOLD: case STATE_RECV_COMPLETE: // Send DATA/ACK packet. TFTP_TRC("Send packet to UDP module."); UNUSED_VARIABLE(retr_timer_reset(index)); err_code = udp6_socket_sendto(&m_instances[index].socket, &m_instances[index].addr, m_instances[index].dst_tid, m_instances[index].p_packet); TFTP_TRC("Recv code: %08lx.", err_code); return err_code; default: TFTP_ERR("Invalid state (send)!"); return (NRF_ERROR_INVALID_STATE | IOT_TFTP_ERR_BASE); } } /**@brief Aborts TFTP client ongoing procedure. * * @param[in] index Index of TFTP instance. * * @retval NRF_SUCCESS on successful execution of procedure, else an error code indicating reason * for failure. */ void instance_abort(uint32_t index) { uint32_t internal_err; switch (m_instances[index].state) { case STATE_SEND_HOLD: case STATE_RECV_HOLD: // Free pbuffer. internal_err = iot_pbuffer_free(m_instances[index].p_packet, true); if (internal_err != NRF_SUCCESS) { TFTP_ERR("Cannot free pbuffer - %p", m_instances[index].p_packet); } // Close file. internal_err = iot_file_fclose(m_instances[index].p_file); if (internal_err != NRF_SUCCESS) { TFTP_ERR("Cannot close file - %p", m_instances[index].p_file); } break; case STATE_SENDING: case STATE_RECEIVING: // Close file. internal_err = iot_file_fclose(m_instances[index].p_file); if (internal_err != NRF_SUCCESS) { TFTP_ERR("Cannot close file - %p", m_instances[index].p_file); } break; case STATE_CONNECTING_RRQ: case STATE_CONNECTING_WRQ: case STATE_IDLE: case STATE_FREE: default: break; } TFTP_TRC("Reset instance %ld.", index); m_instances[index].state = STATE_IDLE; m_instances[index].block_id = 0; m_instances[index].dst_tid = m_instances[index].dst_port; m_instances[index].retries = 0; m_instances[index].request_timeout = 0; memcpy(&m_instances[index].connect_params, &m_instances[index].init_params, sizeof(iot_tftp_trans_params_t)); } /**@brief Generates event for application. * * @param[in] index Index of TFTP instance. * @param[in] evt_id Id of generated event. * @param[in] p_param Pointer to event parameter union. */ static void handle_evt(uint32_t index, iot_tftp_evt_id_t evt_id, iot_tftp_evt_param_t * p_param) { uint32_t internal_err; iot_tftp_evt_t evt; memset(&evt, 0, sizeof(evt)); evt.id = evt_id; evt.p_file = m_instances[index].p_file; if (p_param != NULL) { evt.param = * p_param; } if (evt_id == IOT_TFTP_EVT_ERROR) { uint32_t err_code = evt.param.err.code; TFTP_TRC("Raise an ERROR event."); if ((err_code & IOT_TFTP_ERR_BASE) == IOT_TFTP_ERR_BASE) { evt.param.err.code = CONVERT_TO_TFTP_ERROR(err_code); internal_err = send_err_msg(index, &evt.param.err); if (internal_err != NRF_SUCCESS) { TFTP_TRC("Cannot send error message to peer %08lx.", internal_err); } } // Restore error code. evt.param.err.code = err_code; // Return to IDLE and notify. instance_abort(index); app_notify(&index, &evt); } else if ((evt_id == IOT_TFTP_EVT_TRANSFER_GET_COMPLETE) || (evt_id == IOT_TFTP_EVT_TRANSFER_PUT_COMPLETE)) { TFTP_TRC("Raise TRANSFER COMPLETE event."); if (m_instances[index].state == STATE_RECV_HOLD) { TFTP_TRC("Holding last ACK transfer."); m_instances[index].state = STATE_RECV_COMPLETE; } else if (m_instances[index].state != STATE_RECV_COMPLETE) { // Skip into IDLE state. instance_abort(index); app_notify(&index, &evt); } } else if (evt_id == IOT_TFTP_EVT_TRANSFER_DATA_RECEIVED) { app_notify(&index, &evt); } } /**@brief Generates error event for application. * * @param[in] index Index of TFTP instance. * @param[in] err_code Code of error event. * @param[in] p_msg Character string containing error message. */ static void handle_evt_err(uint32_t index, uint32_t err_code, char * p_msg) { iot_tftp_evt_param_t evt_param; memset(&evt_param, 0, sizeof(evt_param)); evt_param.err.code = err_code; evt_param.err.p_msg = p_msg; evt_param.err.size_transfered = m_instances[index].block_id * m_instances[index].connect_params.block_size; handle_evt(index, IOT_TFTP_EVT_ERROR, &evt_param); } /**@brief: Find instance number by Transmission ID (UDP source port). * * @param[in] port UDP port number on which new message was received. * @param[out] p_index Index of found TFTP instance assigned to the passed UDP port. * * @retval NRF_SUCCESS on successful execution of procedure, else an error code indicating reason * for failure. */ static __INLINE uint32_t find_instance_by_tid(uint16_t port, uint32_t * p_index) { uint32_t index; for (index = 0; index < TFTP_MAX_INSTANCES; index++) { if (m_instances[index].src_tid == port) { break; } } if (index == TFTP_MAX_INSTANCES) { return (NRF_ERROR_NOT_FOUND | IOT_TFTP_ERR_BASE); } *p_index = index; return NRF_SUCCESS; } /**@brief: Negotiation procedure. This function skips through input option string and modifies instance parameters according to negotiated values. * * @param[in] p_iter Pointer to iterator configured to parse RQ/OACK packet. * @param[out] p_instance Pointer to the instance, which parameters should be negotiated. * * @retval NRF_SUCCESS on successful execution of procedure, else TFTP_OPTION_REJECT error indicating * that server/client requests unsupported option values. */ static uint32_t option_negotiate(option_iter_t * p_iter, tftp_instance_t * p_instance) { uint32_t err_code; bool op_size_set = false; bool op_blksize_set = false; bool op_time_set = false; TFTP_TRC("Negotiate options:"); if (p_iter != NULL) // If NULL passed - reset option values to those defined by RFC. { UNUSED_VARIABLE(op_get_next(p_iter)); while (p_iter->curr.p_key != p_iter->p_end) { if (strcmp_ci(p_iter->curr.p_key, OPTION_TIMEOUT) == 0) { if (p_instance->init_params.next_retr != 0) { uint16_t server_time = str_to_uint(p_iter->curr.p_value); if (server_time < p_instance->init_params.next_retr) // Take minimum. { p_instance->connect_params.next_retr = server_time; } else { p_instance->connect_params.next_retr = p_instance->init_params.next_retr; } op_time_set = true; TFTP_TRC("TIMEOUT: %ld", p_instance->connect_params.next_retr); } } else if (strcmp_ci(p_iter->curr.p_key, OPTION_SIZE) == 0) { if (p_instance->p_file != NULL) { uint32_t file_size = str_to_uint(p_iter->curr.p_value); err_code = iot_file_fopen(p_instance->p_file, file_size); if (err_code != NRF_SUCCESS) { TFTP_TRC(" TSIZE: REJECT!"); return TFTP_OPTION_REJECT; } op_size_set = true; TFTP_TRC(" TSIZE: %ld", file_size); } } else if (strcmp_ci(p_iter->curr.p_key, OPTION_BLKSIZE) == 0) { uint16_t block_size = str_to_uint(p_iter->curr.p_value); op_blksize_set = true; if (p_instance->init_params.block_size >= block_size) { p_instance->connect_params.block_size = block_size; } else { TFTP_TRC("BLKSIZE: REJECT!"); return TFTP_OPTION_REJECT; } TFTP_TRC("BLKSIZE: %d", p_instance->connect_params.block_size); } else if ((strlen(p_iter->curr.p_key) > 0) && (p_iter->curr.p_value == p_iter->p_end)) { // Password option. TFTP_TRC("PASSWORD FOUND: %s", p_iter->curr.p_key); p_iter->curr.p_key = p_iter->p_end; } else { TFTP_TRC("UNKNOWN OPTION"); } UNUSED_VARIABLE(op_get_next(p_iter)); } } // Set values of not negotiated options. if (!op_size_set && p_instance->p_file != NULL) { err_code = iot_file_fopen(p_instance->p_file, 0);// Open with default file size (not known). if (err_code != NRF_SUCCESS) { TFTP_TRC("TSIZE: REJECT!"); return TFTP_OPTION_REJECT; } TFTP_TRC("TSIZE: %ld", p_instance->p_file->file_size); } if (!op_blksize_set) { if ((p_instance->init_params.block_size < TFTP_DEFAULT_BLOCK_SIZE) && (p_instance->init_params.block_size != 0)) { TFTP_TRC("BLKSIZE: REJECT!"); return TFTP_OPTION_REJECT; } else { p_instance->connect_params.block_size = TFTP_DEFAULT_BLOCK_SIZE; } TFTP_TRC("BLKSIZE: %d", p_instance->connect_params.block_size); } if (!op_time_set) { p_instance->connect_params.next_retr = p_instance->init_params.next_retr; TFTP_TRC("TIMEOUT: %ld", p_instance->connect_params.next_retr); } return NRF_SUCCESS; } /**@brief This function holds ongoing transmission of TFTP. * * @param[in] index Index of TFTP instance. * * @retval NRF_SUCCESS on successful execution of procedure, else an error code indicating reason * for failure. */ static uint32_t transfer_hold(uint32_t index) { if (m_instances[index].state == STATE_SENDING) { m_instances[index].state = STATE_SEND_HOLD; } else if (m_instances[index].state == STATE_RECEIVING) { m_instances[index].state = STATE_RECV_HOLD; } else if (m_instances[index].state == STATE_RECV_COMPLETE) { m_instances[index].state = STATE_RECV_COMPLETE; } else { TFTP_ERR("Hold called in invalid state."); return (NRF_ERROR_INVALID_STATE | IOT_TFTP_ERR_BASE); } return NRF_SUCCESS; } /**@brief This function resumes ongoing transmission of TFTP. * * @param[in] index Index of TFTP instance. * * @retval NRF_SUCCESS on successful execution of procedure, else an error code indicating reason * for failure. */ static uint32_t transfer_resume(uint32_t index) { uint32_t err_code = NRF_SUCCESS; if ((m_instances[index].state != STATE_SEND_HOLD) && (m_instances[index].state != STATE_RECV_HOLD) && (m_instances[index].state != STATE_RECV_COMPLETE)) { TFTP_ERR("Failed due to invalid state."); TFTP_EXIT(); return (NRF_ERROR_INVALID_STATE | IOT_TFTP_ERR_BASE); } err_code = send_response(&index); if (err_code != NRF_SUCCESS) { TFTP_ERR("Failed to send packet after resume."); } if (m_instances[index].state == STATE_SEND_HOLD) { m_instances[index].state = STATE_SENDING; } else if (m_instances[index].state == STATE_RECV_HOLD) { m_instances[index].state = STATE_RECEIVING; } else if (m_instances[index].state == STATE_RECV_COMPLETE) { m_instances[index].state = STATE_RECEIVING; TFTP_ERR("Complete due to STATE_RECV_COMPLETE state."); handle_evt(index, IOT_TFTP_EVT_TRANSFER_GET_COMPLETE, NULL); } return err_code; } /**@brief This function creates ACK packet for specified TFTP instance. * * @param[in] index Index of TFTP instance. * @param[in] block_id Data block ID to be acknowledged. * * @retval NRF_SUCCESS on successful execution of procedure, else an error code indicating reason * for failure. */ static uint32_t create_ack_packet(uint32_t index, uint16_t block_id) { uint32_t err_code; TFTP_ENTRY(); // Reuse p_packet pointer for a new (response) packet. Previous one will be automatically freed by IPv6 module. err_code = compose_packet(TYPE_ACK, block_id, &m_instances[index].p_packet, 0); return err_code; } /**@brief This function creates data packet for specified TFTP instance. * * @param[in] index Index of TFTP instance. * @param[in] block_id Data block ID to be sent. * * @retval NRF_SUCCESS on successful execution of procedure, else an error code indicating reason * for failure. */ static uint32_t create_data_packet(uint32_t index, uint16_t block_id) { uint32_t err_code; uint32_t internal_err; uint32_t cursor; uint32_t byte_index; iot_pbuffer_t * p_buffer = NULL; TFTP_ENTRY(); if (m_instances[index].p_file == NULL) { TFTP_ERR("[TFTP]: Error while sending response. Reason: Missing file to send.\r\n"); return NRF_ERROR_INVALID_DATA; } // If ACK block ID doesn't match last sent packet number. if (m_instances[index].block_id != block_id) { // fseek on file to move to the right point. If fseek returns error - whole transmission will be dropped. err_code = iot_file_fseek(m_instances[index].p_file, (block_id) * m_instances[index].connect_params.block_size); if (err_code != NRF_SUCCESS) { TFTP_ERR("Unable to fseek on input data file!"); } else { m_instances[index].block_id = block_id + 1; } } else { m_instances[index].block_id = block_id + 1; } cursor = (m_instances[index].block_id - 1) * m_instances[index].connect_params.block_size; // If previous DATA packet wasn't fully filled. if (cursor > m_instances[index].p_file->file_size) { TFTP_TRC("Transfer complete. Don't send data."); handle_evt(index, IOT_TFTP_EVT_TRANSFER_PUT_COMPLETE, NULL); return NRF_SUCCESS; } else if (cursor + m_instances[index].connect_params.block_size > m_instances[index].p_file->file_size) // If current sendto operation will send all remaining data. { TFTP_TRC("Send last data packet."); err_code = compose_packet(TYPE_DATA, m_instances[index].block_id, &p_buffer, m_instances[index].p_file->file_size - cursor); } else { TFTP_TRC("Send regular data packet."); err_code = compose_packet(TYPE_DATA, m_instances[index].block_id, &p_buffer, m_instances[index].connect_params.block_size); } if (err_code != NRF_SUCCESS) { return err_code; } TFTP_TRC("Created packet:"); TFTP_TRC("length: %ld", p_buffer->length); TFTP_TRC(" type: %d", p_buffer->p_payload[0] * 256 + p_buffer->p_payload[1]); TFTP_TRC(" ID: %d", p_buffer->p_payload[2] * 256 + p_buffer->p_payload[3]); byte_index = TFTP_HEADER_SIZE + TFTP_BLOCK_ID_SIZE; // Save reference to correctly filled packet buffer. m_instances[index].p_packet = p_buffer; if (p_buffer->length - TFTP_HEADER_SIZE - TFTP_BLOCK_ID_SIZE != 0) { TFTP_MUTEX_UNLOCK(); // Hold transfer. internal_err = transfer_hold(index); if (internal_err != NRF_SUCCESS) { TFTP_ERR("Error while holding the transfer. Reason: %08lx.", internal_err); } err_code = iot_file_fread(m_instances[index].p_file, &(p_buffer->p_payload[byte_index]), p_buffer->length - TFTP_HEADER_SIZE - TFTP_BLOCK_ID_SIZE); // Unlock instance if file has not assigned callback (probably no needs more time to perform read/write). if (m_instances[index].p_file->p_callback == NULL) { internal_err = transfer_resume(index); if (internal_err != NRF_SUCCESS) { TFTP_ERR("Error while resuming the transfer. Reason: %08lx.", internal_err); } } TFTP_MUTEX_LOCK(); } else { // TFTP is going to send empty data packet, so file callback won't be called. internal_err = send_response(&index); if (internal_err != NRF_SUCCESS) { TFTP_ERR("Error while sending response. Reason: %08lx.", internal_err); } } if (err_code != NRF_SUCCESS) { TFTP_ERR("Failed to save received data (fread)!"); handle_evt_err(index, TFTP_ACCESS_DENIED, ACCESS_ERROR_MSG); } return err_code; } /**@brief Callback handler to receive data on the UDP port. * * @param[in] p_socket Socket identifier. * @param[in] p_ip_header IPv6 header containing source and destination addresses. * @param[in] p_udp_header UDP header identifying local and remote endpoints. * @param[in] process_result Result of data reception, there could be possible errors like * invalid checksum etc. * @param[in] p_rx_packet Packet buffer containing the received data packet. * * @retval NRF_SUCCESS Indicates received data was handled successfully, else an an * error code indicating reason for failure.. */ static uint32_t client_process(const udp6_socket_t * p_socket, const ipv6_header_t * p_ip_header, const udp6_header_t * p_udp_header, uint32_t process_result, iot_pbuffer_t * p_rx_packet) { uint32_t index; iot_tftp_evt_t evt; uint8_t * p_new_packet; uint32_t byte_index; uint32_t err_code; uint32_t internal_err; uint16_t packet_opcode; option_iter_t oack_iter; uint16_t recv_block_id; TFTP_ENTRY(); TFTP_MUTEX_LOCK(); err_code = find_instance_by_tid(p_udp_header->destport, &index); if (err_code != NRF_SUCCESS) { TFTP_ERR("Unable to find TFTP instance associated with given UDP port."); // Send TID error to the server. err_code = send_err_tid(p_socket, &p_ip_header->srcaddr, p_udp_header->destport); TFTP_EXIT(); TFTP_MUTEX_UNLOCK(); return err_code; } // Check UDP status code. if (process_result != NRF_SUCCESS) { TFTP_ERR("UDP error!"); evt.id = IOT_TFTP_EVT_ERROR; evt.param.err.code = process_result; evt.param.err.p_msg = UDP_ERROR_MSG; // Call user callback (inform about error). app_notify(&index, &evt); TFTP_EXIT(); TFTP_MUTEX_UNLOCK(); return process_result; } // Check packet length. if (p_rx_packet->length < TFTP_HEADER_SIZE + TFTP_BLOCK_ID_SIZE) { TFTP_ERR("Invalid packet length!"); evt.id = IOT_TFTP_EVT_ERROR; evt.param.err.code = TFTP_INVALID_PACKET; evt.param.err.p_msg = (char *)"Invalid packet length!"; app_notify(&index, &evt); TFTP_EXIT(); TFTP_MUTEX_UNLOCK(); return evt.param.err.code; } // Read received packet type. p_new_packet = p_rx_packet->p_payload; packet_opcode = uint16_decode(p_new_packet); packet_opcode = NTOHS(packet_opcode); byte_index = TFTP_HEADER_SIZE; if ((m_instances[index].state == STATE_SEND_HOLD) || (m_instances[index].state == STATE_RECV_HOLD) || (m_instances[index].state == STATE_IDLE)) { TFTP_TRC("Ignore packets in HOLD/IDLE states."); TFTP_MUTEX_UNLOCK(); TFTP_EXIT(); return NRF_SUCCESS; // Ignore retransmission of other side. } switch (packet_opcode) { case TYPE_OACK: if ((m_instances[index].state != STATE_CONNECTING_RRQ) && (m_instances[index].state != STATE_CONNECTING_WRQ) && (m_instances[index].state != STATE_IDLE && m_instances[index].retries > 0)) { TFTP_ERR("Invalid TFTP instance state!"); break; } op_init(&oack_iter, (char *)&p_new_packet[byte_index], p_rx_packet->length - TFTP_HEADER_SIZE); // Options uses whole packet except opcode. TFTP_TRC("Received OACK."); err_code = option_negotiate(&oack_iter, &m_instances[index]); if (err_code != NRF_SUCCESS) { TFTP_ERR("Failed to negotiate options!"); handle_evt_err(index, TFTP_OPTION_REJECT, OPTION_ERROR_MESSAGE); break; } // Set server transmission id. m_instances[index].dst_tid = p_udp_header->srcport; if (m_instances[index].state == STATE_CONNECTING_RRQ) { m_instances[index].p_packet = p_rx_packet; m_instances[index].state = STATE_RECEIVING; err_code = create_ack_packet(index, 0); if (err_code == NRF_SUCCESS) { err_code = send_response(&index); } } else if (m_instances[index].state == STATE_CONNECTING_WRQ) { m_instances[index].state = STATE_SENDING; err_code = create_data_packet(index, 0); } else { TFTP_ERR("Incorrect state to receive OACK!"); } if (err_code != NRF_SUCCESS) { TFTP_ERR("Failed to create packet!"); handle_evt_err(index, err_code, NULL); } break; case TYPE_ACK: recv_block_id = uint16_decode(&p_new_packet[byte_index]); recv_block_id = NTOHS(recv_block_id); if (m_instances[index].state == STATE_CONNECTING_WRQ) { TFTP_TRC("Options not supported. Received ACK."); err_code = option_negotiate(NULL, &m_instances[index]); if (err_code != NRF_SUCCESS) { TFTP_ERR("Failed to negotiate default options!"); handle_evt_err(index, TFTP_OPTION_REJECT, OPTION_ERROR_MESSAGE); break; } // Set server transmission id. m_instances[index].dst_tid = p_udp_header->srcport; // Set instance state. m_instances[index].state = STATE_SENDING; m_instances[index].block_id = 0; } if (m_instances[index].state == STATE_SENDING || m_instances[index].state == STATE_RECEIVING) { if (recv_block_id == m_instances[index].block_id) { m_instances[index].retries = 0; } TFTP_TRC("Received ACK. Send block %4d of %ld.", m_instances[index].block_id + 1, CEIL_DIV(m_instances[index].p_file->file_size, m_instances[index].connect_params.block_size) + ((m_instances[index].p_file->file_size % m_instances[index].connect_params.block_size == 0) ? 0 : 1)); err_code = create_data_packet(index, recv_block_id); if ((err_code != (NRF_ERROR_DATA_SIZE | IOT_TFTP_ERR_BASE)) && (err_code != NRF_SUCCESS)) { TFTP_ERR("Failed to create data packet."); handle_evt_err(index, err_code, NULL); } } else { TFTP_ERR("Invalid state to receive ACK packet."); TFTP_MUTEX_UNLOCK(); TFTP_EXIT(); return NRF_SUCCESS; } break; case TYPE_DATA: recv_block_id = uint16_decode(&p_new_packet[byte_index]); recv_block_id = NTOHS(recv_block_id); byte_index += TFTP_BLOCK_ID_SIZE; if (m_instances[index].state == STATE_CONNECTING_RRQ) { TFTP_TRC("Options not supported. Received DATA."); err_code = option_negotiate(NULL, &m_instances[index]); if (err_code != NRF_SUCCESS) { TFTP_ERR("Failed to set default options."); handle_evt_err(index, TFTP_OPTION_REJECT, OPTION_ERROR_MESSAGE); break; } if (recv_block_id == 1) // Received first data block. { m_instances[index].block_id = 0; m_instances[index].state = STATE_RECEIVING; } // Set server transmission id. m_instances[index].dst_tid = p_udp_header->srcport; } if (m_instances[index].state == STATE_RECEIVING) { TFTP_TRC("Received DATA."); m_instances[index].p_packet = p_rx_packet; if (recv_block_id == m_instances[index].block_id + 1) { TFTP_TRC("Received next DATA (n+1)."); m_instances[index].retries = 0; err_code = create_ack_packet(index, m_instances[index].block_id + 1); if (err_code != NRF_SUCCESS) { TFTP_ERR("Failed to create ACK packet!"); handle_evt_err(index, err_code, NULL); break; } } else { TFTP_TRC("Skip current DATA packet. Try to request proper block ID by sending ACK."); err_code = create_ack_packet(index, m_instances[index].block_id); if (err_code == NRF_SUCCESS) { err_code = send_response(&index); } else { TFTP_ERR("Failed to create ACK packet."); handle_evt_err(index, err_code, NULL); } } // Check if payload size is smaller than defined block size. if ((p_rx_packet->length - TFTP_BLOCK_ID_SIZE - TFTP_HEADER_SIZE) < m_instances[index].connect_params.block_size) { m_instances[index].state = STATE_RECV_COMPLETE; } else if (err_code != NRF_SUCCESS) { TFTP_ERR("Failed to create ACK packet."); handle_evt_err(index, err_code, NULL); break; } TFTP_TRC("Send block %4d of %ld ACK.", m_instances[index].block_id, m_instances[index].p_file->file_size / m_instances[index].connect_params.block_size); if (recv_block_id == m_instances[index].block_id + 1) { m_instances[index].block_id = recv_block_id; TFTP_MUTEX_UNLOCK(); if (p_rx_packet->length - byte_index > 0) { iot_tftp_evt_param_t evt_param; memset(&evt_param, 0, sizeof(evt_param)); evt_param.data_received.p_data = &p_new_packet[byte_index]; evt_param.data_received.size = p_rx_packet->length - byte_index; internal_err = transfer_hold(index); if (internal_err != NRF_SUCCESS) { TFTP_ERR("Error while holding the transfer. Reason: %08lx.", internal_err); } if (m_instances[index].p_file != NULL) { err_code = iot_file_fwrite(m_instances[index].p_file, evt_param.data_received.p_data, evt_param.data_received.size); } handle_evt(index, IOT_TFTP_EVT_TRANSFER_DATA_RECEIVED, &evt_param); // Unlock instance if file has not assigned callback (probably not needs more time to perform read/write). if (m_instances[index].p_file == NULL || m_instances[index].p_file->p_callback == NULL) { internal_err = transfer_resume(index); if (internal_err != NRF_SUCCESS) { TFTP_ERR("Error while resuming the transfer. Reason: %08lx.", internal_err); } } } else { if (m_instances[index].p_file != NULL) { err_code = iot_file_fclose(m_instances[index].p_file); } internal_err = send_response(&index); if (internal_err != NRF_SUCCESS) { TFTP_ERR("Error while sending response. Reason: %08lx.", internal_err); } TFTP_ERR("Complete due to packet length. (%ld: %ld)", p_rx_packet->length, byte_index); m_instances[index].state = STATE_RECEIVING; handle_evt(index, IOT_TFTP_EVT_TRANSFER_GET_COMPLETE, NULL); } TFTP_MUTEX_LOCK(); if (err_code != NRF_SUCCESS) { TFTP_ERR("Failed to save received data (fwrite)!"); handle_evt_err(index, TFTP_ACCESS_DENIED, ACCESS_ERROR_MSG); break; } } } else { TFTP_ERR("Invalid state to receive DATA packet."); TFTP_MUTEX_UNLOCK(); TFTP_EXIT(); return NRF_SUCCESS; } break; case TYPE_ERR: recv_block_id = uint16_decode(&p_new_packet[byte_index]); recv_block_id = NTOHS(recv_block_id); byte_index += TFTP_ERR_CODE_SIZE; TFTP_ERR("Received error packet!"); evt.id = IOT_TFTP_EVT_ERROR; evt.param.err.code = CONVERT_TO_IOT_ERROR(recv_block_id); if (p_rx_packet->length > TFTP_HEADER_SIZE + TFTP_ERR_CODE_SIZE) { evt.param.err.p_msg = (char *) &p_new_packet[byte_index]; p_new_packet[p_rx_packet->length-1] = '\0'; } app_notify(&index, &evt); TFTP_MUTEX_UNLOCK(); TFTP_EXIT(); if (evt.param.err.code != TFTP_INVALID_TID) { instance_abort(index); } return evt.param.err.code; default: TFTP_ERR("Invalid TFTP packet opcode!"); handle_evt_err(index, TFTP_INVALID_PACKET, NULL); break; } TFTP_EXIT(); return err_code; } /**@brief Count how many bytes are required to store options inside request packet. * * @param[in] index Index of TFTP instance. * @param[in] type Type of TFTP request. * * @retval NRF_SUCCESS on successful execution of procedure, else an error code indicating reason * for failure. */ static uint32_t count_options_length(uint32_t index, uint32_t type) { uint32_t op_length = 0; char next_retr_str[NEXT_RETR_MAX_LENGTH]; char block_size_str[BLKSIZE_MAX_LENGTH]; char file_size_str[FILE_SIZE_MAX_LENGTH]; if ((m_instances[index].init_params.next_retr > 0) && (m_instances[index].init_params.next_retr < 256)) { UNUSED_VARIABLE(uint_to_str(m_instances[index].init_params.next_retr, next_retr_str, NEXT_RETR_MAX_LENGTH)); op_length += sizeof(OPTION_TIMEOUT); // Time out option length. op_length += strlen(next_retr_str) + 1; // The '\0' character ate the end of a string. } if ((m_instances[index].init_params.block_size > 0) && (m_instances[index].init_params.block_size != TFTP_DEFAULT_BLOCK_SIZE)) { UNUSED_VARIABLE(uint_to_str(m_instances[index].init_params.block_size, block_size_str, sizeof(block_size_str))); op_length += sizeof(OPTION_BLKSIZE); // Time out option length. op_length += strlen(block_size_str) + 1; // The '\0' character ate the end of a string. } op_length += sizeof(OPTION_SIZE); // TFTP tsize option length. if (type == TYPE_RRQ) { op_length += sizeof(OPTION_SIZE_REQUEST_VALUE); // Just send 0 to inform other side that you support this option and would like to receive file size inside OptionACK. } if (type == TYPE_WRQ) { UNUSED_VARIABLE(uint_to_str(m_instances[index].p_file->file_size, file_size_str, sizeof(file_size_str))); op_length += strlen(file_size_str) + 1; } if (m_instances[index].p_password != NULL) { if (m_instances[index].p_password[0] != '\0') { op_length += strlen(m_instances[index].p_password) + 1; // Password is always sent as last string without option name. } } return op_length; } /**@brief This function inserts options into request packet payload using passed option iterator. * * @param[in] index Index of TFTP instance. * @param[in] type Type of TFTP request. * @param[in] p_iter Iterator which will be used to set options. * * @retval NRF_SUCCESS on successful execution of procedure, else an error code indicating reason * for failure. */ static uint32_t insert_options(uint32_t index, uint32_t type, option_iter_t * p_iter) { uint32_t err_code = NRF_SUCCESS; char next_retr_str[NEXT_RETR_MAX_LENGTH]; char block_size_str[BLKSIZE_MAX_LENGTH]; char file_size_str[FILE_SIZE_MAX_LENGTH]; if (type == TYPE_RRQ) { err_code = op_set(p_iter, OPTION_SIZE, OPTION_SIZE_REQUEST_VALUE); } if (type == TYPE_WRQ) { UNUSED_VARIABLE(uint_to_str(m_instances[index].p_file->file_size, file_size_str, FILE_SIZE_MAX_LENGTH)); err_code = op_set(p_iter, OPTION_SIZE, file_size_str); } if (err_code == NRF_SUCCESS) { if ((m_instances[index].init_params.next_retr > 0) && (m_instances[index].init_params.next_retr < 256)) { UNUSED_VARIABLE(uint_to_str(m_instances[index].init_params.next_retr, next_retr_str, NEXT_RETR_MAX_LENGTH)); err_code = op_set(p_iter, OPTION_TIMEOUT, next_retr_str); if (err_code != NRF_SUCCESS) { return err_code; } } if ((m_instances[index].init_params.block_size > 0) && (m_instances[index].init_params.block_size != TFTP_DEFAULT_BLOCK_SIZE)) { UNUSED_VARIABLE(uint_to_str(m_instances[index].init_params.block_size, block_size_str, BLKSIZE_MAX_LENGTH)); err_code = op_set(p_iter, OPTION_BLKSIZE, block_size_str); if (err_code != NRF_SUCCESS) { return err_code; } } if (m_instances[index].p_password != NULL) { if (m_instances[index].p_password[0] != '\0') { err_code = op_set(p_iter, NULL, m_instances[index].p_password); } } } return err_code; } /**@Sends Read/Write TFTP Request. * * @param[in] type Type of request (allowed values: TYPE_RRQ and TYPE_WRQ). * @param[in] p_tftp Pointer to the TFTP instance (from user space). * @param[in] p_file Pointer to the file, which should be assigned to passed instance. * @param[in] p_params Pointer to transmission parameters structure (retransmission time, block size). * * @retval NRF_SUCCESS on successful execution of procedure, else an error code indicating reason * for failure. */ static uint32_t send_request(uint32_t type, iot_tftp_t * p_tftp, iot_file_t * p_file, const char * p_path) { uint32_t err_code; iot_pbuffer_t * p_buffer; iot_pbuffer_alloc_param_t buffer_param; uint8_t * p_rrq_packet; option_iter_t rrq_iter; uint32_t index; uint32_t byte_index; err_code = find_instance(p_tftp, &index); if (err_code != NRF_SUCCESS) { return err_code; } if (p_path == NULL || p_path[0] == '\0') { TFTP_ERR("[TFTP]: Invalid path passed.\r\n"); return (NRF_ERROR_INVALID_PARAM | IOT_TFTP_ERR_BASE); } if (p_file != NULL && p_file->p_filename[0] == '\0') { TFTP_ERR("Invalid file name passed!"); return (NRF_ERROR_INVALID_PARAM | IOT_TFTP_ERR_BASE); } if (m_instances[index].state != STATE_IDLE) { TFTP_ERR("Invalid instance state!"); return (NRF_ERROR_INVALID_STATE | IOT_TFTP_ERR_BASE); } // Assign file with TFTP instance. m_instances[index].p_file = p_file; m_instances[index].p_path = p_path; m_instances[index].block_id = 0; m_instances[index].dst_tid = m_instances[index].dst_port; memset(&buffer_param, 0, sizeof(buffer_param)); buffer_param.type = UDP6_PACKET_TYPE; buffer_param.flags = PBUFFER_FLAG_DEFAULT; // Calculate size of required packet. buffer_param.length = TFTP_HEADER_SIZE; // Bytes reserved for TFTP opcode value. buffer_param.length += strlen(p_path) + 1; // File name with '\0' character. buffer_param.length += sizeof(OPTION_MODE_OCTET); // Mode option value length. TFTP_TRC("Estimated packet length without options: %ld.", buffer_param.length); buffer_param.length += count_options_length(index, type); // TFTP options. TFTP_TRC("Estimated packet length with options: %ld.", buffer_param.length); // Allocate packet buffer. err_code = iot_pbuffer_allocate(&buffer_param, &p_buffer); if (err_code != NRF_SUCCESS) { return err_code; } memset(p_buffer->p_payload, 0, buffer_param.length); byte_index = 0; // Compose TFTP Read Request according to configuration. p_rrq_packet = p_buffer->p_payload; uint16_t size = uint16_encode(HTONS(type), &p_rrq_packet[byte_index]); byte_index += size; // Initialization of option iterator. op_init(&rrq_iter, (char *)&p_rrq_packet[byte_index], buffer_param.length - TFTP_HEADER_SIZE); // Options uses whole packet except opcode. rrq_iter.p_start[0] = '\0'; // Insert file path and mode strings. err_code = op_set(&rrq_iter, NULL, p_path); PBUFFER_FREE_IF_ERROR(err_code); err_code = op_set(&rrq_iter, NULL, OPTION_MODE_OCTET); PBUFFER_FREE_IF_ERROR(err_code); // Insert TFTP options into packet. err_code = insert_options(index, type, &rrq_iter); PBUFFER_FREE_IF_ERROR(err_code); // Change instance status to connecting. if (type == TYPE_RRQ) { m_instances[index].state = STATE_CONNECTING_RRQ; } else { m_instances[index].state = STATE_CONNECTING_WRQ; } // Send read request. UNUSED_VARIABLE(retr_timer_reset(index)); err_code = udp6_socket_sendto(&m_instances[index].socket, &m_instances[index].addr, m_instances[index].dst_tid, p_buffer); if (err_code != NRF_SUCCESS) { TFTP_ERR("Unable to send request!"); m_instances[index].state = STATE_IDLE; } PBUFFER_FREE_IF_ERROR(err_code); return NRF_SUCCESS; } /**@brief Function used in order to change initial connection parameters. */ uint32_t iot_tftp_set_params(iot_tftp_t * p_tftp, iot_tftp_trans_params_t * p_params) { uint32_t err_code; uint32_t index; NULL_PARAM_CHECK(p_params); NULL_PARAM_CHECK(p_tftp); TFTP_ENTRY(); TFTP_MUTEX_LOCK(); err_code = find_instance(p_tftp, &index); if (err_code == NRF_SUCCESS) { // Modifying connection parameters could be done only when TFTP instance is disconnected. // NOTE: STATE_FREE is not allowed, because there have to be a moment (e.g. after calling iot_tftp_init()) when transmission parameters were set to default values. if (m_instances[index].state == STATE_IDLE) { // Reset connection parameters to initial values. They will be set (negotiated) after get/put call. memcpy(&m_instances[index].init_params, p_params, sizeof(iot_tftp_trans_params_t)); } else { err_code = (NRF_ERROR_INVALID_STATE | IOT_TFTP_ERR_BASE); TFTP_ERR("Cannot modify connection parameters inside %d state!", m_instances[index].state); } } else { TFTP_ERR("Unable to find TFTP instance!"); } TFTP_MUTEX_UNLOCK(); TFTP_EXIT(); return err_code; } /**@brief Function for performing retransmissions of TFTP acknowledgments. */ void iot_tftp_timeout_process(iot_timer_time_in_ms_t wall_clock_value) { uint32_t index; uint32_t err_code; UNUSED_PARAMETER(wall_clock_value); TFTP_MUTEX_LOCK(); for (index = 0; index < TFTP_MAX_INSTANCES; index++) { if ((m_instances[index].state == STATE_CONNECTING_RRQ) || (m_instances[index].state == STATE_CONNECTING_WRQ) || (m_instances[index].state == STATE_SENDING) || (m_instances[index].state == STATE_RECEIVING)) { TFTP_ENTRY(); TFTP_TRC("Current timer: %ld, %ld", m_instances[index].request_timeout, wall_clock_value); if (instance_timer_is_expired(index)) { err_code = NRF_SUCCESS; if (m_instances[index].retries < TFTP_MAX_RETRANSMISSION_COUNT) { TFTP_TRC("Query retransmission [%d] for file %s.", m_instances[index].retries, m_instances[index].p_file->p_filename); // Increase retransmission number. m_instances[index].retries++; TFTP_TRC("Compose packet for retransmission."); // Send packet again. if (m_instances[index].state == STATE_RECEIVING) { TFTP_TRC("Retransmission of ACK packet."); err_code = create_ack_packet(index, m_instances[index].block_id); if (err_code == NRF_SUCCESS) { err_code = send_response(&index); } else { TFTP_ERR("Failed to create packet!"); handle_evt_err(index, err_code, NULL); } } else if (m_instances[index].state == STATE_SENDING) { TFTP_TRC("Retransmission of DATA packet."); err_code = create_data_packet(index, m_instances[index].block_id - 1); if (err_code == NRF_SUCCESS) { if (m_instances[index].p_file->p_callback == NULL) { err_code = send_response(&index); } } else { TFTP_ERR("Failed to create packet!"); handle_evt_err(index, err_code, NULL); } } else if (m_instances[index].state == STATE_CONNECTING_RRQ) { TFTP_TRC("OACK time out. Retransmit RRQ."); m_instances[index].state = STATE_IDLE; err_code = send_request(TYPE_RRQ, &index, m_instances[index].p_file, m_instances[index].p_path); } else if (m_instances[index].state == STATE_CONNECTING_WRQ) { TFTP_TRC("OACK time out. Retransmit WRQ."); m_instances[index].state = STATE_IDLE; err_code = send_request(TYPE_WRQ, &index, m_instances[index].p_file, NULL); } else { TFTP_TRC("In idle state."); } } else { TFTP_ERR("TFTP server did not response on query for file %s.", m_instances[index].p_file->p_filename); // No response from server. err_code = TFTP_REMOTE_UNREACHABLE; } if (err_code != NRF_SUCCESS) { // Inform application that timeout occurs. TFTP_ERR("Timeout error."); handle_evt_err(index, err_code, NULL); } } TFTP_MUTEX_UNLOCK(); TFTP_EXIT(); return; } } TFTP_MUTEX_UNLOCK(); } /**@brief Initializes TFTP client. */ uint32_t iot_tftp_init(iot_tftp_t * p_tftp, iot_tftp_init_t * p_init_params) { uint32_t index = 0; uint32_t err_code; NULL_PARAM_CHECK(p_tftp); NULL_PARAM_CHECK(p_init_params); NULL_PARAM_CHECK(p_init_params->p_ipv6_addr); TFTP_ENTRY(); TFTP_MUTEX_LOCK(); // Find first available instance. err_code = find_free_instance(&index); if (err_code == NRF_SUCCESS) { // Reset instance values. instance_reset(index); // Assign new values. *p_tftp = index; m_instances[index].callback = p_init_params->callback; m_instances[index].src_tid = p_init_params->src_port; m_instances[index].dst_port = p_init_params->dst_port; m_instances[index].p_password = p_init_params->p_password; m_instances[index].dst_tid = m_instances[index].dst_port; memcpy(&m_instances[index].addr, p_init_params->p_ipv6_addr, sizeof(ipv6_addr_t)); // Configure socket. err_code = udp6_socket_allocate(&m_instances[index].socket); if (err_code == NRF_SUCCESS) { err_code = udp6_socket_bind(&m_instances[index].socket, IPV6_ADDR_ANY, p_init_params->src_port); if (err_code == NRF_SUCCESS) { // Attach callback. err_code = udp6_socket_recv(&m_instances[index].socket, client_process); if (err_code == NRF_SUCCESS) { m_instances[index].state = STATE_IDLE; } } if (err_code != NRF_SUCCESS) { (void)udp6_socket_free(&m_instances[index].socket); TFTP_ERR("UDP socket configuration failure!"); } } } else { TFTP_ERR("No more free instances left!"); } TFTP_MUTEX_UNLOCK(); TFTP_EXIT(); return err_code; } /**@brief Resets TFTP client instance, so it is possible to make another request after error. */ uint32_t iot_tftp_abort(iot_tftp_t * p_tftp) { uint32_t err_code; uint32_t index; NULL_PARAM_CHECK(p_tftp); TFTP_ENTRY(); TFTP_MUTEX_LOCK(); err_code = find_instance(p_tftp, &index); if (err_code == NRF_SUCCESS) { instance_abort(index); } TFTP_MUTEX_UNLOCK(); TFTP_EXIT(); return err_code; } /**@brief Frees assigned sockets. */ uint32_t iot_tftp_uninit(iot_tftp_t * p_tftp) { uint32_t err_code; uint32_t index; NULL_PARAM_CHECK(p_tftp); TFTP_ENTRY(); TFTP_MUTEX_LOCK(); err_code = find_instance(p_tftp, &index); if (err_code == NRF_SUCCESS) { if (m_instances[index].state == STATE_SEND_HOLD || m_instances[index].state == STATE_RECV_HOLD || m_instances[index].state == STATE_SENDING || m_instances[index].state == STATE_RECEIVING) { // Free pbuffer. err_code = iot_pbuffer_free(m_instances[index].p_packet, true); if (err_code != NRF_SUCCESS) { TFTP_ERR("Cannot free pbuffer - %p", m_instances[index].p_packet); } err_code = iot_file_fclose(m_instances[index].p_file); if (err_code != NRF_SUCCESS) { TFTP_ERR("Cannot close file - %p", m_instances[index].p_file); } } if (m_instances[index].state != STATE_IDLE) { handle_evt_err(index, TFTP_UNDEFINED_ERROR, UNINT_ERROR_MSG); } (void)udp6_socket_free(&m_instances[index].socket); m_instances[index].state = STATE_FREE; } TFTP_MUTEX_UNLOCK(); TFTP_EXIT(); return err_code; } /**@brief Retrieves file from remote server into p_file. */ uint32_t iot_tftp_get(iot_tftp_t * p_tftp, iot_file_t * p_file, const char * p_path) { uint32_t err_code; NULL_PARAM_CHECK(p_tftp); NULL_PARAM_CHECK(p_path); if (p_file != NULL) { NULL_PARAM_CHECK(p_file->p_filename); } TFTP_ENTRY(); TFTP_MUTEX_LOCK(); err_code = send_request(TYPE_RRQ, p_tftp, p_file, p_path); if (err_code != NRF_SUCCESS) { TFTP_ERR("Error while sending read request. Reason: %08lx.", err_code); } TFTP_MUTEX_UNLOCK(); TFTP_EXIT(); return err_code; } /**@brief Sends local file p_file to a remote server. */ uint32_t iot_tftp_put(iot_tftp_t * p_tftp, iot_file_t * p_file, const char * p_path) { uint32_t err_code; NULL_PARAM_CHECK(p_tftp); NULL_PARAM_CHECK(p_file); NULL_PARAM_CHECK(p_file->p_filename); TFTP_ENTRY(); TFTP_MUTEX_LOCK(); err_code = send_request(TYPE_WRQ, p_tftp, p_file, p_path); if (err_code != NRF_SUCCESS) { TFTP_ERR("Error while sending write request. Reason: %08lx.", err_code); } TFTP_MUTEX_UNLOCK(); TFTP_EXIT(); return err_code; } /**@brief Holds transmission of ACK (use in order to slow transmission). */ uint32_t iot_tftp_hold(iot_tftp_t * p_tftp) { uint32_t index; uint32_t err_code; NULL_PARAM_CHECK(p_tftp); TFTP_ENTRY(); TFTP_MUTEX_LOCK(); err_code = find_instance(p_tftp, &index); if (err_code == NRF_SUCCESS) { // Hold transfer. err_code = transfer_hold(index); } else { TFTP_ERR("Hold called on unknown TFTP instance."); } TFTP_MUTEX_UNLOCK(); TFTP_EXIT(); return err_code; } /**@brief Resumes transmission. */ uint32_t iot_tftp_resume(iot_tftp_t * p_tftp) { uint32_t index; uint32_t err_code; NULL_PARAM_CHECK(p_tftp); TFTP_ENTRY(); TFTP_MUTEX_LOCK(); err_code = find_instance(p_tftp, &index); if (err_code == NRF_SUCCESS) { err_code = transfer_resume(index); } else { TFTP_ERR("Failed to find instance."); } TFTP_MUTEX_UNLOCK(); TFTP_EXIT(); return err_code; }