Files
my-os-project2/kernel/picotcp/modules/pico_tftp.c
2025-10-29 14:29:06 +01:00

1324 lines
38 KiB
C

/*********************************************************************
PicoTCP. Copyright (c) 2012-2017 Altran Intelligent Systems. Some rights reserved.
See COPYING, LICENSE.GPLv2 and LICENSE.GPLv3 for usage.
.
Author: Daniele Lacamera
*********************************************************************/
#include <pico_defines.h>
#include <pico_stack.h>
#include <pico_socket.h>
#include <pico_tftp.h>
#include <pico_strings.h>
#ifdef DEBUG_TFTP
#define tftp_dbg dbg
#else
#define tftp_dbg(...) do {} while(0)
#endif
/* a zero value means adaptative timeout! (2, 4, 8) */
#define PICO_TFTP_TIMEOUT 2000U
#define TFTP_MAX_RETRY 3
#define TFTP_STATE_READ_REQUESTED 0
#define TFTP_STATE_RX 1
#define TFTP_STATE_LAST_ACK_SENT 2
#define TFTP_STATE_WRITE_REQUESTED 3
#define TFTP_STATE_TX 4
#define TFTP_STATE_WAIT_OPT_CONFIRM 5
#define TFTP_STATE_WAIT_LAST_ACK 6
#define TFTP_STATE_CLOSING 7
#define AUTOMA_STATES (TFTP_STATE_CLOSING + 1)
/* MAX_OPTIONS_SIZE: "timeout" 255 "tsize" filesize => 8 + 4 + 6 + 11 */
#define MAX_OPTIONS_SIZE 29
/* RRQ and WRQ packets (opcodes 1 and 2 respectively) */
PACKED_STRUCT_DEF pico_tftp_hdr
{
uint16_t opcode;
};
/* DATA or ACK (opcodes 3 and 4 respectively)*/
PACKED_STRUCT_DEF pico_tftp_data_hdr
{
uint16_t opcode;
uint16_t block;
};
/* ERROR (opcode 5) */
PACKED_STRUCT_DEF pico_tftp_err_hdr
{
uint16_t opcode;
uint16_t error_code;
};
#define PICO_TFTP_TOTAL_BLOCK_SIZE (PICO_TFTP_PAYLOAD_SIZE + (int32_t)sizeof(struct pico_tftp_data_hdr))
#define tftp_payload(p) (((uint8_t *)(p)) + sizeof(struct pico_tftp_data_hdr))
/* STATUS FLAGS */
#define SESSION_STATUS_CLOSED 1
#define SESSION_STATUS_APP_PENDING 2
#define SESSION_STATUS_IN_CALLBACK 4
#define SESSION_STATUS_APP_ACK 64
struct pico_tftp_session {
int state;
int status;
int options;
int retry;
uint16_t packet_counter;
/* Current connection */
struct pico_socket *socket;
union pico_address remote_address;
uint16_t remote_port;
uint16_t localport;
pico_time wallclock_timeout;
pico_time bigger_wallclock;
struct pico_tftp_session *next;
uint32_t timer;
unsigned int active_timers;
void *argument;
int (*callback)(struct pico_tftp_session *session, uint16_t event, uint8_t *block, int32_t len, void *arg);
int32_t file_size;
int32_t len;
uint8_t option_timeout;
uint8_t tftp_block[PICO_TFTP_TOTAL_BLOCK_SIZE];
int32_t block_len;
};
struct server_t {
void (*listen_callback)(union pico_address *addr, uint16_t port, uint16_t opcode, char *filename, int32_t len);
struct pico_socket *listen_socket;
uint8_t tftp_block[PICO_TFTP_TOTAL_BLOCK_SIZE];
};
struct automa_events {
void (*ack)(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port);
void (*data)(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port);
void (*error)(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port);
void (*oack)(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port);
void (*timeout)(struct pico_tftp_session *session, pico_time t);
};
static struct server_t server;
static struct pico_tftp_session *tftp_sessions = NULL;
static inline void session_status_set(struct pico_tftp_session *session, int status)
{
session->status |= status;
}
static inline void session_status_clear(struct pico_tftp_session *session, int status)
{
session->status &= ~status;
}
static char *extract_arg_pointer(char *arg, char *end_arg, char **value)
{
char *pos;
pos = get_string_terminator_position(arg, (size_t)(end_arg - arg));
if (!pos)
return NULL;
if (end_arg == ++pos)
return NULL;
arg = get_string_terminator_position(pos, (size_t)(end_arg - pos));
if (!arg)
return NULL;
*value = pos;
return arg + 1;
}
static int extract_value(char *str, uint32_t *value, uint32_t max)
{
char *endptr;
unsigned long num;
num = strtoul(str, &endptr, 10);
if (endptr == str || *endptr || num > max)
return -1;
*value = (uint32_t)num;
return 0;
}
static int parse_optional_arguments(char *option_string, int32_t len, int *options, uint8_t *timeout, int32_t *filesize)
{
char *pos;
char *end_args = option_string + len;
char *current_option;
int ret;
uint32_t value;
*options = 0;
while (option_string < end_args) {
current_option = option_string;
option_string = extract_arg_pointer(option_string, end_args, &pos);
if (!option_string)
return 0;
if (!pico_strncasecmp("timeout", current_option, (size_t)(pos - current_option))) {
ret = extract_value(pos, &value, PICO_TFTP_MAX_TIMEOUT);
if (ret)
return -1;
*timeout = (uint8_t)value;
*options |= PICO_TFTP_OPTION_TIME;
} else {
if (!pico_strncasecmp("tsize", current_option, (size_t)(pos - current_option))) {
ret = extract_value(pos, (uint32_t *)filesize, PICO_TFTP_MAX_FILESIZE);
if (ret)
return -1;
if (*filesize < 0)
return -1;
*options |= PICO_TFTP_OPTION_FILE;
}
}
}
return 0;
}
static inline struct pico_tftp_session *pico_tftp_session_create(struct pico_socket *sock, union pico_address *remote_addr)
{
struct pico_tftp_session *session;
session = (struct pico_tftp_session *) PICO_ZALLOC(sizeof (struct pico_tftp_session));
if (!session)
pico_err = PICO_ERR_ENOMEM;
else {
session->state = 0;
session->status = 0;
session->options = 0;
session->packet_counter = 0u;
session->socket = sock;
session->wallclock_timeout = 0;
session->bigger_wallclock = 0;
session->active_timers = 0;
session->next = NULL;
session->localport = 0;
session->callback = NULL;
session->argument = NULL;
memcpy(&session->remote_address, remote_addr, sizeof(union pico_address));
session->remote_port = 0;
session->len = 0;
}
return session;
}
static struct pico_tftp_session *find_session_by_socket(struct pico_socket *tftp_socket)
{
struct pico_tftp_session *pos = tftp_sessions;
for (; pos; pos = pos->next)
if (pos->socket == tftp_socket)
return pos;
return NULL;
}
/* **************** for future use...
static struct pico_tftp_session * find_session_by_localport(uint16_t localport)
{
struct pico_tftp_session *idx = tftp_sessions;
for (; idx; idx = idx->next)
if (idx->localport == localport)
return idx;
return NULL;
} *********************/
static void add_session(struct pico_tftp_session *idx)
{
struct pico_tftp_session *prev = NULL;
struct pico_tftp_session *pos;
for (pos = tftp_sessions; pos; prev = pos, pos = pos->next)
if (pos->localport > idx->localport)
break;
if (prev) {
idx->next = prev->next;
prev->next = idx;
} else {
idx->next = tftp_sessions;
tftp_sessions = idx;
}
}
/* Returns 0 if OK and -1 in case of errors */
static int del_session(struct pico_tftp_session *idx)
{
struct pico_tftp_session *prev = NULL;
struct pico_tftp_session *pos;
for (pos = tftp_sessions; pos; pos = pos->next) {
if (pos == idx) {
if (pos == tftp_sessions)
tftp_sessions = tftp_sessions->next;
else
prev->next = pos->next;
PICO_FREE(idx);
return 0;
}
prev = pos;
}
return -1;
}
static inline int do_callback(struct pico_tftp_session *session, uint16_t err, uint8_t *data, int32_t len)
{
int ret;
session_status_set(session, SESSION_STATUS_IN_CALLBACK);
ret = session->callback(session, err, data, len, session->argument);
session_status_clear(session, SESSION_STATUS_IN_CALLBACK);
return ret;
}
static void timer_callback(pico_time now, void *arg);
static void tftp_finish(struct pico_tftp_session *session);
static void tftp_schedule_timeout(struct pico_tftp_session *session, pico_time interval)
{
pico_time new_timeout = PICO_TIME_MS() + interval;
if (session->active_timers) {
if (session->bigger_wallclock > new_timeout) {
session->timer = pico_timer_add(interval + 1, timer_callback, session);
if (!session->timer) {
tftp_dbg("TFTP: Failed to start callback timer, deleting session\n");
tftp_finish(session);
return;
}
session->active_timers++;
}
} else {
session->timer = pico_timer_add(interval + 1, timer_callback, session);
if (!session->timer) {
tftp_dbg("TFTP: Failed to start callback timer, deleting session\n");
tftp_finish(session);
return;
}
session->active_timers++;
session->bigger_wallclock = new_timeout;
}
session->wallclock_timeout = new_timeout;
}
static void tftp_finish(struct pico_tftp_session *session)
{
if (session->state != TFTP_STATE_CLOSING) {
pico_socket_close(session->socket);
session->state = TFTP_STATE_CLOSING;
if (session->active_timers) {
pico_timer_cancel(session->timer);
--session->active_timers;
}
session->wallclock_timeout = 0;
tftp_schedule_timeout(session, 5);
}
}
static void tftp_send(struct pico_tftp_session *session, int len)
{
if (len)
session->len = len;
else
len = session->len;
pico_socket_sendto(session->socket, session->tftp_block, session->len, &session->remote_address, session->remote_port);
}
static void tftp_send_ack(struct pico_tftp_session *session)
{
struct pico_tftp_data_hdr *dh;
dh = PICO_ZALLOC(sizeof(struct pico_tftp_data_hdr));
if (!dh)
return;
dh->opcode = short_be(PICO_TFTP_ACK);
dh->block = short_be(session->packet_counter);
if (session->socket) {
pico_socket_sendto(session->socket, dh, (int) sizeof(struct pico_tftp_err_hdr),
&session->remote_address, session->remote_port);
tftp_schedule_timeout(session, PICO_TFTP_TIMEOUT);
}
PICO_FREE(dh);
}
static size_t prepare_options_string(struct pico_tftp_session *session, char *str_options, int32_t filesize)
{
size_t len = 0;
int res;
if (session->options & PICO_TFTP_OPTION_TIME) {
strcpy(str_options, "timeout");
len += 8;
res = num2string(session->option_timeout, &str_options[len], 4);
if (res < 0)
return 0;
len += (size_t)res;
}
if (session->options & PICO_TFTP_OPTION_FILE) {
strcpy(&str_options[len], "tsize");
len += 6;
res = num2string(filesize, &str_options[len], 11);
if (res < 0)
return 0;
len += (size_t)res;
}
return len;
}
static void tftp_send_oack(struct pico_tftp_session *session)
{
struct pico_tftp_hdr *hdr;
size_t options_size;
size_t options_pos = sizeof(struct pico_tftp_hdr);
uint8_t *buf;
char str_options[MAX_OPTIONS_SIZE] = {
0
};
options_size = prepare_options_string(session, str_options, session->file_size);
buf = PICO_ZALLOC(options_pos + options_size);
if (!buf) {
strcpy((char *)session->tftp_block, "Out of memory");
do_callback(session, PICO_TFTP_EV_ERR_LOCAL, session->tftp_block, 0);
tftp_finish(session);
return;
}
hdr = (struct pico_tftp_hdr *)buf;
hdr->opcode = short_be(PICO_TFTP_OACK);
memcpy(buf + options_pos, str_options, options_size);
(void)pico_socket_sendto(session->socket, buf, (int)(options_pos + options_size), &session->remote_address, session->remote_port);
PICO_FREE(buf);
}
static void tftp_send_req(struct pico_tftp_session *session, union pico_address *a, uint16_t port, const char *filename, uint16_t opcode)
{
#define OCTET_STRSIZ 7U
static const char octet[OCTET_STRSIZ] = {
0, 'o', 'c', 't', 'e', 't', 0
};
struct pico_tftp_hdr *hdr;
size_t len;
size_t options_size;
size_t options_pos;
uint8_t *buf;
char str_options[MAX_OPTIONS_SIZE] = {
0
};
if (!filename) {
return;
}
len = strlen(filename);
options_size = prepare_options_string(session, str_options, (opcode == PICO_TFTP_WRQ) ? (session->file_size) : (0));
options_pos = sizeof(struct pico_tftp_hdr) + OCTET_STRSIZ + len;
buf = PICO_ZALLOC(options_pos + options_size);
if (!buf) {
strcpy((char *)session->tftp_block, "Out of memory");
do_callback(session, PICO_TFTP_EV_ERR_LOCAL, session->tftp_block, 0);
tftp_finish(session);
return;
}
hdr = (struct pico_tftp_hdr *)buf;
hdr->opcode = short_be(opcode);
memcpy(buf + sizeof(struct pico_tftp_hdr), filename, len);
memcpy(buf + sizeof(struct pico_tftp_hdr) + len, octet, OCTET_STRSIZ);
memcpy(buf + options_pos, str_options, options_size);
(void)pico_socket_sendto(session->socket, buf, (int)(options_pos + options_size), a, port);
PICO_FREE(buf);
}
static void tftp_send_rx_req(struct pico_tftp_session *session, union pico_address *a, uint16_t port, const char *filename)
{
tftp_send_req(session, a, port, filename, PICO_TFTP_RRQ);
session->state = TFTP_STATE_READ_REQUESTED;
tftp_schedule_timeout(session, PICO_TFTP_TIMEOUT);
}
static void tftp_send_tx_req(struct pico_tftp_session *session, union pico_address *a, uint16_t port, const char *filename)
{
tftp_send_req(session, a, port, filename, PICO_TFTP_WRQ);
session->state = TFTP_STATE_WRITE_REQUESTED;
tftp_schedule_timeout(session, PICO_TFTP_TIMEOUT);
}
static int send_error(uint8_t *buf, struct pico_socket *sock, union pico_address *a, uint16_t port, uint16_t errcode, const char *errmsg)
{
struct pico_tftp_err_hdr *eh;
int32_t len;
int32_t maxlen = PICO_TFTP_TOTAL_BLOCK_SIZE - sizeof(struct pico_tftp_err_hdr);
if (!errmsg)
len = 0;
else
len = (int32_t)strlen(errmsg);
eh = (struct pico_tftp_err_hdr *) buf;
eh->opcode = short_be(PICO_TFTP_ERROR);
eh->error_code = short_be(errcode);
if (len + 1 > maxlen)
len = maxlen;
if (len)
memcpy(tftp_payload(eh), errmsg, (size_t)len);
tftp_payload(eh)[len++] = (char)0;
return pico_socket_sendto(sock, eh, (int)(len + (int32_t)sizeof(struct pico_tftp_err_hdr)), a, port);
}
static void tftp_send_error(struct pico_tftp_session *session, union pico_address *a, uint16_t port, uint16_t errcode, const char *errmsg)
{
struct pico_tftp_err_hdr *eh;
int32_t len;
int32_t maxlen = PICO_TFTP_TOTAL_BLOCK_SIZE - sizeof(struct pico_tftp_err_hdr);
if (!errmsg)
len = 0;
else
len = (int32_t)strlen(errmsg);
if (!a) {
a = &session->remote_address;
port = session->remote_port;
}
eh = (struct pico_tftp_err_hdr *) (session ? (session->tftp_block) : (server.tftp_block));
eh->opcode = short_be(PICO_TFTP_ERROR);
eh->error_code = short_be(errcode);
if (len + 1 > maxlen)
len = maxlen;
if (len)
memcpy(tftp_payload(eh), errmsg, (size_t)len);
tftp_payload(eh)[len++] = (char)0;
if (session) {
(void)pico_socket_sendto(session->socket, eh, (int) (len + (int32_t)sizeof(struct pico_tftp_err_hdr)), a, port);
tftp_finish(session);
} else
(void)pico_socket_sendto(server.listen_socket, eh, (int) (len + (int32_t)sizeof(struct pico_tftp_err_hdr)), a, port);
}
static void tftp_send_data(struct pico_tftp_session *session, const uint8_t *data, int32_t len)
{
struct pico_tftp_data_hdr *dh;
dh = (struct pico_tftp_data_hdr *) session->tftp_block;
dh->opcode = short_be(PICO_TFTP_DATA);
dh->block = short_be(session->packet_counter++);
if (len < PICO_TFTP_PAYLOAD_SIZE)
session->state = TFTP_STATE_WAIT_LAST_ACK;
else
session->state = TFTP_STATE_TX;
memcpy(session->tftp_block + sizeof(struct pico_tftp_data_hdr), data, (size_t)len);
pico_socket_sendto(session->socket, session->tftp_block, (int)(len + (int32_t)sizeof(struct pico_tftp_data_hdr)),
&session->remote_address, session->remote_port);
tftp_schedule_timeout(session, PICO_TFTP_TIMEOUT);
}
static inline void tftp_eval_finish(struct pico_tftp_session *session, int32_t len)
{
if (len < PICO_TFTP_PAYLOAD_SIZE) {
pico_socket_close(session->socket);
session->state = TFTP_STATE_CLOSING;
}
}
static inline int tftp_data_prepare(struct pico_tftp_session *session, union pico_address *a, uint16_t port)
{
if (!session->socket)
return -1;
if (pico_address_compare(a, &session->remote_address, session->socket->net->proto_number) != 0) {
tftp_send_error(session, a, port, TFTP_ERR_EXCEEDED, "TFTP busy, try again later.");
return -1;
}
return 0;
}
static void tftp_req(uint8_t *block, int32_t len, union pico_address *a, uint16_t port)
{
struct pico_tftp_hdr *hdr = (struct pico_tftp_hdr *)block;
char *filename;
char *pos;
char *mode;
int ret;
switch (short_be(hdr->opcode)) {
case PICO_TFTP_RRQ:
case PICO_TFTP_WRQ:
filename = (char *)(block + sizeof(struct pico_tftp_hdr));
len -= (int32_t)sizeof(struct pico_tftp_hdr);
pos = extract_arg_pointer(filename, filename + len, &mode);
if (!pos) {
send_error(block, server.listen_socket, a, port, TFTP_ERR_EILL, "Invalid argument in request");
return;
}
ret = strcmp("octet", mode);
if (ret) {
send_error(block, server.listen_socket, a, port, TFTP_ERR_EILL, "Unsupported mode");
return;
}
/*ret = parse_optional_arguments((char *)(block + sizeof(struct pico_tftp_hdr)), len - sizeof(struct pico_tftp_hdr), &new_options, &new_timeout, &new_filesize);
if (ret) {
tftp_send_error(NULL, a, port, TFTP_ERR_EILL, "Bad request");
return;
} */
if (server.listen_callback) {
server.listen_callback(a, port, short_be(hdr->opcode), filename, len);
}
break;
default:
send_error(block, server.listen_socket, a, port, TFTP_ERR_EILL, "Illegal opcode");
}
}
static int event_ack_base(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port)
{
struct pico_tftp_data_hdr *dh;
uint16_t block_n;
const char *wrong_address = "Wrong address";
const char *wrong_block = "Wrong packet number";
(void)len;
if (pico_address_compare(a, &session->remote_address, session->socket->net->proto_number) != 0) {
strcpy((char *)session->tftp_block, wrong_address);
do_callback(session, PICO_TFTP_EV_ERR_PEER, session->tftp_block, len);
tftp_send_error(session, a, port, TFTP_ERR_EXCEEDED, wrong_address);
return -1;
}
dh = (struct pico_tftp_data_hdr *)session->tftp_block;
block_n = short_be(dh->block);
if (block_n != (session->packet_counter - 1U)) {
strcpy((char *)session->tftp_block, wrong_block);
do_callback(session, PICO_TFTP_EV_ERR_PEER, session->tftp_block, len);
tftp_send_error(session, a, port, TFTP_ERR_EILL, wrong_block);
return -1;
}
return 0;
}
static inline int event_ack0_check(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port)
{
struct pico_tftp_data_hdr *dh;
uint16_t block_n;
(void)len;
if (pico_address_compare(a, &session->remote_address, session->socket->net->proto_number) != 0) {
tftp_send_error(session, a, port, TFTP_ERR_EXCEEDED, "TFTP busy, try again later.");
return -1;
}
dh = (struct pico_tftp_data_hdr *)session->tftp_block;
block_n = short_be(dh->block);
if (block_n != 0) {
tftp_send_error(session, a, port, TFTP_ERR_EILL, "TFTP connection broken!");
return -1;
}
return 0;
}
static void event_ack0_wr(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port)
{
if (!event_ack0_check(session, len, a, port)) {
session->remote_port = port;
do_callback(session, PICO_TFTP_EV_OK, session->tftp_block, 0);
}
}
static void event_ack0_woc(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port)
{
if (!event_ack0_check(session, len, a, port))
do_callback(session, PICO_TFTP_EV_OPT, session->tftp_block, 0);
}
static void event_ack(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port)
{
if (!event_ack_base(session, len, a, port))
do_callback(session, PICO_TFTP_EV_OK, session->tftp_block, 0);
}
static void event_ack_last(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port)
{
if (!event_ack_base(session, len, a, port))
tftp_finish(session);
}
static void event_data(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port)
{
struct pico_tftp_data_hdr *dh;
int32_t payload_len = len - (int32_t)sizeof(struct pico_tftp_data_hdr);
if (tftp_data_prepare(session, a, port))
return;
dh = (struct pico_tftp_data_hdr *)session->tftp_block;
if (short_be(dh->block) > (session->packet_counter + 1U)) {
strcpy((char *)session->tftp_block, "Wrong/unexpected sequence number");
do_callback(session, PICO_TFTP_EV_ERR_LOCAL, session->tftp_block, 0);
tftp_send_error(session, a, port, TFTP_ERR_EILL, "TFTP connection broken!");
return;
}
if (short_be(dh->block) == (session->packet_counter + 1U)) {
session->packet_counter++;
if (do_callback(session, PICO_TFTP_EV_OK, tftp_payload(session->tftp_block), payload_len) >= 0) {
if (!(session->status & SESSION_STATUS_APP_ACK))
tftp_send_ack(session);
}
if (!(session->status & SESSION_STATUS_APP_ACK))
tftp_eval_finish(session, len);
}
}
static void event_data_rdr(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port)
{
if (tftp_data_prepare(session, a, port))
return;
session->remote_port = port;
session->state = TFTP_STATE_RX;
event_data(session, len, a, port);
}
static void event_data_rpl(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port)
{
struct pico_tftp_data_hdr *dh;
(void)len;
if (tftp_data_prepare(session, a, port))
return;
dh = (struct pico_tftp_data_hdr *)session->tftp_block;
if (short_be(dh->block) == session->packet_counter)
tftp_send_ack(session);
}
static void event_err(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port)
{
(void)a;
(void)port;
do_callback(session, PICO_TFTP_EV_ERR_PEER, session->tftp_block, len);
tftp_finish(session);
}
static inline void event_oack(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port)
{
char *option_string = (char *)session->tftp_block + sizeof(struct pico_tftp_hdr);
int ret;
int proposed_options = session->options;
(void)a;
session->remote_port = port;
ret = parse_optional_arguments(option_string, len - (int32_t)sizeof(struct pico_tftp_hdr), &session->options, &session->option_timeout, &session->file_size);
if (ret || (session->options & ~proposed_options)) {
do_callback(session, PICO_TFTP_EV_ERR_PEER, session->tftp_block, len);
tftp_send_error(session, a, port, TFTP_ERR_EOPT, "Invalid option");
return;
}
do_callback(session, PICO_TFTP_EV_OPT, session->tftp_block, len);
}
static void event_oack_rr(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port)
{
event_oack(session, len, a, port);
tftp_send_ack(session);
session->state = TFTP_STATE_RX;
}
static void event_oack_wr(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port)
{
event_oack(session, len, a, port);
session->state = TFTP_STATE_TX;
}
static void event_timeout(struct pico_tftp_session *session, pico_time t)
{
pico_time new_timeout;
int factor;
(void)t;
if (++session->retry == TFTP_MAX_RETRY) {
strcpy((char *)session->tftp_block, "Network timeout");
do_callback(session, PICO_TFTP_EV_ERR_PEER, session->tftp_block, 0);
tftp_finish(session);
return;
}
tftp_send(session, 0);
if (session->options & PICO_TFTP_OPTION_TIME)
new_timeout = session->option_timeout * 1000U;
else {
new_timeout = PICO_TFTP_TIMEOUT;
for (factor = session->retry; factor; --factor)
new_timeout *= 2;
}
tftp_schedule_timeout(session, new_timeout);
}
static void event_timeout_closing(struct pico_tftp_session *session, pico_time t)
{
(void)t;
if (session->active_timers == 0)
del_session(session);
}
static void event_timeout_final(struct pico_tftp_session *session, pico_time t)
{
(void)t;
tftp_finish(session);
}
static void unexpected(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port)
{
(void)len;
tftp_send_error(session, a, port, TFTP_ERR_EILL, "Unexpected message");
}
static void null(struct pico_tftp_session *session, int32_t len, union pico_address *a, uint16_t port)
{
(void)session;
(void)len;
(void)a;
(void)port;
}
static struct automa_events fsm[AUTOMA_STATES] = {
/* STATE * ACK DATA ERROR OACK TIMEOUT */
/* ***************************************************************************************************************** */
{ /* TFTP_STATE_READ_REQUESTED */ unexpected, event_data_rdr, event_err, event_oack_rr, event_timeout},
{ /* TFTP_STATE_RX */ unexpected, event_data, event_err, unexpected, event_timeout},
{ /* TFTP_STATE_LAST_ACK_SENT */ unexpected, event_data_rpl, null, unexpected, event_timeout_final},
{ /* TFTP_STATE_WRITE_REQUESTED */ event_ack0_wr, unexpected, event_err, event_oack_wr, event_timeout},
{ /* TFTP_STATE_TX */ event_ack, unexpected, event_err, unexpected, event_timeout},
{ /* TFTP_STATE_WAIT_OPT_CONFIRM */ event_ack0_woc, unexpected, event_err, unexpected, event_timeout},
{ /* TFTP_STATE_WAIT_LAST_ACK */ event_ack_last, unexpected, event_err, unexpected, event_timeout},
{ /* TFTP_STATE_CLOSING */ null, null, null, null, event_timeout_closing}
};
static void tftp_message_received(struct pico_tftp_session *session, uint8_t *block, int32_t len, union pico_address *a, uint16_t port)
{
struct pico_tftp_hdr *th = (struct pico_tftp_hdr *) block;
if (!session->callback)
return;
session->wallclock_timeout = 0;
switch (short_be(th->opcode)) {
case PICO_TFTP_RRQ:
case PICO_TFTP_WRQ:
unexpected(session, len, a, port);
break;
case PICO_TFTP_DATA:
fsm[session->state].data(session, len, a, port);
break;
case PICO_TFTP_ACK:
fsm[session->state].ack(session, len, a, port);
break;
case PICO_TFTP_ERROR:
fsm[session->state].error(session, len, a, port);
break;
case PICO_TFTP_OACK:
fsm[session->state].oack(session, len, a, port);
break;
default:
tftp_send_error(session, NULL, 0, TFTP_ERR_EILL, "Illegal opcode");
}
}
static void tftp_cb(uint16_t ev, struct pico_socket *s)
{
int r;
struct pico_tftp_session *session;
union pico_address ep;
uint16_t port = 0;
session = find_session_by_socket(s);
if (session) {
if (ev == PICO_SOCK_EV_ERR) {
strcpy((char *)session->tftp_block, "Socket Error");
do_callback(session, PICO_TFTP_EV_ERR_LOCAL, session->tftp_block, (int32_t)strlen((char *)session->tftp_block));
tftp_finish(session);
return;
}
r = pico_socket_recvfrom(s, session->tftp_block, PICO_TFTP_TOTAL_BLOCK_SIZE, &ep, &port);
if (r < (int)sizeof(struct pico_tftp_hdr))
return;
tftp_message_received(session, session->tftp_block, r, &ep, port);
} else {
if (!server.listen_socket || s != server.listen_socket) {
return;
}
r = pico_socket_recvfrom(s, server.tftp_block, PICO_TFTP_TOTAL_BLOCK_SIZE, &ep, &port);
if (r < (int)sizeof(struct pico_tftp_hdr))
return;
tftp_req(server.tftp_block, r, &ep, port);
}
}
static int application_rx_cb(struct pico_tftp_session *session, uint16_t event, uint8_t *block, int32_t len, void *arg)
{
int *flag = (int *)arg;
(void)block;
switch (event) {
case PICO_TFTP_EV_ERR_PEER:
case PICO_TFTP_EV_ERR_LOCAL:
*flag = 0 - event;
break;
case PICO_TFTP_EV_OK:
session->len = len;
*flag = 1;
break;
case PICO_TFTP_EV_OPT:
break;
}
return 0;
}
static int application_tx_cb(struct pico_tftp_session *session, uint16_t event, uint8_t *block, int32_t len, void *arg)
{
(void)session;
(void)block;
(void)len;
*(int*)arg = ((event == PICO_TFTP_EV_OK) || (event == PICO_TFTP_EV_OPT)) ? (1) : (0 - event);
return 0;
}
static void timer_callback(pico_time now, void *arg)
{
struct pico_tftp_session *session = (struct pico_tftp_session *)arg;
--session->active_timers;
if (session->wallclock_timeout == 0) {
/* Timer is cancelled. */
return;
}
if (now >= session->wallclock_timeout) {
session->wallclock_timeout = 0ULL;
fsm[session->state].timeout(session, now);
} else {
tftp_schedule_timeout(session, session->wallclock_timeout - now);
}
}
static struct pico_socket *tftp_socket_open(uint16_t family, uint16_t localport)
{
struct pico_socket *sock;
union pico_address local_address;
sock = pico_socket_open(family, PICO_PROTO_UDP, tftp_cb);
if (!sock)
return NULL;
localport = short_be(localport);
memset(&local_address, 0, sizeof(union pico_address));
if (pico_socket_bind(sock, &local_address, &localport) < 0) {
pico_socket_close(sock);
return NULL;
}
return sock;
}
static inline int tftp_start_check(struct pico_tftp_session *session, uint16_t port, const char *filename,
int (*user_cb)(struct pico_tftp_session *session, uint16_t err, uint8_t *block, int32_t len, void *arg))
{
if (!session) {
pico_err = PICO_ERR_EINVAL;
return -1;
}
if ((!server.listen_socket) && (port != short_be(PICO_TFTP_PORT))) {
pico_err = PICO_ERR_EINVAL;
return -1;
}
if (!filename) {
pico_err = PICO_ERR_EINVAL;
return -1;
}
if (!user_cb) {
pico_err = PICO_ERR_EINVAL;
return -1;
}
return 0;
}
/* *** EXPORTED FUNCTIONS *** */
struct pico_tftp_session *pico_tftp_session_setup(union pico_address *a, uint16_t family)
{
struct pico_socket *sock;
sock = tftp_socket_open(family, 0);
if (!sock)
return NULL;
return pico_tftp_session_create(sock, a);
}
int pico_tftp_get_option(struct pico_tftp_session *session, uint8_t type, int32_t *value)
{
if (!session) {
pico_err = PICO_ERR_EINVAL;
return -1;
}
switch (type) {
case PICO_TFTP_OPTION_FILE:
if (session->options & PICO_TFTP_OPTION_FILE)
*value = session->file_size;
else {
pico_err = PICO_ERR_ENOENT;
return -1;
}
break;
case PICO_TFTP_OPTION_TIME:
if (session->options & PICO_TFTP_OPTION_TIME)
*value = session->option_timeout;
else {
pico_err = PICO_ERR_ENOENT;
return -1;
}
break;
default:
pico_err = PICO_ERR_EINVAL;
return -1;
}
return 0;
}
int pico_tftp_set_option(struct pico_tftp_session *session, uint8_t type, int32_t value)
{
if (!session) {
pico_err = PICO_ERR_EINVAL;
return -1;
}
switch (type) {
case PICO_TFTP_OPTION_FILE:
if (value < 0) {
pico_err = PICO_ERR_EINVAL;
return -1;
}
session->file_size = value;
session->options |= PICO_TFTP_OPTION_FILE;
break;
case PICO_TFTP_OPTION_TIME:
if (value > PICO_TFTP_MAX_TIMEOUT) {
pico_err = PICO_ERR_EINVAL;
return -1;
}
session->option_timeout = (uint8_t)(value & 0xFF);
if (value) {
session->options |= PICO_TFTP_OPTION_TIME;
} else {
session->options &= ~PICO_TFTP_OPTION_TIME;
}
break;
default:
pico_err = PICO_ERR_EINVAL;
return -1;
}
return 0;
}
/* Active RX request from PicoTCP */
int pico_tftp_start_rx(struct pico_tftp_session *session, uint16_t port, const char *filename,
int (*user_cb)(struct pico_tftp_session *session, uint16_t event, uint8_t *block, int32_t len, void *arg), void *arg)
{
if (tftp_start_check(session, port, filename, user_cb))
return -1;
session->callback = user_cb;
session->packet_counter = 0u;
session->argument = arg;
add_session(session);
if (port != short_be(PICO_TFTP_PORT)) {
session->remote_port = port;
session->state = TFTP_STATE_RX;
if (session->options & (PICO_TFTP_OPTION_FILE | PICO_TFTP_OPTION_TIME))
tftp_send_oack(session);
else
tftp_send_ack(session);
} else {
tftp_send_rx_req(session, &session->remote_address, port, filename);
}
return 0;
}
int pico_tftp_start_tx(struct pico_tftp_session *session, uint16_t port, const char *filename,
int (*user_cb)(struct pico_tftp_session *session, uint16_t event, uint8_t *block, int32_t len, void *arg), void *arg)
{
if (tftp_start_check(session, port, filename, user_cb))
return -1;
session->callback = user_cb;
session->packet_counter = 1u;
session->argument = arg;
add_session(session);
if (port != short_be(PICO_TFTP_PORT)) {
session->remote_port = port;
if (session->options) {
tftp_send_oack(session);
session->state = TFTP_STATE_WAIT_OPT_CONFIRM;
} else {
do_callback(session, PICO_TFTP_EV_OK, NULL, 0);
}
} else
tftp_send_tx_req(session, &session->remote_address, port, filename);
return 0;
}
int pico_tftp_reject_request(union pico_address*addr, uint16_t port, uint16_t error_code, const char*error_message)
{
return send_error(server.tftp_block, server.listen_socket, addr, port, error_code, error_message);
}
int32_t pico_tftp_send(struct pico_tftp_session *session, const uint8_t *data, int32_t len)
{
int32_t size;
if (len < 0) {
pico_err = PICO_ERR_EINVAL;
return -1;
}
size = len;
if (size > PICO_TFTP_PAYLOAD_SIZE) {
pico_err = PICO_ERR_EINVAL;
return -1;
}
tftp_send_data(session, data, size);
return len;
}
int pico_tftp_listen(uint16_t family, void (*cb)(union pico_address *addr, uint16_t port, uint16_t opcode, char *filename, int32_t len))
{
struct pico_socket *sock;
if (server.listen_socket) {
pico_err = PICO_ERR_EEXIST;
return -1;
}
sock = tftp_socket_open(family, PICO_TFTP_PORT);
if (!sock)
return -1;
server.listen_socket = sock;
server.listen_callback = cb;
return 0;
}
int pico_tftp_parse_request_args(char *args, int32_t len, int *options, uint8_t *timeout, int32_t *filesize)
{
char *pos;
char *end_args = args + len;
args = extract_arg_pointer(args, end_args, &pos);
return parse_optional_arguments(args, (int32_t)(end_args - args), options, timeout, filesize);
}
int pico_tftp_abort(struct pico_tftp_session *session, uint16_t error, const char *reason)
{
if (!session) {
pico_err = PICO_ERR_EINVAL;
return -1;
}
if (!find_session_by_socket(session->socket)) {
pico_err = PICO_ERR_EINVAL;
return -1;
}
tftp_send_error(session, NULL, 0, error, reason);
return 0;
}
int pico_tftp_close_server(void)
{
if (!server.listen_socket) {
pico_err = PICO_ERR_EINVAL;
return -1;
}
pico_socket_close(server.listen_socket);
server.listen_socket = NULL;
return 0;
}
int pico_tftp_get_file_size(struct pico_tftp_session *session, int32_t *file_size)
{
return pico_tftp_get_option(session, PICO_TFTP_OPTION_FILE, file_size);
}
struct pico_tftp_session *pico_tftp_app_setup(union pico_address *a, uint16_t port, uint16_t family, int *synchro)
{
struct pico_tftp_session *session;
if (!synchro) {
pico_err = PICO_ERR_EINVAL;
return NULL;
}
session = pico_tftp_session_setup(a, family);
if (!session)
return NULL;
session->remote_port = port;
session->status |= SESSION_STATUS_APP_ACK;
session->argument = synchro;
*synchro = 0;
return session;
}
int pico_tftp_app_start_rx(struct pico_tftp_session *session, const char *filename)
{
return pico_tftp_start_rx(session, session->remote_port, filename, application_rx_cb, session->argument);
}
int pico_tftp_app_start_tx(struct pico_tftp_session *session, const char *filename)
{
return pico_tftp_start_tx(session, session->remote_port, filename, application_tx_cb, session->argument);
}
int32_t pico_tftp_get(struct pico_tftp_session *session, uint8_t *data, int32_t len)
{
int synchro;
if (!session || len < session->len ) {
pico_err = PICO_ERR_EINVAL;
return -1;
}
synchro = *(int*)session->argument;
*(int*)session->argument = 0;
if ((session->state != TFTP_STATE_RX) && (session->state != TFTP_STATE_READ_REQUESTED))
return -1;
if (synchro < 0)
return synchro;
memcpy(data, tftp_payload(session->tftp_block), (size_t)session->len);
len = session->len;
tftp_send_ack(session);
tftp_eval_finish(session, len);
return len;
}
int32_t pico_tftp_put(struct pico_tftp_session *session, uint8_t *data, int32_t len)
{
int synchro;
if ((!session) || (!data) || (len < 0)) {
pico_err = PICO_ERR_EINVAL;
return -1;
}
synchro = *(int*)session->argument;
*(int*)session->argument = 0;
if (synchro < 0)
return synchro;
if (len > PICO_TFTP_PAYLOAD_SIZE)
len = PICO_TFTP_PAYLOAD_SIZE;
pico_tftp_send(session, data, len);
return len;
}