blob: 06567d0f545f1812b5fa176152dbc5c75b4a075c [file] [log] [blame]
/*
* Copyright (c) 2020 InnBlue
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <logging/log.h>
LOG_MODULE_REGISTER(tftp_client, LOG_LEVEL_DBG);
/* Public / Internal TFTP header file. */
#include <net/tftp.h>
#include "tftp_client.h"
/* TFTP Global Buffer. */
static u8_t tftpc_buffer[TFTPC_MAX_BUF_SIZE];
static u32_t tftpc_buffer_size;
/* TFTP Request block number property. */
static u32_t tftpc_block_no;
static u32_t tftpc_index;
/* Global mutex to protect critical resources. */
K_MUTEX_DEFINE(tftpc_lock);
/* Name: make_request
* Description: This function takes in a given list of parameters and
* returns a read request packet. This packet can be sent
* out directly to the TFTP server.
*/
static u32_t make_request(const char *remote_file, const char *mode,
u8_t request_type)
{
u32_t req_size;
const char def_mode[] = "octet";
/* Populate the read request with the provided params. Note that this
* is created per RFC1350.
*/
/* Fill in the Request Type. Also keep tabs on the request size. */
sys_put_be16(request_type, tftpc_buffer);
req_size = 2;
/* Copy in the name of the remote file - the file we need to get
* from the server. Add an upper bound to ensure no buffers overflow.
*/
strncpy(tftpc_buffer + req_size, remote_file, TFTP_MAX_FILENAME_SIZE);
req_size += strlen(remote_file);
/* Fill in 0. */
tftpc_buffer[req_size] = 0x0;
req_size++;
/* Default to "Octet" if mode not specified. */
if (mode == NULL) {
mode = def_mode;
}
/* Copy the mode of operation. */
strncpy(tftpc_buffer + req_size, mode, TFTP_MAX_MODE_SIZE);
req_size += strlen(mode);
/* Fill in 0. */
tftpc_buffer[req_size] = 0x0;
req_size++;
return req_size;
}
/* Name: send_err
* Description: This function sends an Error report to the TFTP Server.
*/
static inline int send_err(int sock, int err_code, char *err_string)
{
u32_t req_size;
LOG_ERR("Client Error. Sending code: %d(%s)", err_code, err_string);
/* Fill in the "Err" Opcode and the actual error code. */
sys_put_be16(ERROR_OPCODE, tftpc_buffer);
sys_put_be16(err_code, tftpc_buffer + 2);
req_size = 4;
/* Copy the Error String. */
strcpy(tftpc_buffer + req_size, err_string);
req_size += strlen(err_string);
/* Send Error to server. */
return send(sock, tftpc_buffer, req_size, 0);
}
/* Name: tftpc_recv_data
* Description: This function tries to get data from the TFTP Server
* (either response or data). Times out eventually.
*/
static int tftpc_recv_data(int sock)
{
int stat;
struct pollfd fds;
/* Setup FDS. */
fds.fd = sock;
fds.events = ZSOCK_POLLIN;
/* Enable poll on this socket. Wait for the specified number
* of milliseconds before exiting the call.
*/
stat = poll(&fds, 1, CONFIG_TFTPC_REQUEST_TIMEOUT);
if (stat > 0) {
/* Receive data from the TFTP Server. */
stat = recv(sock, tftpc_buffer, TFTPC_MAX_BUF_SIZE, 0);
/* Store the amount of data received in the global variable. */
tftpc_buffer_size = stat;
}
return stat;
}
/* Name: tftpc_process_resp
* Description: This function will process the data received from the
* TFTP Server (a file or part of the file) and place it in the user buffer.
* Return Value: This function will return one of the following values,
* -> TFTPC_DUPLICATE_DATA: If the block is already received by the client.
* -> TFTPC_BUFFER_OVERFLOW: If the data is more than the user provided buffer.
* -> TFTPC_DATA_RX_SUCCESS: data received but their is more to come.
* -> TFTPC_SUCCESS: Block received successfully and no more data is coming.
*/
static int tftpc_process_resp(int sock, struct tftpc *client)
{
u16_t block_no;
/* Get the block number as received in the packet. */
block_no = sys_get_be16(tftpc_buffer + 2);
if (tftpc_block_no > block_no) {
LOG_DBG("Duplicate block received: %d", block_no);
LOG_DBG("Client waiting for Block Number: %d", tftpc_block_no);
send_ack(sock, block_no);
/* Duplicate block received. */
return TFTPC_DUPLICATE_DATA;
}
/* Valid block number received. */
LOG_DBG("Block Number: %d received", tftpc_block_no);
/* Only copy block if the user buffer has enough space. */
if (RECV_DATA_SIZE() > (client->user_buf_size - tftpc_index)) {
send_err(sock, 0x3, "Buffer Overflow");
return TFTPC_BUFFER_OVERFLOW;
}
/* Perform the actual copy and update the index. */
memcpy(client->user_buf + tftpc_index,
tftpc_buffer + TFTP_HEADER_SIZE, RECV_DATA_SIZE());
tftpc_index += RECV_DATA_SIZE();
/* "block" of data received. */
send_ack(sock, block_no);
tftpc_block_no++;
/* For RFC1350, the block size will always be 512.
* -> If block_size == 512, the transfer is still in progress.
* -> If block_size < 512, we will conclude the transfer.
*/
return (RECV_DATA_SIZE() == TFTP_BLOCK_SIZE) ? TFTP_BLOCK_SIZE :
TFTPC_SUCCESS;
}
/* Name: tftp_send_request
* Description: This function sends out a request to the TFTP Server
* (Read / Write) and waits for a response. Once we get some response
* from the server, it is interpreted and ensured to be correct.
* If not, we keep on poking the server for data until we eventually
* give up.
* Return Value: This function will return the "opcode" received from
* the remote server, i.e.
* -> ERROR_OPCODE: If the remote server responded with "Error" or if
* the client was unable to send / receive anything from the server.
* -> DATA_OPCODE: If the remote server responded with "Data".
* -> ACK_OPCODE: If the remote server responded with "Ack".
*/
static int tftp_send_request(int sock, u8_t request,
const char *remote_file, const char *mode)
{
u8_t retx_cnt = 0;
u32_t req_size;
/* Create TFTP Request. */
req_size = make_request(remote_file, mode, request);
send_req:
/* Send this request to the server. */
if (send(sock, tftpc_buffer, req_size, 0)) {
if (tftpc_recv_data(sock)) {
return sys_get_be16(tftpc_buffer);
}
}
/* No of times we had to re-tx this "request". */
retx_cnt++;
/* Log this out. */
LOG_DBG("Unable to get data from the TFTP Server.");
LOG_DBG("no_of_retransmists = %d", retx_cnt);
/* Are we retransmitting? */
if (retx_cnt < TFTP_REQ_RETX) {
LOG_DBG("Are we re-transmitting: YES");
goto send_req;
}
LOG_DBG("Are we re-transmitting: NO");
return ERROR_OPCODE;
}
/* Name: tftp_get
* Description: This function gets "file" from the remote server.
*/
int tftp_get(struct sockaddr *server, struct tftpc *client,
const char *remote_file, const char *mode)
{
s32_t stat = TFTPC_UNKNOWN_FAILURE;
u8_t retx_cnt = 0;
s32_t sock;
/* Re-init the global "block number" variable. */
tftpc_block_no = 1;
tftpc_index = 0;
/* Create Socket for TFTP (IPv4 / UDP) */
sock = socket(server->sa_family, SOCK_DGRAM, IPPROTO_UDP);
if (sock < 0) {
LOG_ERR("Failed to create UDP socket (IPv4): %d", errno);
return -errno;
}
/* Connect with the address. */
stat = connect(sock, server, sizeof(struct sockaddr_in6));
if (stat < 0) {
LOG_ERR("Cannot connect to UDP remote (IPv4): %d", errno);
return -errno;
}
/* Obtain Global Lock before accessing critical resources. */
k_mutex_lock(&tftpc_lock, K_FOREVER);
/* Send out the request to the TFTP Server. */
stat = tftp_send_request(sock, READ_REQUEST, remote_file, mode);
if (stat == ERROR_OPCODE) {
LOG_ERR("Server responded with error.");
return TFTPC_REMOTE_ERROR;
}
process_resp:
/* Process server response. */
stat = tftpc_process_resp(sock, client);
if (stat == TFTPC_BUFFER_OVERFLOW ||
stat == TFTPC_SUCCESS) {
goto req_done;
}
tftpc_recv:
/* More data is available - Read (or we read a duplicate). */
stat = tftpc_recv_data(sock);
if (stat <= 0) {
/* No response from server. */
LOG_DBG("Server response timeout.");
/* Retries exhausted ? */
if (++retx_cnt >= TFTP_REQ_RETX) {
LOG_ERR("No more retransmits available. Exiting");
return TFTPC_RETRIES_EXHAUSTED;
}
/* Start Retransmission. */
LOG_DBG("Starting Re-transmission.");
send_ack(sock, tftpc_block_no);
goto tftpc_recv;
}
/* Received response from the server. */
LOG_DBG("Received data of size: %d", stat);
goto process_resp;
req_done:
/* Clean up. */
k_mutex_unlock(&tftpc_lock);
close(sock);
return stat;
}