1// Copyright 2017 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#define _POSIX_C_SOURCE 200809L  // for strnlen
6#include <arpa/inet.h>
7#include <ctype.h>
8#include <inttypes.h>
9#include <stdarg.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13#include <strings.h>
14
15#include "internal.h"
16
17// TODO: update this
18// RRQ    ->
19//        <- DATA or OACK or ERROR
20// ACK(0) -> (to confirm reception of OACK)
21// ERROR  -> (on OACK with non requested options)
22//        <- DATA(1)
23// ACK(1) ->
24
25// WRQ     ->
26//         <- ACK or OACK or ERROR
27// DATA(1) ->
28// ERROR   -> (on OACK with non requested options)
29//        <- DATA(2)
30// ACK(2) ->
31
32// MODE
33static const char* kNetascii = "NETASCII";
34static const char* kOctet = "OCTET";
35static const char* kMail = "MAIL";
36static const size_t kMaxMode = 9;  // strlen(NETASCII) + 1
37
38// TSIZE
39// Limit transfer to less than 10GB
40static const char* kTsize = "TSIZE";
41static const size_t kTsizeLen = 5; // strlen(kTsize)
42static const size_t kMaxTsizeOpt = 17;  // strlen(TSIZE) + 1 + strlen(1000000000) + 1
43
44// BLKSIZE
45// Max size is 65535 (max IP datagram)
46static const char* kBlkSize = "BLKSIZE";
47static const size_t kBlkSizeLen = 7; // strlen(kBlkSize)
48static const size_t kMaxBlkSizeOpt = 15; // kBlkSizeLen + strlen("!") + 1 + strlen(65535) + 1
49
50// TIMEOUT
51// Max is 255 (RFC 2349)
52static const char* kTimeout = "TIMEOUT";
53static const size_t kTimeoutLen = 7; // strlen(kTimeout)
54static const size_t kMaxTimeoutOpt = 13; // kTimeoutLen + strlen("!") + 1 + strlen(255) + 1;
55
56// WINDOWSIZE
57// Max is 65535 (RFC 7440)
58static const char* kWindowSize = "WINDOWSIZE";
59static const size_t kWindowSizeLen = 10; // strlen(kWindowSize);
60static const size_t kMaxWindowSizeOpt = 18; // kWindowSizeLen + strlen("!") + 1 + strlen(65535) + 1;
61
62// Since RRQ and WRQ come before option negotation, they are limited to max TFTP
63// blocksize of 512 (RFC 1350 and 2347).
64static const size_t kMaxRequestSize = 512;
65
66#if defined(TFTP_HOSTLIB)
67// Host (e.g., netcp, bootserver)
68#include <time.h>
69#define DEBUG 0
70#elif defined(TFTP_USERLIB)
71// Fuchsia (e.g., netsvc)
72#define DEBUG 0
73#elif defined(TFTP_EFILIB)
74// Bootloader: use judiciously, since the console can easily become overwhelmed and hang
75#define DEBUG 0
76#else
77#error unable to identify target environment
78#endif
79
80#if DEBUG
81# define xprintf(args...) fprintf(stderr, args)
82#else
83# define xprintf(args...)
84#endif
85
86#define __ATTR_PRINTF(__fmt, __varargs) \
87    __attribute__((__format__(__printf__, __fmt, __varargs)))
88#define MIN(x,y) ((x) < (y) ? (x) : (y))
89
90static void append_option_name(char** body, size_t* left, const char* name) {
91    size_t offset = strlen(name);
92    memcpy(*body, name, offset);
93    offset++;
94    *body += offset;
95    *left -= offset;
96}
97
98static void __ATTR_PRINTF(5, 6) append_option(char** body, size_t* left, const char* name,
99        bool force, const char* fmt, ...) {
100    char* bodyp = *body;
101    size_t leftp = *left;
102
103    size_t offset = strlen(name);
104    memcpy(bodyp, name, offset);
105    if (force) {
106        bodyp[offset] = '!';
107        offset++;
108    }
109    offset++;
110    bodyp += offset;
111    leftp -= offset;
112    va_list args;
113    va_start(args, fmt);
114    offset = vsnprintf(bodyp, leftp - 1, fmt, args);
115    va_end(args);
116    offset++;
117    bodyp += offset;
118    leftp -= offset;
119
120    *body = bodyp;
121    *left = leftp;
122}
123
124#define OPCODE(session, msg, value)                                                           \
125    do {                                                                                      \
126        if (session->use_opcode_prefix) {                                                     \
127            (msg)->opcode = htons((value & 0xff) | ((uint16_t)session->opcode_prefix << 8));  \
128        } else {                                                                              \
129            (msg)->opcode = htons(value);                                                     \
130        }                                                                                     \
131    } while (0)
132
133#define TRANSMIT_MORE 1
134#define TRANSMIT_WAIT_ON_ACK 2
135
136static size_t next_option(char* buffer, size_t len, char** option, char** value) {
137    size_t left = len;
138    size_t option_len = strnlen(buffer, left);
139    if (option_len == len) {
140        return 0;
141    }
142
143    *option = buffer;
144    xprintf("'%s' %ld\n", *option, option_len);
145    buffer += option_len + 1;
146    left -= option_len + 1;
147    size_t value_len = strnlen(buffer, left);
148    if (value_len == left) {
149        return 0;
150    }
151    *value = buffer;
152    xprintf("'%s' %ld\n", *value, value_len);
153    left -= value_len + 1;
154    return len - left;
155}
156
157/* Build an err packet in resp_buf and set session state to ERROR
158
159     2 bytes        2 bytes    string   1 byte
160   +--------------+----------+---------+------+
161   | OPCODE_ERROR | ERR_CODE | ERR_MSG |   0  |
162   +--------------+----------+---------+------+
163*/
164static void set_error(tftp_session* session, uint16_t err_code, void* resp_buf,
165                      size_t* resp_len, const char* err_msg) {
166    tftp_err_msg* resp = resp_buf;
167    OPCODE(session, resp, OPCODE_ERROR);
168    resp->err_code = htons(err_code);
169    size_t err_msg_len = strlen(err_msg);
170    size_t max_msg_sz = *resp_len - (sizeof(tftp_err_msg) + 1);
171    if (err_msg_len >= max_msg_sz) {
172        memcpy(resp->msg, err_msg, max_msg_sz);
173        resp->msg[max_msg_sz] = '\0';
174        // *resp_len is unchanged - the whole buffer was used
175    } else {
176        strcpy(resp->msg, err_msg);
177        *resp_len = sizeof(tftp_err_msg) + err_msg_len + 1;
178    }
179    session->state = ERROR;
180}
181
182tftp_status tx_data(tftp_session* session, tftp_data_msg* resp, size_t* outlen, void* cookie) {
183    session->offset = (session->block_number + session->window_index) * session->block_size;
184    *outlen = 0;
185    if (session->offset <= session->file_size) {
186        session->window_index++;
187        OPCODE(session, resp, OPCODE_DATA);
188        resp->block = htons(session->block_number + session->window_index);
189        size_t len = MIN(session->file_size - session->offset, session->block_size);
190        xprintf(" -> Copying block #%" PRIu64 " (size:%zu/%d) from %zu/%zu [%d/%d]\n",
191                session->block_number + session->window_index, len, session->block_size,
192                session->offset, session->file_size, session->window_index, session->window_size);
193        void* buf = resp->data;
194        size_t len_remaining = len;
195        size_t off = session->offset;
196        while (len_remaining > 0) {
197            // TODO(tkilbourn): assert that these function pointers are set
198            size_t rr = len_remaining;
199            tftp_status s = session->file_interface.read(buf, &rr, off, cookie);
200            if (s < 0) {
201                xprintf("Err reading: %d\n", s);
202                return s;
203            }
204            buf += rr;
205            off += rr;
206            len_remaining -= rr;
207        }
208        *outlen = sizeof(*resp) + len;
209
210        if (session->window_index < session->window_size) {
211            xprintf(" -> TRANSMIT_MORE(%d < %d)\n", session->window_index, session->window_size);
212        } else {
213            xprintf(" -> TRANSMIT_WAIT_ON_ACK(%d >= %d)\n", session->window_index,
214                    session->window_size);
215        }
216    } else {
217        xprintf(" -> TRANSMIT_WAIT_ON_ACK(completed)\n");
218    }
219    return TFTP_NO_ERROR;
220}
221
222size_t tftp_sizeof_session(void) {
223    return sizeof(tftp_session);
224}
225
226int tftp_init(tftp_session** session, void* buffer, size_t size) {
227    if (buffer == NULL) {
228        return TFTP_ERR_INVALID_ARGS;
229    }
230    if (size < sizeof(tftp_session)) {
231        return TFTP_ERR_BUFFER_TOO_SMALL;
232    }
233    *session = buffer;
234    tftp_session* s = *session;
235    memset(s, 0, sizeof(tftp_session));
236
237    // Sensible defaults for non-negotiated values
238    s->file_size = DEFAULT_FILESIZE;
239    s->mode = DEFAULT_MODE;
240    s->max_timeouts = DEFAULT_MAX_TIMEOUTS;
241    s->use_opcode_prefix = DEFAULT_USE_OPCODE_PREFIX;
242
243    return TFTP_NO_ERROR;
244}
245
246tftp_status tftp_session_set_file_interface(tftp_session* session,
247                                            tftp_file_interface* callbacks) {
248    if (session == NULL) {
249        return TFTP_ERR_INVALID_ARGS;
250    }
251
252    session->file_interface = *callbacks;
253    return TFTP_NO_ERROR;
254}
255
256tftp_status tftp_session_set_transport_interface(tftp_session* session,
257                                                 tftp_transport_interface* callbacks) {
258    if (session == NULL) {
259        return TFTP_ERR_INVALID_ARGS;
260    }
261    session->transport_interface = *callbacks;
262    return TFTP_NO_ERROR;
263}
264
265bool tftp_session_has_pending(tftp_session* session) {
266    return session->direction == SEND_FILE &&
267           session->window_index > 0 &&
268           session->window_index < session->window_size &&
269           ((session->block_number + session->window_index) * session->block_size) <=
270            session->file_size;
271}
272
273tftp_status tftp_set_options(tftp_session* session, const uint16_t* block_size,
274                             const uint8_t* timeout, const uint16_t* window_size) {
275    session->options.mask = 0;
276    if (block_size) {
277        session->options.block_size = *block_size;
278        session->options.mask |= BLOCKSIZE_OPTION;
279    }
280    if (timeout) {
281        session->options.timeout = *timeout;
282        session->options.mask |= TIMEOUT_OPTION;
283    }
284    if (window_size) {
285        session->options.window_size = *window_size;
286        session->options.mask |= WINDOWSIZE_OPTION;
287    }
288    return TFTP_NO_ERROR;
289}
290
291tftp_status tftp_generate_request(tftp_session* session,
292                                  tftp_file_direction direction,
293                                  const char* local_filename,
294                                  const char* remote_filename,
295                                  tftp_mode mode,
296                                  size_t datalen,
297                                  const uint16_t* block_size,
298                                  const uint8_t* timeout,
299                                  const uint16_t* window_size,
300                                  void* outgoing,
301                                  size_t* outlen,
302                                  uint32_t* timeout_ms) {
303    if (*outlen < 2) {
304        xprintf("outlen too short: %zd\n", *outlen);
305        return TFTP_ERR_BUFFER_TOO_SMALL;
306    }
307
308    // The actual options are not set until we get a confirmation OACK message. Until then,
309    // we have to assume the TFTP defaults.
310    session->block_size = DEFAULT_BLOCKSIZE;
311    session->timeout = DEFAULT_TIMEOUT;
312    session->window_size = DEFAULT_WINDOWSIZE;
313
314    tftp_msg* ack = outgoing;
315    OPCODE(session, ack, (direction == SEND_FILE) ? OPCODE_WRQ : OPCODE_RRQ);
316    char* body = ack->data;
317    memset(body, 0, *outlen - sizeof(*ack));
318    size_t left = *outlen - sizeof(*ack);
319    size_t remote_filename_len = strlen(remote_filename);
320    if (remote_filename_len + 1 > left - kMaxMode) {
321        xprintf("filename too long %zd > %zd\n", remote_filename_len, left - kMaxMode);
322        return TFTP_ERR_INVALID_ARGS;
323    }
324    memcpy(body, remote_filename, remote_filename_len);
325    body += remote_filename_len + 1;
326    left -= remote_filename_len + 1;
327    strncpy(session->filename, local_filename, sizeof(session->filename) - 1);
328    session->filename[sizeof(session->filename) - 1] = '\0';
329    switch (mode) {
330    case MODE_NETASCII:
331        append_option_name(&body, &left, kNetascii);
332        break;
333    case MODE_OCTET:
334        append_option_name(&body, &left, kOctet);
335        break;
336    case MODE_MAIL:
337        append_option_name(&body, &left, kMail);
338        break;
339    default:
340        return TFTP_ERR_INVALID_ARGS;
341    }
342    session->mode = mode;
343
344    if (left < kMaxTsizeOpt) {
345        return TFTP_ERR_BUFFER_TOO_SMALL;
346    }
347    append_option(&body, &left, kTsize, false, "%zu", datalen);
348    session->file_size = datalen;
349    tftp_options* sent_opts = &session->client_sent_opts;
350    sent_opts->mask = 0;
351
352    if (block_size || session->options.mask & BLOCKSIZE_OPTION) {
353        if (left < kMaxBlkSizeOpt) {
354            return TFTP_ERR_BUFFER_TOO_SMALL;
355        }
356        bool force_value;
357        if (block_size) {
358            force_value = true;
359            sent_opts->block_size = *block_size;
360        } else {
361            force_value = false;
362            sent_opts->block_size = session->options.block_size;
363        }
364        append_option(&body, &left, kBlkSize, force_value, "%"PRIu16, sent_opts->block_size);
365        sent_opts->mask |= BLOCKSIZE_OPTION;
366    }
367
368    if (timeout || session->options.mask & TIMEOUT_OPTION) {
369        if (left < kMaxTimeoutOpt) {
370            return TFTP_ERR_BUFFER_TOO_SMALL;
371        }
372        bool force_value;
373        if (timeout) {
374            force_value = true;
375            sent_opts->timeout = *timeout;
376        } else {
377            force_value = false;
378            sent_opts->timeout = session->options.timeout;
379        }
380        append_option(&body, &left, kTimeout, force_value, "%"PRIu8, sent_opts->timeout);
381        sent_opts->mask |= TIMEOUT_OPTION;
382    }
383
384    if (window_size || session->options.mask & WINDOWSIZE_OPTION) {
385        if (left < kMaxWindowSizeOpt) {
386            return TFTP_ERR_BUFFER_TOO_SMALL;
387        }
388        bool force_value;
389        if (window_size) {
390            force_value = true;
391            sent_opts->window_size = *window_size;
392        } else {
393            force_value = false;
394            sent_opts->window_size = session->options.window_size;
395        }
396        append_option(&body, &left, kWindowSize, force_value, "%"PRIu16, sent_opts->window_size);
397        sent_opts->mask |= WINDOWSIZE_OPTION;
398    }
399
400    *outlen = *outlen - left;
401    // Nothing has been negotiated yet so use default
402    *timeout_ms = 1000 * session->timeout;
403
404    session->direction = direction;
405    session->state = REQ_SENT;
406    xprintf("Generated %s request, len=%zu\n",
407            (direction == SEND_FILE) ? "write" : "read", *outlen);
408    return TFTP_NO_ERROR;
409}
410
411tftp_status tftp_handle_request(tftp_session* session,
412                                tftp_file_direction direction,
413                                tftp_msg* req,
414                                size_t req_len,
415                                tftp_msg* resp,
416                                size_t* resp_len,
417                                uint32_t* timeout_ms,
418                                void* cookie) {
419    // We could be in REQ_RECEIVED if our OACK was dropped.
420    if (session->state != NONE && session->state != REQ_RECEIVED) {
421        xprintf("Invalid state transition %d -> %d\n", session->state, REQ_RECEIVED);
422        set_error(session, TFTP_ERR_CODE_UNDEF, resp, resp_len, "invalid state transition");
423        return TFTP_ERR_BAD_STATE;
424    }
425    // opcode, filename, 0, mode, 0, opt1, 0, value1 ... optN, 0, valueN, 0
426    // Max length is 512 no matter
427    if (req_len > kMaxRequestSize) {
428        xprintf("Write request is too large\n");
429        set_error(session, TFTP_ERR_CODE_UNDEF, resp, resp_len, "write request is too large");
430        return TFTP_ERR_INTERNAL;
431    }
432    // Skip opcode
433    size_t left = req_len - sizeof(*resp);
434    char* cur = req->data;
435    char *option, *value;
436    // filename, 0, mode, 0 can be interpreted like option, 0, value, 0
437    size_t offset = next_option(cur, left, &option, &value);
438    if (!offset) {
439        xprintf("No options\n");
440        set_error(session, TFTP_ERR_CODE_BAD_OPTIONS, resp, resp_len, "no options");
441        return TFTP_ERR_INTERNAL;
442    }
443    left -= offset;
444
445    xprintf("filename = '%s', mode = '%s'\n", option, value);
446
447    strncpy(session->filename, option, sizeof(session->filename) - 1);
448    session->filename[sizeof(session->filename) - 1] = '\0';
449    char* mode = value;
450    if (!strncasecmp(mode, kNetascii, strlen(kNetascii))) {
451        session->mode = MODE_NETASCII;
452    } else if (!strncasecmp(mode, kOctet, strlen(kOctet))) {
453        session->mode = MODE_OCTET;
454    } else if (!strncasecmp(mode, kMail, strlen(kMail))) {
455        session->mode = MODE_MAIL;
456    } else {
457        xprintf("Unknown write request mode\n");
458        set_error(session, TFTP_ERR_CODE_BAD_OPTIONS, resp, resp_len, "unknown write request mode");
459        return TFTP_ERR_INTERNAL;
460    }
461
462    // Initialize the values to TFTP defaults
463    session->block_size = DEFAULT_BLOCKSIZE;
464    session->timeout = DEFAULT_TIMEOUT;
465    session->window_size = DEFAULT_WINDOWSIZE;
466
467    // TODO(tkilbourn): refactor option handling code to share with
468    // tftp_handle_oack
469    cur += offset;
470    bool file_size_seen = false;
471    tftp_options requested_options = {.mask = 0};
472    tftp_options* override_opts = &session->options;
473    while (offset > 0 && left > 0) {
474        offset = next_option(cur, left, &option, &value);
475        if (!offset) {
476            xprintf("No more options\n");
477            set_error(session, TFTP_ERR_CODE_BAD_OPTIONS, resp, resp_len, "no more options");
478            return TFTP_ERR_INTERNAL;
479        }
480
481        if (!strncasecmp(option, kTsize, kTsizeLen)) { // RFC 2349
482            if (direction == RECV_FILE) {
483                long val = atol(value);
484                if (val < 0) {
485                    xprintf("invalid file size\n");
486                    set_error(session, TFTP_ERR_CODE_BAD_OPTIONS, resp, resp_len,
487                              "invalid file size");
488                    return TFTP_ERR_INTERNAL;
489                }
490                session->file_size = val;
491            }
492            file_size_seen = true;
493        } else if (!strncasecmp(option, kBlkSize, kBlkSizeLen)) { // RFC 2348
494            bool force_block_size = (option[kBlkSizeLen] == '!');
495            // Valid values range between "8" and "65464" octets, inclusive
496            long val = atol(value);
497            // TODO(tkilbourn): with an MTU of 1500, shouldn't be more than 1428
498            if (val < 8 || val > 65464) {
499                xprintf("invalid block size\n");
500                set_error(session, TFTP_ERR_CODE_BAD_OPTIONS, resp, resp_len, "invalid block size");
501                return TFTP_ERR_INTERNAL;
502            }
503            requested_options.block_size = val;
504            requested_options.mask |= BLOCKSIZE_OPTION;
505            if (force_block_size || !(override_opts->mask & BLOCKSIZE_OPTION)) {
506                session->block_size = val;
507            } else {
508                session->block_size = override_opts->block_size;
509            }
510        } else if (!strncasecmp(option, kTimeout, kTimeoutLen)) { // RFC 2349
511            bool force_timeout_val = (option[kTimeoutLen] == '!');
512            // Valid values range between "1" and "255" seconds inclusive.
513            long val = atol(value);
514            if (val < 1 || val > 255) {
515                xprintf("invalid timeout\n");
516                set_error(session, TFTP_ERR_CODE_BAD_OPTIONS, resp, resp_len, "invalid timeout");
517                return TFTP_ERR_INTERNAL;
518            }
519            requested_options.timeout = val;
520            requested_options.mask |= TIMEOUT_OPTION;
521            if (force_timeout_val || !(override_opts->mask & TIMEOUT_OPTION)) {
522                session->timeout = val;
523            } else {
524                session->timeout = override_opts->timeout;
525            }
526        } else if (!strncasecmp(option, kWindowSize, kWindowSizeLen)) { // RFC 7440
527            bool force_window_size = (option[kWindowSizeLen] == '!');
528            // The valid values range MUST be between 1 and 65535 blocks, inclusive.
529            long val = atol(value);
530            if (val < 1 || val > 65535) {
531                xprintf("invalid window size\n");
532                set_error(session, TFTP_ERR_CODE_BAD_OPTIONS, resp, resp_len,
533                          "invalid window size");
534                return TFTP_ERR_INTERNAL;
535            }
536            requested_options.window_size = val;
537            requested_options.mask |= WINDOWSIZE_OPTION;
538            if (force_window_size || !(override_opts->mask & WINDOWSIZE_OPTION)) {
539                session->window_size = val;
540            } else {
541                session->window_size = override_opts->window_size;
542            }
543        } else {
544            // Options which the server does not support should be omitted from the
545            // OACK; they should not cause an ERROR packet to be generated.
546        }
547
548        cur += offset;
549        left -= offset;
550    }
551
552    char* body = resp->data;
553    memset(body, 0, *resp_len - sizeof(*resp));
554    left = *resp_len - sizeof(*resp);
555
556    OPCODE(session, resp, OPCODE_OACK);
557
558    // Open file, if we haven't already
559    if (session->state == NONE) {
560        if (direction == RECV_FILE) {
561            if (!session->file_interface.open_write) {
562                xprintf("Unable to service write request: no open_write implementation\n");
563                set_error(session, TFTP_ERR_CODE_UNDEF, resp, resp_len, "internal error");
564                return TFTP_ERR_BAD_STATE;
565            }
566            switch(session->file_interface.open_write(session->filename, session->file_size,
567                                                      cookie)) {
568            case TFTP_ERR_SHOULD_WAIT:
569                // The open_write() callback can return an ERR_SHOULD_WAIT response if it isn't
570                // prepared to service another requst at the moment and the client should retry
571                // later.
572                xprintf("Denying write request received when not ready\n");
573                set_error(session, TFTP_ERR_CODE_BUSY, resp, resp_len, "not ready to receive");
574                session->state = NONE;
575                return TFTP_ERR_SHOULD_WAIT;
576            case TFTP_NO_ERROR:
577                break;
578            default:
579                xprintf("Could not open file on write request\n");
580                set_error(session, TFTP_ERR_CODE_ACCESS_VIOLATION, resp, resp_len,
581                          "could not open file for writing");
582                return TFTP_ERR_BAD_STATE;
583            }
584        } else {
585            ssize_t file_size;
586            if (!session->file_interface.open_read) {
587                xprintf("Unable to service read request: no open_read implementation\n");
588                set_error(session, TFTP_ERR_CODE_UNDEF, resp, resp_len, "internal error");
589                return TFTP_ERR_BAD_STATE;
590            }
591
592            file_size = session->file_interface.open_read(session->filename, cookie);
593            if (file_size == TFTP_ERR_SHOULD_WAIT) {
594                // The open_read() callback can return an ERR_SHOULD_WAIT response if it isn't
595                // prepared to service another requst at the moment and the client should retry
596                // later.
597                xprintf("Denying read request received when not ready\n");
598                set_error(session, TFTP_ERR_CODE_BUSY, resp, resp_len, "not ready to send");
599                session->state = NONE;
600                return TFTP_ERR_SHOULD_WAIT;
601            }
602            if (file_size < 0) {
603                xprintf("Unable to open file %s for reading\n", session->filename);
604                set_error(session, TFTP_ERR_CODE_FILE_NOT_FOUND, resp, resp_len,
605                          "could not open file for reading");
606                return TFTP_ERR_BAD_STATE;
607            }
608            session->file_size = file_size;
609        }
610    }
611
612    if (file_size_seen) {
613        append_option(&body, &left, kTsize, false, "%zu", session->file_size);
614    } else {
615        xprintf("No TSIZE option specified\n");
616        set_error(session, TFTP_ERR_CODE_BAD_OPTIONS, resp, resp_len, "no TSIZE option");
617        if (session->file_interface.close) {
618            session->file_interface.close(cookie);
619        }
620        return TFTP_ERR_BAD_STATE;
621    }
622    if (requested_options.mask & BLOCKSIZE_OPTION) {
623        // TODO(jpoichet) Make sure this block size is possible. Need API upwards to
624        // request allocation of block size * window size memory
625        append_option(&body, &left, kBlkSize, false, "%d", session->block_size);
626    }
627    if (requested_options.mask & TIMEOUT_OPTION) {
628        // TODO(jpoichet) Make sure this timeout is possible. Need API upwards to
629        // request allocation of block size * window size memory
630        append_option(&body, &left, kTimeout, false, "%d", session->timeout);
631        *timeout_ms = 1000 * session->timeout;
632    }
633    if (requested_options.mask & WINDOWSIZE_OPTION) {
634        append_option(&body, &left, kWindowSize, false, "%d", session->window_size);
635    }
636    *resp_len = *resp_len - left;
637    session->state = REQ_RECEIVED;
638    session->direction = direction;
639
640    xprintf("%s Request Parsed\n", (direction == SEND_FILE) ? "Read" : "Write");
641    xprintf("    Mode       : %s\n", session->mode == MODE_NETASCII ? "netascii" :
642                                     session->mode == MODE_OCTET ? "octet" :
643                                     session->mode == MODE_MAIL ? "mail" :
644                                     "unrecognized");
645    xprintf("    File Size  : %zu\n", session->file_size);
646    xprintf("Options requested: %08x\n", requested_options.mask);
647    xprintf("    Block Size : %d\n", requested_options.block_size);
648    xprintf("    Timeout    : %d\n", requested_options.timeout);
649    xprintf("    Window Size: %d\n", requested_options.window_size);
650    xprintf("Using options\n");
651    xprintf("    Block Size : %d\n", session->block_size);
652    xprintf("    Timeout    : %d\n", session->timeout);
653    xprintf("    Window Size: %d\n", session->window_size);
654
655    return TFTP_NO_ERROR;
656}
657
658tftp_status tftp_handle_wrq(tftp_session* session,
659                            tftp_msg* wrq,
660                            size_t wrq_len,
661                            tftp_msg* resp,
662                            size_t* resp_len,
663                            uint32_t* timeout_ms,
664                            void* cookie) {
665    return tftp_handle_request(session, RECV_FILE, wrq, wrq_len, resp, resp_len, timeout_ms,
666                               cookie);
667}
668
669tftp_status tftp_handle_rrq(tftp_session* session,
670                            tftp_msg* rrq,
671                            size_t rrq_len,
672                            tftp_msg* resp,
673                            size_t* resp_len,
674                            uint32_t* timeout_ms,
675                            void* cookie) {
676    return tftp_handle_request(session, SEND_FILE, rrq, rrq_len, resp, resp_len, timeout_ms,
677                               cookie);
678}
679
680static void tftp_prepare_ack(tftp_session* session,
681                             tftp_msg* msg,
682                             size_t* msg_len) {
683    tftp_data_msg* ack_data = (tftp_data_msg*)msg;
684    xprintf(" -> Ack %" PRIu64 "\n", session->block_number);
685    session->window_index = 0;
686    OPCODE(session, ack_data, OPCODE_ACK);
687    ack_data->block = htons(session->block_number & 0xffff);
688    *msg_len = sizeof(*ack_data);
689}
690
691tftp_status tftp_handle_data(tftp_session* session,
692                             tftp_msg* msg,
693                             size_t msg_len,
694                             tftp_msg* resp,
695                             size_t* resp_len,
696                             uint32_t* timeout_ms,
697                             void* cookie) {
698    if ((session->direction == RECV_FILE) &&
699        ((session->state == REQ_RECEIVED) ||
700         (session->state == FIRST_DATA) ||
701         (session->state == RECEIVING_DATA))) {
702        session->state = RECEIVING_DATA;
703    } else {
704        set_error(session, TFTP_ERR_CODE_UNDEF, resp, resp_len, "internal error: bad state");
705        return TFTP_ERR_INTERNAL;
706    }
707
708    tftp_data_msg* data = (tftp_data_msg*)msg;
709
710    uint16_t block_num = ntohs(data->block);
711
712    // The block field of the message is only 16 bits wide. To support large files
713    // (> 65535 * blocksize bytes), we allow the block number to wrap. We use signed modulo
714    // math to determine the relative location of the block to our current position.
715    int16_t block_delta = block_num - (uint16_t)session->block_number;
716    xprintf(" <- Block %" PRIu64 " (Last = %" PRIu64 ", Offset = %" PRIu64
717                ", Size = %zd, Left = %" PRIu64 ")\n",
718            session->block_number + block_delta, session->block_number,
719            session->block_number * session->block_size, session->file_size,
720            session->file_size - session->block_number * session->block_size);
721    if (block_delta == 1) {
722        xprintf("Advancing normally + 1\n");
723        void* buf = data->data;
724        size_t len = msg_len - sizeof(tftp_data_msg);
725        size_t off = session->block_number * session->block_size;
726        while (len > 0) {
727            tftp_status ret;
728            // TODO(tkilbourn): assert that these function pointers are set
729            size_t wr = len;
730            ret = session->file_interface.write(buf, &wr, off, cookie);
731            if (ret < 0) {
732                xprintf("Error writing: %d\n", ret);
733                return ret;
734            }
735            buf += wr;
736            off += wr;
737            len -= wr;
738        }
739        session->block_number++;
740        session->window_index++;
741    } else if (block_delta > 1) {
742        // Force sending a ACK with the last block_number we received
743        xprintf("Skipped: got %" PRIu64 ", expected %" PRIu64 "\n",
744                session->block_number + block_delta, session->block_number + 1);
745        session->window_index = session->window_size;
746        // It's possible that a previous ACK wasn't received, increment the prefix
747        if (session->use_opcode_prefix) {
748            session->opcode_prefix++;
749        }
750    }
751
752    if (session->window_index == session->window_size ||
753            session->block_number * session->block_size > session->file_size) {
754        tftp_prepare_ack(session, resp, resp_len);
755        if (session->block_number * session->block_size > session->file_size) {
756            return TFTP_TRANSFER_COMPLETED;
757        }
758    } else {
759        // Nothing to send
760        *resp_len = 0;
761    }
762    return TFTP_NO_ERROR;
763}
764
765tftp_status tftp_handle_ack(tftp_session* session,
766                            tftp_msg* ack,
767                            size_t ack_len,
768                            tftp_msg* resp,
769                            size_t* resp_len,
770                            uint32_t* timeout_ms,
771                            void* cookie) {
772    if ((session->direction != SEND_FILE) ||
773        ((session->state != FIRST_DATA) &&
774         (session->state != REQ_RECEIVED) &&
775         (session->state != SENDING_DATA))) {
776        set_error(session, TFTP_ERR_CODE_UNDEF, resp, resp_len, "internal error: bad state");
777        return TFTP_ERR_INTERNAL;
778    }
779    // Need to move forward in data and send it
780    tftp_data_msg* ack_data = (void*)ack;
781    tftp_data_msg* resp_data = (void*)resp;
782
783    uint16_t ack_block = ntohs(ack_data->block);
784    xprintf(" <- Ack %d\n", ack_block);
785
786    // Since we track blocks in 32 bits, but the packets only support 16 bits, calculate the
787    // signed 16 bit offset to determine the adjustment to the current position.
788    int16_t block_offset = ack_block - (uint16_t)session->block_number;
789
790    if (session->state != FIRST_DATA && session->state != REQ_RECEIVED && block_offset == 0) {
791        // Don't acknowledge duplicate ACKs, avoiding the "Sorcerer's Apprentice Syndrome"
792        *resp_len = 0;
793        return TFTP_NO_ERROR;
794    }
795
796    if (block_offset < session->window_size) {
797        // If it looks like some of our data might have been dropped, modify the prefix
798        // before resending.
799        if (session->use_opcode_prefix) {
800            session->opcode_prefix++;
801        }
802    }
803    session->state = SENDING_DATA;
804    session->block_number += block_offset;
805    session->window_index = 0;
806
807    if (session->block_number * session->block_size > session->file_size) {
808        *resp_len = 0;
809        return TFTP_TRANSFER_COMPLETED;
810    }
811
812    tftp_status ret = tx_data(session, resp_data, resp_len, cookie);
813    if (ret < 0) {
814        set_error(session, TFTP_ERR_CODE_UNDEF, resp, resp_len, "could not transmit data");
815    }
816    return ret;
817}
818
819tftp_status tftp_handle_error(tftp_session* session,
820                              tftp_err_msg* err,
821                              size_t err_len,
822                              tftp_msg* resp,
823                              size_t* resp_len,
824                              uint32_t* timeout_ms,
825                              void* cookie) {
826    uint16_t err_code = ntohs(err->err_code);
827
828    // There's no need to respond to an error
829    *resp_len = 0;
830
831    if (err_code == TFTP_ERR_CODE_BUSY) {
832        xprintf("Target busy\n");
833        session->state = NONE;
834        return TFTP_ERR_SHOULD_WAIT;
835    }
836    xprintf("Target sent error %d\n", err_code);
837    session->state = ERROR;
838    return TFTP_ERR_INTERNAL;
839}
840
841tftp_status tftp_handle_oack(tftp_session* session,
842                             tftp_msg* oack,
843                             size_t oack_len,
844                             tftp_msg* resp,
845                             size_t* resp_len,
846                             uint32_t* timeout_ms,
847                             void* cookie) {
848    xprintf("Option Ack\n");
849    if (session->state == REQ_SENT || session->state == FIRST_DATA) {
850        session->state = FIRST_DATA;
851    }
852
853    size_t left = oack_len - sizeof(*oack);
854    char* cur = oack->data;
855    size_t offset;
856    char *option, *value;
857
858    while (left > 0) {
859        offset = next_option(cur, left, &option, &value);
860        if (!offset) {
861            set_error(session, TFTP_ERR_CODE_BAD_OPTIONS, resp, resp_len, "invalid option format");
862            return TFTP_ERR_INTERNAL;
863        }
864
865        if (!strncasecmp(option, kTsize, kTsizeLen)) { // RFC 2349
866            if (session->direction == RECV_FILE) {
867                session->file_size = atol(value);
868            }
869            // If we are sending the file, we don't care what value the server wrote in here
870        } else if (!strncasecmp(option, kBlkSize, kBlkSizeLen)) { // RFC 2348
871            if (!(session->client_sent_opts.mask & BLOCKSIZE_OPTION)) {
872                xprintf("block size not requested\n");
873                set_error(session, TFTP_ERR_CODE_BAD_OPTIONS, resp, resp_len, "no block size");
874                return TFTP_ERR_INTERNAL;
875            }
876            // Valid values range between "8" and "65464" octets, inclusive
877            long val = atol(value);
878            if (val < 8 || val > 65464) {
879                xprintf("invalid block size\n");
880                set_error(session, TFTP_ERR_CODE_BAD_OPTIONS, resp, resp_len, "invalid block size");
881                return TFTP_ERR_INTERNAL;
882            }
883            // TODO(tkilbourn): with an MTU of 1500, shouldn't be more than 1428
884            session->block_size = val;
885        } else if (!strncasecmp(option, kTimeout, kTimeoutLen)) { // RFC 2349
886            if (!(session->client_sent_opts.mask & TIMEOUT_OPTION)) {
887                xprintf("timeout not requested\n");
888                set_error(session, TFTP_ERR_CODE_BAD_OPTIONS, resp, resp_len, "no timeout");
889                return TFTP_ERR_INTERNAL;
890            }
891            // Valid values range between "1" and "255" seconds inclusive.
892            long val = atol(value);
893            if (val < 1 || val > 255) {
894                xprintf("invalid timeout\n");
895                set_error(session, TFTP_ERR_CODE_BAD_OPTIONS, resp, resp_len, "invalid timeout");
896                return TFTP_ERR_INTERNAL;
897            }
898            session->timeout = val;
899        } else if (!strncasecmp(option, kWindowSize, kWindowSizeLen)) { // RFC 7440
900            if (!(session->client_sent_opts.mask & WINDOWSIZE_OPTION)) {
901                xprintf("window size not requested\n");
902                set_error(session, TFTP_ERR_CODE_BAD_OPTIONS, resp, resp_len, "no window size");
903                return TFTP_ERR_INTERNAL;
904            }
905            // The valid values range MUST be between 1 and 65535 blocks, inclusive.
906            long val = atol(value);
907            if (val < 1 || val > 65535) {
908                xprintf("invalid window size\n");
909                set_error(session, TFTP_ERR_CODE_BAD_OPTIONS, resp, resp_len,
910                          "invalid window size");
911                return TFTP_ERR_INTERNAL;
912            }
913            session->window_size = val;
914        } else {
915            // Options which the server does not support should be omitted from the
916            // OACK; they should not cause an ERROR packet to be generated.
917        }
918
919        cur += offset;
920        left -= offset;
921    }
922    *timeout_ms = 1000 * session->timeout;
923
924    xprintf("Options negotiated\n");
925    xprintf("    File Size  : %zu\n", session->file_size);
926    xprintf("    Block Size : %d\n", session->block_size);
927    xprintf("    Timeout    : %d\n", session->timeout);
928    xprintf("    Window Size: %d\n", session->window_size);
929
930    session->offset = 0;
931    session->block_number = 0;
932    session->window_index = 0;
933
934    if (session->direction == SEND_FILE) {
935        tftp_data_msg* resp_data = (void*)resp;
936        tftp_status ret = tx_data(session, resp_data, resp_len, cookie);
937        if (ret < 0) {
938            set_error(session, TFTP_ERR_CODE_UNDEF, resp, resp_len, "failure to transmit data");
939        }
940        return ret;
941    } else {
942        if (!session->file_interface.open_write ||
943            session->file_interface.open_write(session->filename, session->file_size,
944                                               cookie)) {
945            xprintf("Could not open file on write request\n");
946            set_error(session, TFTP_ERR_CODE_UNDEF, resp, resp_len, "could not open file for writing");
947            return TFTP_ERR_BAD_STATE;
948        }
949        tftp_prepare_ack(session, resp, resp_len);
950        return TFTP_NO_ERROR;
951    }
952}
953
954tftp_status tftp_process_msg(tftp_session* session,
955                             void* incoming,
956                             size_t inlen,
957                             void* outgoing,
958                             size_t* outlen,
959                             uint32_t* timeout_ms,
960                             void* cookie) {
961    tftp_msg* msg = incoming;
962    tftp_msg* resp = outgoing;
963
964    // Decode opcode
965    uint16_t opcode = ntohs(msg->opcode) & 0xff;
966    xprintf("handle_msg opcode=%u length=%d\n", opcode, (int)inlen);
967
968    // Set default timeout
969    *timeout_ms = 1000 * session->timeout;
970
971    // Reset timeout count
972    session->consecutive_timeouts = 0;
973
974    switch (opcode) {
975    case OPCODE_RRQ:
976        return tftp_handle_rrq(session, incoming, inlen, resp, outlen, timeout_ms, cookie);
977    case OPCODE_WRQ:
978        return tftp_handle_wrq(session, incoming, inlen, resp, outlen, timeout_ms, cookie);
979    case OPCODE_DATA:
980        return tftp_handle_data(session, incoming, inlen, resp, outlen, timeout_ms, cookie);
981    case OPCODE_ACK:
982        return tftp_handle_ack(session, incoming, inlen, resp, outlen, timeout_ms, cookie);
983    case OPCODE_ERROR:
984        return tftp_handle_error(session, incoming, inlen, resp, outlen, timeout_ms, cookie);
985    case OPCODE_OACK:
986        return tftp_handle_oack(session, incoming, inlen, resp, outlen, timeout_ms, cookie);
987    default:
988        xprintf("Unknown opcode\n");
989        session->state = ERROR;
990        return TFTP_ERR_INTERNAL;
991    }
992}
993
994tftp_status tftp_prepare_data(tftp_session* session,
995                              void* outgoing,
996                              size_t* outlen,
997                              uint32_t* timeout_ms,
998                              void* cookie) {
999    tftp_data_msg* resp_data = outgoing;
1000
1001    if ((session->block_number + session->window_index) * session->block_size > session->file_size) {
1002        *outlen = 0;
1003        return TFTP_TRANSFER_COMPLETED;
1004    }
1005
1006    tftp_status ret = tx_data(session, resp_data, outlen, cookie);
1007    if (ret < 0) {
1008        set_error(session, TFTP_ERR_CODE_UNDEF, outgoing, outlen, "failure to transmit data");
1009    }
1010    return ret;
1011}
1012
1013void tftp_session_set_max_timeouts(tftp_session* session,
1014                                   uint16_t max_timeouts) {
1015    session->max_timeouts = max_timeouts;
1016}
1017
1018void tftp_session_set_opcode_prefix_use(tftp_session* session,
1019                                        bool enable) {
1020    session->use_opcode_prefix = enable;
1021}
1022
1023tftp_status tftp_timeout(tftp_session* session,
1024                         void* msg_buf,
1025                         size_t* msg_len,
1026                         size_t buf_sz,
1027                         uint32_t* timeout_ms,
1028                         void* file_cookie) {
1029    xprintf("Timeout\n");
1030    if (++session->consecutive_timeouts > session->max_timeouts) {
1031        return TFTP_ERR_TIMED_OUT;
1032    }
1033    // It's possible our previous transmission was dropped because of checksum errors.
1034    // Use a different opcode prefix when we resend.
1035    if (session->use_opcode_prefix) {
1036        session->opcode_prefix++;
1037    }
1038    if (session->state == REQ_SENT || session->state == REQ_RECEIVED) {
1039        // Resend previous message
1040        return TFTP_NO_ERROR;
1041    }
1042    *msg_len = buf_sz;
1043    if (session->direction == SEND_FILE) {
1044        // Reset back to the last-acknowledged block
1045        session->window_index = 0;
1046        return tftp_prepare_data(session, msg_buf, msg_len, timeout_ms, file_cookie);
1047    } else {
1048        // ACK up to the last block read
1049        tftp_prepare_ack(session, msg_buf, msg_len);
1050        return TFTP_NO_ERROR;
1051    }
1052}
1053
1054#define REPORT_ERR(opts,...)                                     \
1055    if (opts->err_msg) {                                         \
1056        snprintf(opts->err_msg, opts->err_msg_sz, __VA_ARGS__);  \
1057    }
1058
1059typedef struct {
1060    void* incoming;
1061    size_t in_buf_sz;
1062    void* outgoing;
1063    size_t out_buf_sz;
1064    char* err_msg;
1065    size_t err_msg_sz;
1066} tftp_msg_loop_opts;
1067
1068static tftp_status tftp_msg_loop(tftp_session* session,
1069                                 void* transport_cookie,
1070                                 void* file_cookie,
1071                                 tftp_msg_loop_opts* opts,
1072                                 uint32_t timeout_ms) {
1073    tftp_status ret;
1074    size_t out_sz = 0;
1075
1076    do {
1077        tftp_status send_status;
1078        int result = session->transport_interface.timeout_set(timeout_ms, transport_cookie);
1079        if (result < 0) {
1080            REPORT_ERR(opts, "failed during transport timeout set callback");
1081            return TFTP_ERR_INTERNAL;
1082        }
1083
1084        bool pending = tftp_session_has_pending(session);
1085        int n = session->transport_interface.recv(opts->incoming, opts->in_buf_sz, !pending,
1086                                                  transport_cookie);
1087        if (n == TFTP_ERR_TIMED_OUT) {
1088            if (pending) {
1089                out_sz = opts->out_buf_sz;
1090                ret = tftp_prepare_data(session,
1091                                        opts->outgoing,
1092                                        &out_sz,
1093                                        &timeout_ms,
1094                                        file_cookie);
1095                if (out_sz) {
1096                    send_status = session->transport_interface.send(opts->outgoing, out_sz,
1097                                                                    transport_cookie);
1098                    if (send_status != TFTP_NO_ERROR) {
1099                        REPORT_ERR(opts, "failed during transport send callback");
1100                        return send_status;
1101                    }
1102                }
1103                if (ret < 0) {
1104                    REPORT_ERR(opts, "failed to prepare data to send");
1105                    return ret;
1106                }
1107            } else if (session->state != NONE) {
1108                ret = tftp_timeout(session,
1109                                   opts->outgoing,
1110                                   &out_sz,
1111                                   opts->out_buf_sz,
1112                                   &timeout_ms,
1113                                   file_cookie);
1114                if (ret == TFTP_ERR_TIMED_OUT) {
1115                    REPORT_ERR(opts, "too many consecutive timeouts, aborting");
1116                    return ret;
1117                }
1118                if (ret < 0) {
1119                    REPORT_ERR(opts, "failed during timeout processing");
1120                    return ret;
1121                }
1122                if (out_sz) {
1123                    send_status = session->transport_interface.send(opts->outgoing, out_sz,
1124                                                                    transport_cookie);
1125                    if (send_status != TFTP_NO_ERROR) {
1126                        REPORT_ERR(opts, "failed during transport send callback");
1127                        return n;
1128                    }
1129                }
1130            }
1131            continue;
1132        } else if (n < 0) {
1133            REPORT_ERR(opts, "failed during transport recv callback");
1134            return n;
1135        }
1136
1137        out_sz = opts->out_buf_sz;
1138        ret = tftp_process_msg(session,
1139                               opts->incoming,
1140                               n,
1141                               opts->outgoing,
1142                               &out_sz,
1143                               &timeout_ms,
1144                               file_cookie);
1145        if (out_sz) {
1146            send_status = session->transport_interface.send(opts->outgoing, out_sz,
1147                                                            transport_cookie);
1148            if (send_status != TFTP_NO_ERROR) {
1149                REPORT_ERR(opts, "failed during transport send callback");
1150                return send_status;
1151            }
1152        }
1153        if (ret < 0) {
1154            REPORT_ERR(opts, "failed to handle message");
1155            return ret;
1156        } else if (ret == TFTP_TRANSFER_COMPLETED) {
1157            return ret;
1158        }
1159    } while (1);
1160}
1161
1162static tftp_status transfer_file(tftp_session* session,
1163                                 void* transport_cookie,
1164                                 void* file_cookie,
1165                                 tftp_file_direction xfer_direction,
1166                                 const char* local_filename,
1167                                 const char* remote_filename,
1168                                 tftp_request_opts* opts) {
1169    if (!opts || !opts->inbuf || !opts->inbuf_sz || !opts->outbuf || !opts->outbuf_sz) {
1170        return TFTP_ERR_INVALID_ARGS;
1171    }
1172
1173    tftp_status status;
1174
1175    ssize_t file_size = 0;
1176    if (xfer_direction == SEND_FILE) {
1177        file_size = session->file_interface.open_read(local_filename, file_cookie);
1178        if (file_size < 0) {
1179            REPORT_ERR(opts, "failed during file open callback");
1180            return file_size;
1181        }
1182    }
1183
1184    tftp_mode mode = opts->mode ? *opts->mode : TFTP_DEFAULT_CLIENT_MODE;
1185
1186    size_t out_sz = opts->outbuf_sz;
1187    uint32_t timeout_ms;
1188    status = tftp_generate_request(session,
1189                                   xfer_direction,
1190                                   local_filename,
1191                                   remote_filename,
1192                                   mode,
1193                                   file_size,
1194                                   opts->block_size,
1195                                   opts->timeout,
1196                                   opts->window_size,
1197                                   opts->outbuf,
1198                                   &out_sz,
1199                                   &timeout_ms);
1200
1201    const char* xfer_direction_str = (xfer_direction == SEND_FILE) ? "write" : "read";
1202    if (status < 0) {
1203        REPORT_ERR(opts, "failed to generate %s request", xfer_direction_str);
1204        goto done;
1205    }
1206    if (!out_sz) {
1207        REPORT_ERR(opts, "no %s request generated", xfer_direction_str);
1208        status = TFTP_ERR_INTERNAL;
1209        goto done;
1210    }
1211
1212    status = session->transport_interface.send(opts->outbuf, out_sz, transport_cookie);
1213    if (status != TFTP_NO_ERROR) {
1214        REPORT_ERR(opts, "failed during transport send callback");
1215        goto done;
1216    }
1217
1218    tftp_msg_loop_opts msg_loop_opts = {.incoming = opts->inbuf,
1219                                        .in_buf_sz = opts->inbuf_sz,
1220                                        .outgoing = opts->outbuf,
1221                                        .out_buf_sz = opts->outbuf_sz,
1222                                        .err_msg = opts->err_msg,
1223                                        .err_msg_sz = opts->err_msg_sz};
1224    status = tftp_msg_loop(session, transport_cookie, file_cookie, &msg_loop_opts, timeout_ms);
1225
1226done:
1227    if ((xfer_direction == SEND_FILE) || (session->state != NONE)) {
1228        if (session->file_interface.close) {
1229            session->file_interface.close(file_cookie);
1230        }
1231    }
1232    return status;
1233}
1234
1235tftp_status tftp_push_file(tftp_session* session,
1236                           void* transport_cookie,
1237                           void* file_cookie,
1238                           const char* local_filename,
1239                           const char* remote_filename,
1240                           tftp_request_opts* opts) {
1241    return transfer_file(session, transport_cookie, file_cookie, SEND_FILE,
1242                         local_filename, remote_filename, opts);
1243}
1244
1245tftp_status tftp_pull_file(tftp_session* session,
1246                           void* transport_cookie,
1247                           void* file_cookie,
1248                           const char* local_filename,
1249                           const char* remote_filename,
1250                           tftp_request_opts* opts) {
1251    return transfer_file(session, transport_cookie, file_cookie, RECV_FILE,
1252                         local_filename, remote_filename, opts);
1253}
1254
1255tftp_status tftp_service_request(tftp_session* session,
1256                                 void* transport_cookie,
1257                                 void* file_cookie,
1258                                 tftp_handler_opts* opts) {
1259    if (!opts || !opts->inbuf || !opts->outbuf || !opts->outbuf_sz) {
1260        return TFTP_ERR_INVALID_ARGS;
1261    }
1262    tftp_msg_loop_opts msg_loop_opts = {.incoming = opts->inbuf,
1263                                        .in_buf_sz = opts->inbuf_sz,
1264                                        .outgoing = opts->outbuf,
1265                                        .out_buf_sz = *opts->outbuf_sz,
1266                                        .err_msg = opts->err_msg,
1267                                        .err_msg_sz = opts->err_msg_sz};
1268    uint32_t timeout_ms = session->timeout * 1000;
1269    tftp_status status = tftp_msg_loop(session, transport_cookie, file_cookie, &msg_loop_opts,
1270                                       timeout_ms);
1271    if ((session->state != NONE) && session->file_interface.close) {
1272        session->file_interface.close(file_cookie);
1273    }
1274    return status;
1275}
1276
1277tftp_status tftp_handle_msg(tftp_session* session,
1278                            void* transport_cookie,
1279                            void* file_cookie,
1280                            tftp_handler_opts* opts) {
1281    if (!opts || !opts->inbuf || !opts->outbuf || !opts->outbuf_sz) {
1282        return TFTP_ERR_INVALID_ARGS;
1283    }
1284    uint32_t timeout_ms;
1285    tftp_status ret;
1286    ret = tftp_process_msg(session, opts->inbuf, opts->inbuf_sz,
1287                           opts->outbuf, opts->outbuf_sz, &timeout_ms, file_cookie);
1288    if (*opts->outbuf_sz) {
1289        tftp_status send_status = session->transport_interface.send(opts->outbuf, *opts->outbuf_sz,
1290                                                                    transport_cookie);
1291        if (send_status != TFTP_NO_ERROR) {
1292            REPORT_ERR(opts, "failed during transport send callback");
1293            return send_status;
1294        }
1295    }
1296    if (ret == TFTP_ERR_SHOULD_WAIT) {
1297        REPORT_ERR(opts, "request received, host is busy");
1298    } else if (ret < 0) {
1299        REPORT_ERR(opts, "handling tftp request failed (file might not exist)");
1300    } else if (ret == TFTP_TRANSFER_COMPLETED) {
1301        if (session->file_interface.close) {
1302            session->file_interface.close(file_cookie);
1303        }
1304    } else {
1305        ret = session->transport_interface.timeout_set(timeout_ms, transport_cookie);
1306        if (ret < 0) {
1307            REPORT_ERR(opts, "failed during transport timeout set callback");
1308        }
1309    }
1310    return ret;
1311}
1312