diff options
Diffstat (limited to 'thirdparty/nRF5_SDK_15.0.0_a53641a/components/iot/ipv6_stack/tftp/iot_tftp.c')
-rw-r--r-- | thirdparty/nRF5_SDK_15.0.0_a53641a/components/iot/ipv6_stack/tftp/iot_tftp.c | 2455 |
1 files changed, 2455 insertions, 0 deletions
diff --git a/thirdparty/nRF5_SDK_15.0.0_a53641a/components/iot/ipv6_stack/tftp/iot_tftp.c b/thirdparty/nRF5_SDK_15.0.0_a53641a/components/iot/ipv6_stack/tftp/iot_tftp.c new file mode 100644 index 0000000..65f0ec1 --- /dev/null +++ b/thirdparty/nRF5_SDK_15.0.0_a53641a/components/iot/ipv6_stack/tftp/iot_tftp.c @@ -0,0 +1,2455 @@ +/** + * Copyright (c) 2015 - 2018, 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; +} |