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#include <tftp/tftp.h>
6
7#include <arpa/inet.h>
8#include <assert.h>
9#include <stdint.h>
10#include <stdio.h>
11#include <stdlib.h>
12#include <string.h>
13
14#include <fbl/algorithm.h>
15#include <fbl/unique_ptr.h>
16#include <unittest/unittest.h>
17
18// For inspecting session state
19#include "internal.h"
20
21constexpr char kLocalFilename[] = "local-filename";
22constexpr char kRemoteFilename[] = "remote-filename";
23
24struct test_state {
25    // Return value used by ulib/unittest
26    bool reset(size_t ssize, size_t msize, size_t osize) {
27        sess_size = ssize;
28        msg_size = msize;
29        out_size = osize;
30        sess_buf.reset(new uint8_t[sess_size]);
31        msg_data.reset(new uint8_t[msg_size]);
32        out_scratch.reset(new uint8_t[out_size]);
33        auto init_status = tftp_init(&session, sess_buf.get(), sess_size);
34        ASSERT_EQ(TFTP_NO_ERROR, init_status, "could not initialize tftp_session");
35        data = reinterpret_cast<void*>(msg_data.get());
36        out = reinterpret_cast<void*>(out_scratch.get());
37        outlen = out_size;
38        return true;
39    }
40
41    tftp_session* session = nullptr;
42    size_t sess_size = 0;
43    size_t msg_size = 0;
44    size_t out_size = 0;
45    fbl::unique_ptr<uint8_t[]> sess_buf;
46    fbl::unique_ptr<uint8_t[]> msg_data;
47    fbl::unique_ptr<uint8_t[]> out_scratch;
48    void* data = nullptr;
49    void* out = nullptr;
50    size_t outlen = 0;
51    uint32_t timeout = 0;
52};
53
54static bool test_tftp_init(void) {
55    BEGIN_TEST;
56
57    uint8_t buf[1024];
58    tftp_session* session;
59    int status = tftp_init(&session, nullptr, 4096);
60    EXPECT_LT(status, 0, "tftp_init should fail for NULL buffer");
61    status = tftp_init(&session, buf, 4);
62    EXPECT_LT(status, 0, "tftp_init should fail for too small buffer");
63    status = tftp_init(&session, buf, sizeof(buf));
64    EXPECT_EQ(status, TFTP_NO_ERROR, "error creating tftp session");
65    EXPECT_EQ(sizeof(tftp_session), tftp_sizeof_session(), "");
66    ASSERT_GE(sizeof(buf), tftp_sizeof_session(),
67            "need to update test for larger tftp_session size");
68    status = tftp_init(&session, buf, tftp_sizeof_session());
69    EXPECT_EQ(status, TFTP_NO_ERROR, "tftp_init failed on correctly sized buffer");
70
71    END_TEST;
72}
73
74static bool test_tftp_session_options(void) {
75    BEGIN_TEST;
76
77    test_state ts;
78    ts.reset(1024, 1024, 1500);
79
80    auto open_read_fn = [](const char* filename, void* cookie) -> ssize_t {
81        return 0;
82    };
83    auto open_write_fn = [](const char* filename, size_t size, void* cookie) -> tftp_status {
84        return 0;
85    };
86    auto read_fn = [](void* data, size_t* len, off_t offset, void* cookie) -> tftp_status {
87        return 0;
88    };
89    auto write_fn = [](const void* data, size_t* len, off_t offset, void* cookie) -> tftp_status {
90        return 0;
91    };
92    auto close_fn = [](void* cookie) {
93        return;
94    };
95    tftp_file_interface ifc = { open_read_fn, open_write_fn, read_fn, write_fn, close_fn };
96    auto status = tftp_session_set_file_interface(ts.session, &ifc);
97    EXPECT_EQ(TFTP_NO_ERROR, status, "could not set file callbacks");
98    EXPECT_EQ((tftp_file_open_read_cb)open_read_fn, ts.session->file_interface.open_read,
99              "bad open (read) function pointer");
100    EXPECT_EQ((tftp_file_open_write_cb)open_write_fn, ts.session->file_interface.open_write,
101              "bad open (write) function pointer");
102    EXPECT_EQ((tftp_file_read_cb)read_fn, ts.session->file_interface.read, "bad read function pointer");
103    EXPECT_EQ((tftp_file_write_cb)write_fn, ts.session->file_interface.write, "bad write function pointer");
104    EXPECT_EQ((tftp_file_close_cb)close_fn, ts.session->file_interface.close, "bad write function pointer");
105
106    END_TEST;
107}
108
109bool verify_write_request(const test_state& ts) {
110    auto msg = reinterpret_cast<tftp_msg*>(ts.out);
111    EXPECT_EQ(msg->opcode, htons(OPCODE_WRQ), "opcode should be 2 (WRQ)");
112    EXPECT_STR_EQ(kRemoteFilename, msg->data, "bad filename");
113    return true;
114}
115
116bool verify_read_request(const test_state& ts) {
117    auto msg = reinterpret_cast<tftp_msg*>(ts.out);
118    EXPECT_EQ(msg->opcode, htons(OPCODE_RRQ), "opcode should be 1 (RRQ)");
119    EXPECT_STR_EQ(kRemoteFilename, msg->data, "bad filename");
120    return true;
121}
122
123// Find a string (which may include NULL characters) inside a memory region.
124static bool find_str_in_mem(const char* str, size_t str_len, const char* mem, size_t mem_len) {
125    while (str_len <= mem_len) {
126        if (!memcmp(str, mem, str_len)) {
127            return true;
128        }
129        mem_len--;
130        mem++;
131    }
132    return false;
133}
134
135static bool test_tftp_generate_request(tftp_file_direction dir,
136                                       size_t file_size,
137                                       const uint16_t* block_size,
138                                       const uint8_t* timeout,
139                                       const uint16_t* window_size,
140                                       const uint16_t* block_size_override,
141                                       const uint8_t* timeout_override,
142                                       const uint16_t* window_size_override) {
143    BEGIN_TEST;
144
145    tftp_status status;
146    test_state ts;
147    ts.reset(1024, file_size, 1500);
148
149    // Test TFTP state, but not internal session state
150    status = tftp_set_options(ts.session, block_size, timeout, window_size);
151    ASSERT_EQ(TFTP_NO_ERROR, status, "error setting session options");
152    if (block_size) {
153        EXPECT_EQ(*block_size, ts.session->options.block_size, "bad session options: block size");
154    }
155    if (timeout) {
156        EXPECT_EQ(*timeout, ts.session->options.timeout, "bad session options: timeout");
157    }
158    if (window_size) {
159        EXPECT_EQ(*window_size, ts.session->options.window_size,
160                  "bad session options: window size");
161    }
162
163    size_t request_file_size = (dir == SEND_FILE) ? file_size : 0;
164    status = tftp_generate_request(ts.session, dir, kLocalFilename, kRemoteFilename,
165                                   MODE_OCTET, request_file_size, block_size_override,
166                                   timeout_override, window_size_override, ts.out, &ts.outlen,
167                                   &ts.timeout);
168    EXPECT_EQ(TFTP_NO_ERROR, status, "error generating request");
169    if (dir == SEND_FILE) {
170        EXPECT_TRUE(verify_write_request(ts), "bad write request");
171    } else {
172        EXPECT_TRUE(verify_read_request(ts), "bad read request");
173    }
174
175    EXPECT_EQ(REQ_SENT, ts.session->state, "bad session: state");
176    if (dir == SEND_FILE) {
177        EXPECT_EQ(file_size, ts.session->file_size, "bad session: file size");
178    }
179    EXPECT_EQ(DEFAULT_MODE, ts.session->mode, "bad session: mode");
180    EXPECT_EQ(0, ts.session->offset, "bad session: offset");
181    EXPECT_EQ(0, ts.session->block_number, "bad session: block number");
182
183    // Verify the options that were set in the request
184    const char* msg = static_cast<const char*>(ts.out);
185    char str[256];
186    size_t str_sz;
187
188    if (block_size_override) {
189        str_sz = snprintf(str, sizeof(str), "BLKSIZE!%c%d", '\0', *block_size_override) + 1;
190        EXPECT_TRUE(find_str_in_mem(str, str_sz, msg, ts.outlen), "block size not overridden");
191    } else if (block_size) {
192        str_sz = snprintf(str, sizeof(str), "BLKSIZE%c%d", '\0', *block_size) + 1;
193        EXPECT_TRUE(find_str_in_mem(str, str_sz, msg, ts.outlen),
194                    "block size not properly requested");
195    } else {
196        str_sz = snprintf(str, sizeof(str), "BLKSIZE") + 1;
197        EXPECT_FALSE(find_str_in_mem(str, str_sz, msg, ts.outlen),
198                     "block size shouldn't appear in request");
199    }
200
201    if (timeout_override) {
202        str_sz = snprintf(str, sizeof(str), "TIMEOUT!%c%d", '\0', *timeout_override) + 1;
203        EXPECT_TRUE(find_str_in_mem(str, str_sz, msg, ts.outlen), "timeout not overridden");
204    } else if (timeout) {
205        str_sz = snprintf(str, sizeof(str), "TIMEOUT%c%d", '\0', *timeout) + 1;
206        EXPECT_TRUE(find_str_in_mem(str, str_sz, msg, ts.outlen),
207                    "timeout not properly requested");
208    } else {
209        str_sz = snprintf(str, sizeof(str), "TIMEOUT") + 1;
210        EXPECT_FALSE(find_str_in_mem(str, str_sz, msg, ts.outlen),
211                     "timeout shouldn't appear in request");
212    }
213
214    if (window_size_override) {
215        str_sz = snprintf(str, sizeof(str), "WINDOWSIZE!%c%d", '\0', *window_size_override) + 1;
216        EXPECT_TRUE(find_str_in_mem(str, str_sz, msg, ts.outlen), "window size not overridden");
217    } else if (window_size) {
218        str_sz = snprintf(str, sizeof(str), "WINDOWSIZE%c%d", '\0', *window_size) + 1;
219        EXPECT_TRUE(find_str_in_mem(str, str_sz, msg, ts.outlen),
220                    "window size not properly requested");
221    } else {
222        str_sz = snprintf(str, sizeof(str), "WINDOWSIZE") + 1;
223        EXPECT_FALSE(find_str_in_mem(str, str_sz, msg, ts.outlen),
224                     "window size shouldn't appear in request");
225    }
226
227    END_TEST;
228}
229
230static bool test_tftp_generate_wrq_default(void) {
231    return test_tftp_generate_request(SEND_FILE, 1024, nullptr, nullptr, nullptr,
232                                                       nullptr, nullptr, nullptr);
233}
234
235static bool test_tftp_generate_wrq_options(void) {
236    constexpr uint16_t kBlockSize = 555;
237    constexpr uint8_t kTimeout = 3;
238    constexpr uint16_t kWindowSize = 44;
239    return test_tftp_generate_request(SEND_FILE, 1024, &kBlockSize, &kTimeout, &kWindowSize,
240                                                       nullptr, nullptr, nullptr);
241}
242
243static bool test_tftp_generate_wrq_override_blocksize(void) {
244    constexpr uint16_t kBlockSize = 1000;
245    return test_tftp_generate_request(SEND_FILE, 1024, nullptr, nullptr, nullptr,
246                                                       &kBlockSize, nullptr, nullptr);
247}
248
249static bool test_tftp_generate_wrq_override_timeout(void) {
250    uint8_t kTimeout = 60;
251    return test_tftp_generate_request(SEND_FILE, 1024, nullptr, nullptr, nullptr,
252                                                 nullptr, &kTimeout, nullptr);
253}
254
255static bool test_tftp_generate_wrq_override_windowsize(void) {
256    uint16_t kWindowSize = 32;
257    return test_tftp_generate_request(SEND_FILE, 1024, nullptr, nullptr, nullptr,
258                                                 nullptr, nullptr, &kWindowSize);
259}
260
261static bool test_tftp_generate_rrq_default(void) {
262    return test_tftp_generate_request(RECV_FILE, 1024, nullptr, nullptr, nullptr,
263                                                       nullptr, nullptr, nullptr);
264}
265
266static bool test_tftp_generate_rrq_options(void) {
267    constexpr uint16_t kBlockSize = 555;
268    constexpr uint8_t kTimeout = 3;
269    constexpr uint16_t kWindowSize = 44;
270    return test_tftp_generate_request(RECV_FILE, 1024, &kBlockSize, &kTimeout, &kWindowSize,
271                                                       nullptr, nullptr, nullptr);
272}
273
274static bool test_tftp_generate_rrq_override_blocksize(void) {
275    constexpr uint16_t kBlockSize = 1000;
276    return test_tftp_generate_request(RECV_FILE, 1024, nullptr, nullptr, nullptr,
277                                                       &kBlockSize, nullptr, nullptr);
278}
279
280static bool test_tftp_generate_rrq_override_timeout(void) {
281    uint8_t kTimeout = 60;
282    return test_tftp_generate_request(RECV_FILE, 1024, nullptr, nullptr, nullptr,
283                                                 nullptr, &kTimeout, nullptr);
284}
285
286static bool test_tftp_generate_rrq_override_windowsize(void) {
287    uint16_t kWindowSize = 32;
288    return test_tftp_generate_request(RECV_FILE, 1024, nullptr, nullptr, nullptr,
289                                                 nullptr, nullptr, &kWindowSize);
290}
291
292bool verify_response_opcode(const test_state& ts, uint16_t opcode) {
293    ASSERT_GT(ts.outlen, 0, "outlen must not be zero");
294    auto msg = reinterpret_cast<tftp_msg*>(ts.out);
295    // The upper byte of the opcode is ignored
296    EXPECT_EQ(ntohs(msg->opcode) & 0xff, opcode, "bad opcode");
297    return true;
298}
299
300static bool test_tftp_receive_request_unexpected(tftp_file_direction dir) {
301    BEGIN_TEST;
302
303    test_state ts;
304    ts.reset(1024, 1024, 1500);
305
306    size_t req_file_size = (dir == SEND_FILE) ? ts.msg_size : 0;
307    auto status = tftp_generate_request(ts.session, dir, kLocalFilename, kRemoteFilename,
308        MODE_OCTET, req_file_size, NULL, NULL, NULL, ts.out, &ts.outlen, &ts.timeout);
309    ASSERT_EQ(TFTP_NO_ERROR, status, "could not generate request");
310    if (dir == SEND_FILE) {
311        ASSERT_TRUE(verify_write_request(ts), "bad write request");
312    } else {
313        ASSERT_TRUE(verify_read_request(ts), "bad read request");
314    }
315
316    ASSERT_LE(ts.outlen, 1500, "outlen too large");
317    uint8_t buf[1500];
318    memcpy(buf, ts.out, ts.outlen);
319
320    // We are unprepared to service a request after we have sent one out
321    status = tftp_process_msg(ts.session, buf, sizeof(buf), ts.out, &ts.outlen, &ts.timeout,
322                              nullptr);
323    EXPECT_EQ(TFTP_ERR_BAD_STATE, status, "receive should fail");
324    EXPECT_TRUE(verify_response_opcode(ts, OPCODE_ERROR), "bad error response");
325
326    END_TEST;
327}
328
329static bool test_tftp_receive_wrq_unexpected(void) {
330    return test_tftp_receive_request_unexpected(SEND_FILE);
331}
332
333static bool test_tftp_receive_rrq_unexpected(void) {
334    return test_tftp_receive_request_unexpected(RECV_FILE);
335}
336
337static bool test_tftp_receive_request_too_large(tftp_file_direction dir) {
338    BEGIN_TEST;
339
340    test_state ts;
341    ts.reset(1024, 1024, 1500);
342
343    uint8_t buf[1024] = { 0 };
344    buf[1] = (dir == SEND_FILE) ? OPCODE_WRQ : OPCODE_RRQ;
345
346    auto status = tftp_process_msg(ts.session, buf, sizeof(buf), ts.out, &ts.outlen, &ts.timeout,
347                                   nullptr);
348    EXPECT_LT(status, 0, "receive should fail");
349    EXPECT_TRUE(verify_response_opcode(ts, OPCODE_ERROR), "bad error response");
350
351    END_TEST;
352}
353
354static bool test_tftp_receive_wrq_too_large(void) {
355    return test_tftp_receive_request_too_large(SEND_FILE);
356}
357
358static bool test_tftp_receive_rrq_too_large(void) {
359    return test_tftp_receive_request_too_large(RECV_FILE);
360}
361
362static bool test_tftp_receive_request_no_tsize(tftp_file_direction dir) {
363    BEGIN_TEST;
364
365    test_state ts;
366    ts.reset(1024, 1024, 1500);
367
368    char buf[256];
369    buf[0] = 0x00;
370    buf[1] = (dir == SEND_FILE) ? OPCODE_WRQ : OPCODE_RRQ;
371    size_t buf_sz = 2 + snprintf(&buf[2], sizeof(buf) - 2,
372                                 "%s%cOCTET",
373                                 kRemoteFilename, '\0')
374                      + 1;
375
376    ASSERT_LT(buf_sz, (int)sizeof(buf), "insufficient space for message");
377    auto status = tftp_process_msg(ts.session, buf, buf_sz, ts.out, &ts.outlen, &ts.timeout,
378                                   nullptr);
379    EXPECT_EQ(TFTP_ERR_BAD_STATE, status, "tftp session should fail");
380    EXPECT_EQ(ERROR, ts.session->state, "tftp session in wrong state");
381    EXPECT_EQ(0, ts.session->file_size, "tftp session bad file size");
382    EXPECT_TRUE(verify_response_opcode(ts, OPCODE_ERROR), "bad error response");
383
384    END_TEST;
385}
386
387static bool test_tftp_receive_wrq_no_tsize(void) {
388    return test_tftp_receive_request_no_tsize(SEND_FILE);
389}
390
391static bool test_tftp_receive_rrq_no_tsize(void) {
392    return test_tftp_receive_request_no_tsize(RECV_FILE);
393}
394
395static bool test_tftp_receive_request_send_oack(tftp_file_direction dir) {
396    BEGIN_TEST;
397
398    test_state ts;
399    ts.reset(1024, 1024, 1500);
400    tftp_file_open_write_cb open_write_cb =
401            [](const char* filename, size_t size, void* cookie) -> tftp_status {
402                EXPECT_STR_EQ(filename, kRemoteFilename,
403                              "bad remote filename in open_write callback");
404                EXPECT_EQ(size, 1024, "bad file size");
405                return 0;
406            };
407    tftp_file_open_read_cb open_read_cb =
408            [](const char* filename, void* cookie) -> ssize_t {
409                EXPECT_STR_EQ(filename, kRemoteFilename,
410                              "bad remote filename in open_read callback");
411                return 0;
412            };
413    tftp_file_interface ifc = {open_read_cb, open_write_cb, NULL, NULL, NULL};
414    tftp_session_set_file_interface(ts.session, &ifc);
415    uint16_t default_block_size = 13;
416    uint8_t default_timeout = 2;
417    uint16_t default_window_size = 42;
418    tftp_set_options(ts.session, &default_block_size, &default_timeout, &default_window_size);
419
420    size_t req_file_size = (dir == SEND_FILE) ? 1024 : 0;
421    char buf[256];
422    buf[0] = 0x00;
423    buf[1] = (dir == SEND_FILE) ? OPCODE_WRQ : OPCODE_RRQ;
424    size_t buf_sz = 2 + snprintf(&buf[2], sizeof(buf) - 2,
425                                 "%s%cOCTET%cTSIZE%c%zu",
426                                 kRemoteFilename, '\0', '\0', '\0', req_file_size)
427                      + 1;
428
429    ASSERT_LT(buf_sz, (int)sizeof(buf), "insufficient space for WRQ message");
430    auto status = tftp_process_msg(ts.session, buf, buf_sz, ts.out, &ts.outlen, &ts.timeout,
431                                   nullptr);
432    EXPECT_EQ(TFTP_NO_ERROR, status, "receive request failed");
433    EXPECT_EQ(REQ_RECEIVED, ts.session->state, "tftp session in wrong state");
434    EXPECT_EQ(req_file_size, ts.session->file_size, "tftp session bad file size");
435    EXPECT_EQ(DEFAULT_BLOCKSIZE, ts.session->block_size, "bad session: block size");
436    EXPECT_EQ(DEFAULT_TIMEOUT, ts.session->timeout, "bad session: timeout");
437    EXPECT_EQ(DEFAULT_WINDOWSIZE, ts.session->window_size, "bad session: window size");
438    EXPECT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response");
439
440    // The request was made from the client's perspective, so our state is the inverse
441    tftp_file_direction our_direction = (dir == SEND_FILE) ? RECV_FILE : SEND_FILE;
442    EXPECT_EQ(our_direction, ts.session->direction, "tftp session has bad direction");
443
444    const char* msg = static_cast<const char*>(ts.out);
445    const char win_sz_str[] = "WINDOWSIZE";
446    EXPECT_FALSE(find_str_in_mem(win_sz_str, sizeof(win_sz_str), msg, ts.outlen),
447                 "window size in oack, but not in request");
448    const char timeout_str[] = "TIMEOUT";
449    EXPECT_FALSE(find_str_in_mem(timeout_str, sizeof(timeout_str), msg, ts.outlen),
450                 "timeout in oack, but not in request");
451    const char block_sz_str[] = "BLKSIZE";
452    EXPECT_FALSE(find_str_in_mem(block_sz_str, sizeof(block_sz_str), msg, ts.outlen),
453                 "block size in oack, but not in request");
454
455    END_TEST;
456}
457
458static bool test_tftp_receive_wrq_send_oack(void) {
459    return test_tftp_receive_request_send_oack(SEND_FILE);
460}
461
462static bool test_tftp_receive_rrq_send_oack(void) {
463    return test_tftp_receive_request_send_oack(RECV_FILE);
464}
465
466static ssize_t dummy_open_read(const char* filename, void* cookie) {
467    return 1024;
468}
469
470static tftp_status dummy_open_write(const char* filename, size_t size, void* cookie) {
471    return 0;
472}
473
474static bool test_tftp_receive_request_options(tftp_file_direction dir,
475                                              const uint16_t* server_block_size,
476                                              const uint8_t* server_timeout,
477                                              const uint16_t* server_window_size,
478                                              const uint16_t* client_block_size,
479                                              const uint8_t* client_timeout,
480                                              const uint16_t* client_window_size,
481                                              bool force_block_size,
482                                              bool force_timeout,
483                                              bool force_window_size) {
484    BEGIN_TEST;
485
486    test_state ts;
487    ts.reset(1024, 1024, 1500);
488    tftp_file_interface ifc = {dummy_open_read, dummy_open_write, NULL, NULL, NULL};
489    tftp_session_set_file_interface(ts.session, &ifc);
490    tftp_status status = tftp_set_options(ts.session, server_block_size, server_timeout,
491                                          server_window_size);
492    ASSERT_EQ(TFTP_NO_ERROR, status, "failed to set server options");
493
494    size_t req_file_size = (dir == SEND_FILE) ? 1024 : 0;
495    char buf[256];
496    buf[0] = 0x00;
497    buf[1] = (dir == SEND_FILE) ? OPCODE_WRQ : OPCODE_RRQ;
498    size_t buf_sz = 2;
499    buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz,
500                       "%s%cOCTET%cTSIZE%c%zu",
501                       kRemoteFilename, '\0', '\0', '\0', req_file_size) + 1;
502    if (client_block_size) {
503        buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz,
504                           "BLKSIZE%s%c%d",
505                           force_block_size ? "!" : "", '\0', *client_block_size) + 1;
506    }
507    if (client_timeout) {
508        buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz,
509                           "TIMEOUT%s%c%d",
510                           force_timeout ? "!" : "", '\0', *client_timeout) + 1;
511    }
512    if (client_window_size) {
513        buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz,
514                           "WINDOWSIZE%s%c%d",
515                           force_window_size ? "!" : "", '\0', *client_window_size) + 1;
516    }
517
518    ASSERT_LT(buf_sz, (int)sizeof(buf), "insufficient space for request");
519    status = tftp_process_msg(ts.session, buf, buf_sz, ts.out, &ts.outlen, &ts.timeout, nullptr);
520    EXPECT_EQ(TFTP_NO_ERROR, status, "receive write request failed");
521    EXPECT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response");
522
523    const char* msg = static_cast<const char*>(ts.out);
524    char opt_str[256];
525    size_t opt_str_sz;
526
527    if (client_block_size) {
528        uint16_t negotiated_block_size = (server_block_size && !force_block_size)
529                                         ? *server_block_size
530                                         : *client_block_size;
531        opt_str_sz = snprintf(opt_str, sizeof(opt_str),
532                              "BLKSIZE%c%d",
533                              '\0', negotiated_block_size) + 1;
534        EXPECT_TRUE(find_str_in_mem(opt_str, opt_str_sz, msg, ts.outlen),
535                    "block size not correct in oack");
536        EXPECT_EQ(negotiated_block_size, ts.session->block_size, "bad session: block size");
537    } else {
538        opt_str_sz = snprintf(opt_str, sizeof(opt_str), "BLKSIZE") + 1;
539        EXPECT_FALSE(find_str_in_mem(opt_str, opt_str_sz, msg, ts.outlen),
540                     "block size appears in oack, but not in request");
541        EXPECT_EQ(DEFAULT_BLOCKSIZE, ts.session->block_size, "bad session: block size");
542    }
543
544    if (client_timeout) {
545        uint8_t negotiated_timeout = (server_timeout && !force_timeout)
546                                     ? *server_timeout
547                                     : *client_timeout;
548        opt_str_sz = snprintf(opt_str, sizeof(opt_str),
549                              "TIMEOUT%c%d",
550                              '\0', negotiated_timeout) + 1;
551        EXPECT_TRUE(find_str_in_mem(opt_str, opt_str_sz, msg, ts.outlen),
552                    "timeout not correct in oack");
553        EXPECT_EQ(negotiated_timeout, ts.session->timeout, "bad session: timeout");
554    } else {
555        opt_str_sz = snprintf(opt_str, sizeof(opt_str), "TIMEOUT") + 1;
556        EXPECT_FALSE(find_str_in_mem(opt_str, opt_str_sz, msg, ts.outlen),
557                     "timeout appears in oack, but not in request");
558        EXPECT_EQ(DEFAULT_TIMEOUT, ts.session->timeout, "bad session: timeout");
559    }
560
561    if (client_window_size) {
562        uint16_t negotiated_window_size = (server_window_size && !force_window_size)
563                                          ? *server_window_size
564                                          : *client_window_size;
565        opt_str_sz = snprintf(opt_str, sizeof(opt_str),
566                              "WINDOWSIZE%c%d",
567                              '\0', negotiated_window_size) + 1;
568        EXPECT_TRUE(find_str_in_mem(opt_str, opt_str_sz, msg, ts.outlen),
569                    "window size not correct in oack");
570        EXPECT_EQ(negotiated_window_size, ts.session->window_size, "bad session: window size");
571    } else {
572        opt_str_sz = snprintf(opt_str, sizeof(opt_str), "WINDOWSIZE") + 1;
573        EXPECT_FALSE(find_str_in_mem(opt_str, opt_str_sz, msg, ts.outlen),
574                     "window size appears in oack, but not in request");
575        EXPECT_EQ(DEFAULT_WINDOWSIZE, ts.session->window_size, "bad session: window size");
576    }
577
578    END_TEST;
579}
580
581static bool test_tftp_receive_wrq_blocksize(void) {
582    constexpr uint8_t kDefaultTimeout = 4;
583    constexpr uint16_t kBlocksize = 1024;
584    return test_tftp_receive_request_options(SEND_FILE, nullptr, &kDefaultTimeout, nullptr,
585                                                        &kBlocksize, nullptr, nullptr,
586                                                        false, false, false);
587}
588
589static bool test_tftp_receive_wrq_timeout(void) {
590    constexpr uint16_t kDefaultBlocksize = 115;
591    constexpr uint8_t kTimeout = 3;
592    return test_tftp_receive_request_options(SEND_FILE, &kDefaultBlocksize, nullptr, nullptr,
593                                                        nullptr, &kTimeout, nullptr,
594                                                        false, false, false);
595}
596
597static bool test_tftp_receive_wrq_windowsize(void) {
598    constexpr uint16_t kWindowsize = 77;
599    return test_tftp_receive_request_options(SEND_FILE, nullptr, nullptr, nullptr,
600                                                        nullptr, nullptr, &kWindowsize,
601                                                        false, false, false);
602}
603
604// Verify that if override values are set, they supercede the values in a normal request
605static bool test_tftp_receive_wrq_have_overrides(void) {
606    constexpr uint16_t kServerBlockSize = 302;
607    constexpr uint8_t kServerTimeout = 7;
608    constexpr uint16_t kServerWindowSize = 16;
609    constexpr uint16_t kClientBlockSize = 32;
610    constexpr uint8_t kClientTimeout = 9;
611    constexpr uint16_t kClientWindowSize = 143;
612    return test_tftp_receive_request_options(
613                   SEND_FILE, &kServerBlockSize, &kServerTimeout, &kServerWindowSize,
614                              &kClientBlockSize, &kClientTimeout, &kClientWindowSize,
615                              false, false, false);
616}
617
618// Verify that if a WRQ has a '!' following an option it is honored, even if overrides are set
619static bool test_tftp_receive_force_wrq_no_overrides(void) {
620    constexpr uint16_t kServerBlockSize = 302;
621    constexpr uint8_t kServerTimeout = 7;
622    constexpr uint16_t kServerWindowSize = 16;
623    constexpr uint16_t kClientBlockSize = 32;
624    constexpr uint8_t kClientTimeout = 9;
625    constexpr uint16_t kClientWindowSize = 143;
626    return test_tftp_receive_request_options(
627                   SEND_FILE, &kServerBlockSize, &kServerTimeout, &kServerWindowSize,
628                              &kClientBlockSize, &kClientTimeout, &kClientWindowSize,
629                              true, true, true);
630}
631
632static bool test_tftp_receive_force_wrq_have_overrides(void) {
633    constexpr uint16_t kClientBlockSize = 32;
634    constexpr uint8_t kClientTimeout = 9;
635    constexpr uint16_t kClientWindowSize = 143;
636    return test_tftp_receive_request_options(
637                   SEND_FILE, nullptr, nullptr, nullptr,
638                              &kClientBlockSize, &kClientTimeout, &kClientWindowSize,
639                              true, true, true);
640}
641
642static bool test_tftp_receive_rrq_blocksize(void) {
643    constexpr uint8_t kDefaultTimeout = 4;
644    constexpr uint16_t kBlocksize = 1024;
645    return test_tftp_receive_request_options(RECV_FILE, nullptr, &kDefaultTimeout, nullptr,
646                                                        &kBlocksize, nullptr, nullptr,
647                                                        false, false, false);
648}
649
650static bool test_tftp_receive_rrq_timeout(void) {
651    constexpr uint16_t kDefaultBlocksize = 115;
652    constexpr uint8_t kTimeout = 3;
653    return test_tftp_receive_request_options(RECV_FILE, &kDefaultBlocksize, nullptr, nullptr,
654                                                        nullptr, &kTimeout, nullptr,
655                                                        false, false, false);
656}
657
658static bool test_tftp_receive_rrq_windowsize(void) {
659    constexpr uint16_t kWindowsize = 77;
660    return test_tftp_receive_request_options(RECV_FILE, nullptr, nullptr, nullptr,
661                                                        nullptr, nullptr, &kWindowsize,
662                                                        false, false, false);
663}
664
665// Verify that if override values are set, they supercede the values in a normal request
666static bool test_tftp_receive_rrq_have_overrides(void) {
667    constexpr uint16_t kServerBlockSize = 302;
668    constexpr uint8_t kServerTimeout = 7;
669    constexpr uint16_t kServerWindowSize = 16;
670    constexpr uint16_t kClientBlockSize = 32;
671    constexpr uint8_t kClientTimeout = 9;
672    constexpr uint16_t kClientWindowSize = 143;
673    return test_tftp_receive_request_options(
674                   RECV_FILE, &kServerBlockSize, &kServerTimeout, &kServerWindowSize,
675                              &kClientBlockSize, &kClientTimeout, &kClientWindowSize,
676                              false, false, false);
677}
678
679// Verify that if a WRQ has a '!' following an option it is honored, even if overrides are set
680static bool test_tftp_receive_force_rrq_no_overrides(void) {
681    constexpr uint16_t kServerBlockSize = 302;
682    constexpr uint8_t kServerTimeout = 7;
683    constexpr uint16_t kServerWindowSize = 16;
684    constexpr uint16_t kClientBlockSize = 32;
685    constexpr uint8_t kClientTimeout = 9;
686    constexpr uint16_t kClientWindowSize = 143;
687    return test_tftp_receive_request_options(
688                   RECV_FILE, &kServerBlockSize, &kServerTimeout, &kServerWindowSize,
689                              &kClientBlockSize, &kClientTimeout, &kClientWindowSize,
690                              true, true, true);
691}
692
693static bool test_tftp_receive_force_rrq_have_overrides(void) {
694    constexpr uint16_t kClientBlockSize = 32;
695    constexpr uint8_t kClientTimeout = 9;
696    constexpr uint16_t kClientWindowSize = 143;
697    return test_tftp_receive_request_options(
698                   RECV_FILE, nullptr, nullptr, nullptr,
699                              &kClientBlockSize, &kClientTimeout, &kClientWindowSize,
700                              true, true, true);
701}
702
703struct tx_test_data {
704    struct {
705        uint16_t block;
706        off_t offset;
707        size_t len;
708        uint8_t data[2048];  // for reads
709    } expected;
710
711    struct {
712        // block is in the outgoing message buffer
713        off_t offset;
714        size_t len;
715        uint8_t data[2048];  // for writes
716    } actual;
717
718    tx_test_data() {
719        expected.block = 1;
720        expected.offset = 0;
721        expected.len = DEFAULT_BLOCKSIZE;
722        expected.data[0] = 'F';
723        expected.data[DEFAULT_BLOCKSIZE - 1] = 'X';
724        actual.offset = -1;
725        actual.len = -1;
726        memset(actual.data, 0, sizeof(actual.data));
727    }
728};
729
730bool verify_read_data(const test_state& ts, const tx_test_data& td) {
731    BEGIN_HELPER;
732    EXPECT_EQ(td.expected.offset, td.actual.offset, "read offset mismatch");
733    EXPECT_EQ(td.expected.len, td.actual.len, "read length mismatch");
734    auto msg = static_cast<tftp_data_msg*>(ts.out);
735    // The upper byte of the opcode is ignored
736    EXPECT_EQ(OPCODE_DATA, ntohs(msg->opcode) & 0xff, "bad opcode");
737    // Don't continue if we have a bad block number or we risk OOB reads
738    ASSERT_EQ(td.expected.block, ntohs(msg->block), "bad block number");
739    EXPECT_BYTES_EQ(td.expected.data, msg->data, td.actual.len, "read data mismatch");
740    END_HELPER;
741}
742
743tftp_status mock_open_write(const char* filename, size_t size, void* file_cookie) {
744    return TFTP_NO_ERROR;
745}
746
747tftp_status mock_read(void* data, size_t* len, off_t offset, void* cookie) {
748    tx_test_data* td = static_cast<tx_test_data*>(cookie);
749    td->actual.len = *len;
750    td->actual.offset = offset;
751    memcpy(data, td->expected.data, *len);
752    return static_cast<tftp_status>(*len);
753}
754
755static bool test_tftp_receive_wrq_oack(const size_t file_size,
756                                       const uint16_t* block_size_ptr,
757                                       const uint8_t* timeout_ptr,
758                                       const uint16_t* window_size_ptr) {
759    BEGIN_TEST;
760
761    uint16_t block_size = block_size_ptr ? *block_size_ptr : DEFAULT_BLOCKSIZE;
762    uint8_t timeout = timeout_ptr ? *timeout_ptr : DEFAULT_TIMEOUT;
763    uint16_t window_size = window_size_ptr ? *window_size_ptr : DEFAULT_WINDOWSIZE;
764
765    test_state ts;
766    ts.reset(1024, file_size, 1500);
767
768    auto status = tftp_generate_request(ts.session, SEND_FILE, kLocalFilename, kRemoteFilename,
769                                        MODE_OCTET, ts.msg_size, block_size_ptr, timeout_ptr,
770                                        window_size_ptr, ts.out, &ts.outlen, &ts.timeout);
771    ASSERT_EQ(TFTP_NO_ERROR, status, "error generating write request");
772    ASSERT_TRUE(verify_write_request(ts), "bad write request");
773
774    char buf[256];
775    buf[0] = 0x00;
776    buf[1] = 0x06; // Opcode (OACK)
777    size_t buf_sz = 2;
778    buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz,
779                       "%s%cOCTET%cTSIZE%c%zu",
780                       kRemoteFilename, '\0', '\0', '\0', file_size) + 1;
781    if (block_size_ptr) {
782        buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz,
783                           "BLKSIZE%c%d",
784                           '\0', block_size) + 1;
785    }
786    if (timeout_ptr) {
787        buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz,
788                           "TIMEOUT%c%d",
789                           '\0', timeout) + 1;
790    }
791    if (window_size_ptr) {
792        buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz,
793                           "WINDOWSIZE%c%d",
794                           '\0', window_size) + 1;
795    }
796
797    tftp_file_interface ifc = {NULL, NULL, mock_read, NULL, NULL};
798    tftp_session_set_file_interface(ts.session, &ifc);
799
800    tx_test_data td;
801    td.expected.len = block_size;
802    td.expected.data[block_size - 1] = 'X';
803    status = tftp_process_msg(ts.session, buf, buf_sz, ts.out, &ts.outlen, &ts.timeout, &td);
804    EXPECT_EQ(TFTP_NO_ERROR, status, "tftp_process_msg failed");
805    bool has_pending = (block_size < file_size) && (window_size > 1);
806    EXPECT_EQ(has_pending, tftp_session_has_pending(ts.session),
807              "Unexpected tftp_session_has_pending()");
808    EXPECT_EQ(FIRST_DATA, ts.session->state, "session should be in state FIRST_DATA");
809    EXPECT_EQ(file_size, ts.session->file_size, "tftp session bad file size");
810    EXPECT_EQ(block_size, ts.session->block_size, "bad session: block size");
811    EXPECT_EQ(timeout, ts.session->timeout, "bad session: timeout");
812    EXPECT_EQ(window_size, ts.session->window_size, "bad session: window size");
813
814    EXPECT_EQ(ts.outlen, sizeof(tftp_data_msg) + block_size, "bad outlen");
815    EXPECT_EQ(ts.timeout, timeout * 1000U, "timeout should be set");
816    EXPECT_TRUE(verify_read_data(ts, td), "bad test data");
817
818    if (has_pending) {
819        // Since pending is true, call for a second data packet to transmit
820        // Updated the read offset and change a few bytes
821        td.expected.block = 2;
822        size_t second_block_size = fbl::min<size_t>(file_size - block_size, block_size);
823        td.expected.offset = block_size;
824        td.expected.data[0] = 'X';
825        if (second_block_size > 0) {
826            td.expected.data[second_block_size - 1] = 'F';
827        }
828
829        status = tftp_prepare_data(ts.session, ts.out, &ts.outlen, &ts.timeout, &td);
830        EXPECT_EQ(ts.outlen, sizeof(tftp_data_msg) + block_size, "bad outlen");
831        EXPECT_TRUE(verify_read_data(ts, td), "bad test data");
832        has_pending = (block_size + second_block_size < file_size) && (window_size > 2);
833        EXPECT_EQ(has_pending, tftp_session_has_pending(ts.session),
834                  "Unexpected tftp_session_has_pending()");
835    }
836    END_TEST;
837}
838
839static bool test_tftp_receive_wrq_oack(void) {
840    return test_tftp_receive_wrq_oack(1024, NULL, NULL, NULL);
841}
842
843static bool test_tftp_receive_wrq_oack_blocksize(void) {
844    constexpr uint16_t kBlockSize = 1024;
845    return test_tftp_receive_wrq_oack(2048, &kBlockSize, nullptr, nullptr);
846}
847
848static bool test_tftp_receive_wrq_oack_timeout(void) {
849    constexpr uint8_t kTimeout = 5;
850    return test_tftp_receive_wrq_oack(1024, nullptr, &kTimeout, nullptr);
851}
852
853static bool test_tftp_receive_wrq_oack_windowsize(void) {
854    constexpr uint16_t kWindowSize = 2;
855    return test_tftp_receive_wrq_oack(4096, nullptr, nullptr, &kWindowSize);
856}
857
858static bool test_tftp_receive_rrq_oack(size_t file_size,
859                                       const uint16_t* block_size_ptr,
860                                       const uint8_t* timeout_ptr,
861                                       const uint16_t* window_size_ptr) {
862    BEGIN_TEST;
863
864    uint16_t block_size = block_size_ptr ? *block_size_ptr : DEFAULT_BLOCKSIZE;
865    uint8_t timeout = timeout_ptr ? *timeout_ptr : DEFAULT_TIMEOUT;
866    uint16_t window_size = window_size_ptr ? *window_size_ptr : DEFAULT_WINDOWSIZE;
867
868    test_state ts;
869    ts.reset(1024, file_size, 1500);
870
871    auto status = tftp_generate_request(ts.session, RECV_FILE, kLocalFilename, kRemoteFilename,
872                                        MODE_OCTET, 0, block_size_ptr, timeout_ptr,
873                                        window_size_ptr, ts.out, &ts.outlen, &ts.timeout);
874    ASSERT_EQ(TFTP_NO_ERROR, status, "error generating read request");
875    ASSERT_TRUE(verify_read_request(ts), "bad read request");
876
877    char buf[256];
878    buf[0] = 0x00;
879    buf[1] = 0x06; // Opcode (OACK)
880    size_t buf_sz = 2;
881    buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz,
882                       "TSIZE%c%zu",
883                       '\0', file_size) + 1;
884    if (block_size_ptr) {
885        buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz,
886                           "BLKSIZE%c%d",
887                           '\0', block_size) + 1;
888    }
889    if (timeout_ptr) {
890        buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz,
891                           "TIMEOUT%c%d",
892                           '\0', timeout) + 1;
893    }
894    if (window_size_ptr) {
895        buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz,
896                           "WINDOWSIZE%c%d",
897                           '\0', window_size) + 1;
898    }
899    tftp_file_interface ifc = {NULL, mock_open_write, NULL, NULL, NULL};
900    tftp_session_set_file_interface(ts.session, &ifc);
901
902    status = tftp_process_msg(ts.session, buf, buf_sz, ts.out, &ts.outlen, &ts.timeout, NULL);
903    EXPECT_FALSE(tftp_session_has_pending(ts.session), "session should not have pending data");
904    EXPECT_EQ(FIRST_DATA, ts.session->state, "session should be in state FIRST_DATA");
905    EXPECT_EQ(file_size, ts.session->file_size, "tftp session bad file size");
906    EXPECT_EQ(block_size, ts.session->block_size, "bad session: block size");
907    EXPECT_EQ(timeout, ts.session->timeout, "bad session: timeout");
908    EXPECT_EQ(window_size, ts.session->window_size, "bad session: window size");
909
910    uint8_t expected_ack[] = {
911        0x00, 0x04,  // Opcode (ACK)
912        0x00, 0x00   // Block
913    };
914    EXPECT_EQ(sizeof(expected_ack), ts.outlen, "response size mismatch");
915    EXPECT_EQ(0, memcmp(expected_ack, ts.out, sizeof(expected_ack)), "bad response");
916    END_TEST;
917}
918
919static bool test_tftp_receive_rrq_oack(void) {
920    return test_tftp_receive_rrq_oack(1024, nullptr, nullptr, nullptr);
921}
922
923static bool test_tftp_receive_rrq_oack_blocksize(void) {
924    constexpr uint16_t kBlockSize = 1024;
925    return test_tftp_receive_rrq_oack(2048, &kBlockSize, nullptr, nullptr);
926}
927
928static bool test_tftp_receive_rrq_oack_timeout(void) {
929    constexpr uint8_t kTimeout = 5;
930    return test_tftp_receive_rrq_oack(1024, nullptr, &kTimeout, nullptr);
931}
932
933static bool test_tftp_receive_rrq_oack_windowsize(void) {
934    constexpr uint16_t kWindowSize = 412;
935    return test_tftp_receive_rrq_oack(1024, nullptr, nullptr, &kWindowSize);
936}
937
938// Verify that if the server overrides our settings we use the oack'd settings it provides
939static bool test_tftp_receive_oack_overrides(void) {
940    BEGIN_TEST;
941
942    test_state ts;
943    ts.reset(1024, 4096, 1500);
944
945    uint16_t kBlockSize = 14;
946    uint8_t kTimeout = 12;
947    uint16_t kWindowSize = 6;
948
949    auto status = tftp_generate_request(ts.session, SEND_FILE, kLocalFilename, kRemoteFilename,
950        MODE_OCTET, ts.msg_size, &kBlockSize, &kTimeout, &kWindowSize, ts.out, &ts.outlen,
951        &ts.timeout);
952    ASSERT_EQ(TFTP_NO_ERROR, status, "error generating write request");
953
954    uint8_t buf[] = {
955        0x00, 0x06,                                   // Opcode (OACK)
956        'T', 'S', 'I', 'Z', 'E', 0x00,                // Option
957        '4', '0', '9', '6', 0x00,                     // TSIZE value
958        'B', 'L', 'K', 'S', 'I', 'Z', 'E', 0x00,      // Option
959        '5', '5', 0x00,                               // BLKSIZE value
960        'T', 'I', 'M', 'E', 'O', 'U', 'T', 0x00,      // Option
961        '3', 0x00,                                    // TIMEOUT value
962        'W', 'I', 'N', 'D', 'O', 'W', 'S', 'I', 'Z', 'E', 0x00,      // Option
963        '2', '1', '4', 0x00,                          // WINDOWSIZE value
964    };
965
966    tftp_file_interface ifc = {NULL, NULL, mock_read, NULL, NULL};
967    tftp_session_set_file_interface(ts.session, &ifc);
968
969    tx_test_data td;
970    status = tftp_process_msg(ts.session, buf, sizeof(buf), ts.out, &ts.outlen, &ts.timeout, &td);
971    EXPECT_EQ(FIRST_DATA, ts.session->state, "session should be in state FIRST_DATA");
972    EXPECT_EQ(4096, ts.session->file_size, "tftp session bad file size");
973    EXPECT_EQ(55, ts.session->block_size, "bad session: block size");
974    EXPECT_EQ(3, ts.session->timeout, "bad session: timeout");
975    EXPECT_EQ(214, ts.session->window_size, "bad session: window size");
976    EXPECT_EQ(ts.outlen, sizeof(tftp_data_msg) + ts.session->block_size, "bad outlen");
977
978    END_TEST;
979}
980
981tftp_status mock_write(const void* data, size_t* len, off_t offset, void* cookie) {
982    tx_test_data* td = static_cast<tx_test_data*>(cookie);
983    td->actual.len = *len;
984    td->actual.offset = offset;
985    memcpy(td->actual.data + offset, data, *len);
986    return static_cast<tftp_status>(*len);
987}
988
989bool verify_write_data(const uint8_t* expected, const tx_test_data& td) {
990    BEGIN_HELPER;
991    ASSERT_EQ(td.expected.offset, td.actual.offset, "write offset mismatch");
992    ASSERT_EQ(td.expected.len, td.actual.len, "write length mismatch");
993    EXPECT_BYTES_EQ(expected, td.actual.data + td.actual.offset, td.actual.len, "write data mismatch");
994    END_HELPER;
995}
996
997static bool test_tftp_receive_data(void) {
998    BEGIN_TEST;
999
1000    test_state ts;
1001    ts.reset(1024, 1024, 1500);
1002    tftp_file_interface ifc = {NULL,
1003            [](const char* filename, size_t size, void* cookie) -> tftp_status {
1004                EXPECT_STR_EQ(filename, kRemoteFilename, "bad filename");
1005                EXPECT_EQ(size, 1024, "bad file size");
1006                return 0;
1007            }, NULL, NULL, NULL};
1008    tftp_session_set_file_interface(ts.session, &ifc);
1009
1010    char req_buf[256];
1011    req_buf[0] = 0x00;
1012    req_buf[1] = OPCODE_WRQ;
1013    size_t req_buf_sz = 2 + snprintf(&req_buf[2], sizeof(req_buf) - 2,
1014                                     "%s%cOCTET%cTSIZE%c%d",
1015                                     kRemoteFilename, '\0', '\0', '\0', 1024)
1016                          + 1;
1017
1018    ASSERT_LT(req_buf_sz, (int)sizeof(req_buf), "insufficient space for WRQ message");
1019    auto status = tftp_process_msg(ts.session, req_buf, req_buf_sz, ts.out, &ts.outlen,
1020                                   &ts.timeout, nullptr);
1021    ASSERT_EQ(TFTP_NO_ERROR, status, "receive write request failed");
1022    ASSERT_EQ(REQ_RECEIVED, ts.session->state, "tftp session in wrong state");
1023    ASSERT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response");
1024
1025    uint8_t data_buf[516] = {
1026        0x00, 0x03,  // Opcode (DATA)
1027        0x00, 0x01,  // Block
1028        0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, // Data; compiler will fill out the rest with zeros
1029    };
1030    data_buf[515] = 0x79;  // set the last byte to make sure it all gets copied
1031
1032    ifc.write = mock_write;
1033    tftp_session_set_file_interface(ts.session, &ifc);
1034
1035    tx_test_data td;
1036    status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, &td);
1037    EXPECT_EQ(TFTP_NO_ERROR, status, "receive data failed");
1038    EXPECT_TRUE(verify_response_opcode(ts, OPCODE_ACK), "bad response");
1039    EXPECT_TRUE(verify_write_data(data_buf + 4, td), "bad write data");
1040    EXPECT_EQ(1, ts.session->block_number, "tftp session block number mismatch");
1041    EXPECT_EQ(0, ts.session->window_index, "tftp session window index mismatch");
1042
1043    END_TEST;
1044}
1045
1046static bool test_tftp_receive_data_final_block(void) {
1047    BEGIN_TEST;
1048
1049    test_state ts;
1050    ts.reset(1024, 1024, 1500);
1051    tftp_file_interface ifc = {NULL,
1052            [](const char* filename, size_t size, void* cookie) -> tftp_status {
1053                EXPECT_STR_EQ(filename, kRemoteFilename, "bad filename");
1054                EXPECT_EQ(size, 1024, "bad file size");
1055                return 0;
1056            }, NULL, NULL, NULL};
1057    tftp_session_set_file_interface(ts.session, &ifc);
1058
1059    char req_buf[256];
1060    req_buf[0] = 0x00;
1061    req_buf[1] = OPCODE_WRQ;
1062    size_t req_buf_sz = 2 + snprintf(&req_buf[2], sizeof(req_buf) - 2,
1063                                     "%s%cOCTET%cTSIZE%c%d",
1064                                     kRemoteFilename, '\0', '\0', '\0', 1024)
1065                          + 1;
1066
1067    ASSERT_LT(req_buf_sz, (int)sizeof(req_buf), "insufficient space for WRQ message");
1068    auto status = tftp_process_msg(ts.session, req_buf, req_buf_sz, ts.out, &ts.outlen, &ts.timeout, nullptr);
1069    ASSERT_EQ(TFTP_NO_ERROR, status, "receive write request failed");
1070    ASSERT_EQ(REQ_RECEIVED, ts.session->state, "tftp session in wrong state");
1071    ASSERT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response");
1072
1073    uint8_t data_buf[516] = {
1074        0x00, 0x03,  // Opcode (DATA)
1075        0x00, 0x01,  // Block
1076        0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, // Data; compiler will fill out the rest with zeros
1077    };
1078    data_buf[515] = 0x79;  // set the last byte to make sure it all gets copied
1079
1080    ifc.write = mock_write;
1081    tftp_session_set_file_interface(ts.session, &ifc);
1082
1083    tx_test_data td;
1084    status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, &td);
1085    ASSERT_EQ(TFTP_NO_ERROR, status, "receive data failed");
1086    ASSERT_TRUE(verify_response_opcode(ts, OPCODE_ACK), "bad response");
1087    ASSERT_TRUE(verify_write_data(data_buf + 4, td), "bad write data");
1088
1089    // Update block number and first/last bytes of the data packet
1090    data_buf[3]++;
1091    data_buf[4]++;
1092    data_buf[515]++;
1093    td.expected.block++;
1094    td.expected.offset = DEFAULT_BLOCKSIZE;
1095
1096    status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, &td);
1097    EXPECT_EQ(TFTP_NO_ERROR, status, "receive data failed");
1098    ASSERT_TRUE(verify_response_opcode(ts, OPCODE_ACK), "bad response");
1099    EXPECT_TRUE(verify_write_data(data_buf + 4, td), "bad write data");
1100
1101    // Last data packet. Empty, indicating end of data.
1102    data_buf[3]++;
1103    status = tftp_process_msg(ts.session, data_buf, 4, ts.out, &ts.outlen, &ts.timeout, nullptr);
1104    EXPECT_EQ(TFTP_TRANSFER_COMPLETED, status, "receive data failed");
1105    ASSERT_TRUE(verify_response_opcode(ts, OPCODE_ACK), "bad response");
1106    END_TEST;
1107}
1108
1109static bool test_tftp_receive_data_blocksize(void) {
1110    BEGIN_TEST;
1111
1112    test_state ts;
1113    ts.reset(1024, 2048, 1500);
1114    tftp_file_interface ifc = {NULL,
1115            [](const char* filename, size_t size, void* cookie) -> tftp_status {
1116                EXPECT_STR_EQ(filename, kRemoteFilename, "bad filename");
1117                EXPECT_EQ(size, 2048, "bad file size");
1118                return 0;
1119            }, NULL, NULL, NULL};
1120    tftp_session_set_file_interface(ts.session, &ifc);
1121
1122    char req_buf[256];
1123    req_buf[0] = 0x00;
1124    req_buf[1] = OPCODE_WRQ;
1125    size_t req_buf_sz = 2 + snprintf(&req_buf[2], sizeof(req_buf) - 2,
1126                                     "%s%cOCTET%cTSIZE%c%d%cBLKSIZE%c%d",
1127                                     kRemoteFilename, '\0', '\0', '\0', 2048, '\0', '\0', 1024)
1128                          + 1;
1129
1130    ASSERT_LT(req_buf_sz, (int)sizeof(req_buf), "insufficient space for WRQ message");
1131    auto status = tftp_process_msg(ts.session, req_buf, req_buf_sz, ts.out, &ts.outlen,
1132                                   &ts.timeout, nullptr);
1133    ASSERT_EQ(TFTP_NO_ERROR, status, "receive write request failed");
1134    ASSERT_EQ(REQ_RECEIVED, ts.session->state, "tftp session in wrong state");
1135    ASSERT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response");
1136
1137    uint8_t data_buf[1028] = {
1138        0x00, 0x03,  // Opcode (DATA)
1139        0x00, 0x01,  // Block
1140        0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, // Data; compiler will fill out the rest with zeros
1141    };
1142    data_buf[1027] = 0x79;  // set the last byte to make sure it all gets copied
1143
1144    ifc.write = mock_write;
1145    tftp_session_set_file_interface(ts.session, &ifc);
1146
1147    tx_test_data td;
1148    td.expected.len = 1024;
1149    status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, &td);
1150    EXPECT_EQ(TFTP_NO_ERROR, status, "receive data failed");
1151    EXPECT_TRUE(verify_response_opcode(ts, OPCODE_ACK), "bad response");
1152    EXPECT_TRUE(verify_write_data(data_buf + 4, td), "bad write data");
1153    EXPECT_EQ(1, ts.session->block_number, "tftp session block number mismatch");
1154    EXPECT_EQ(0, ts.session->window_index, "tftp session window index mismatch");
1155
1156    END_TEST;
1157}
1158
1159static bool test_tftp_receive_data_windowsize(void) {
1160    BEGIN_TEST;
1161
1162    test_state ts;
1163    ts.reset(1024, 1025, 1500);
1164    tftp_file_interface ifc = {NULL,
1165            [](const char* filename, size_t size, void* cookie) -> tftp_status {
1166                EXPECT_STR_EQ(filename, kRemoteFilename, "bad filename");
1167                EXPECT_EQ(size, 1025, "bad file size");
1168                return 0;
1169            }, NULL, NULL, NULL};
1170    tftp_session_set_file_interface(ts.session, &ifc);
1171
1172    char req_buf[256];
1173    req_buf[0] = 0x00;
1174    req_buf[1] = OPCODE_WRQ;
1175    size_t req_buf_sz = 2 + snprintf(&req_buf[2], sizeof(req_buf) - 2,
1176                                     "%s%cOCTET%cTSIZE%c%d%cWINDOWSIZE%c%d",
1177                                     kRemoteFilename, '\0', '\0', '\0', 1025, '\0', '\0', 2)
1178                          + 1;
1179
1180    ASSERT_LT(req_buf_sz, (int)sizeof(req_buf), "insufficient space for WRQ message");
1181    auto status = tftp_process_msg(ts.session, req_buf, req_buf_sz, ts.out, &ts.outlen,
1182                                   &ts.timeout, nullptr);
1183    ASSERT_EQ(TFTP_NO_ERROR, status, "receive write request failed");
1184    ASSERT_EQ(REQ_RECEIVED, ts.session->state, "tftp session in wrong state");
1185    ASSERT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response");
1186
1187    uint8_t data_buf[516] = {
1188        0x00, 0x03,  // Opcode (DATA)
1189        0x00, 0x01,  // Block
1190        0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, // Data; compiler will fill out the rest with zeros
1191    };
1192    data_buf[515] = 0x79;  // set the last byte to make sure it all gets copied
1193
1194    ifc.write = mock_write;
1195    tftp_session_set_file_interface(ts.session, &ifc);
1196
1197    tx_test_data td;
1198    status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, &td);
1199    EXPECT_EQ(TFTP_NO_ERROR, status, "receive data failed");
1200    EXPECT_EQ(0, ts.outlen, "no response expected");
1201    EXPECT_TRUE(verify_write_data(data_buf + 4, td), "bad write data");
1202    EXPECT_EQ(1, ts.session->block_number, "tftp session block number mismatch");
1203    EXPECT_EQ(1, ts.session->window_index, "tftp session window index mismatch");
1204
1205    // Update block number and first/last bytes of the data packet
1206    data_buf[3]++;
1207    data_buf[4]++;
1208    data_buf[515]++;
1209    td.expected.block++;
1210    td.expected.offset += DEFAULT_BLOCKSIZE;
1211
1212    status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, &td);
1213    EXPECT_EQ(TFTP_NO_ERROR, status, "receive data failed");
1214    EXPECT_TRUE(verify_response_opcode(ts, OPCODE_ACK), "bad response");
1215    EXPECT_TRUE(verify_write_data(data_buf + 4, td), "bad write data");
1216    EXPECT_EQ(2, ts.session->block_number, "tftp session block number mismatch");
1217    EXPECT_EQ(0, ts.session->window_index, "tftp session window index mismatch");
1218
1219    END_TEST;
1220}
1221
1222static bool test_tftp_receive_data_skipped_block(void) {
1223    BEGIN_TEST;
1224
1225    test_state ts;
1226    ts.reset(1024, 1024, 1500);
1227    tftp_file_interface ifc = {NULL,
1228            [](const char* filename, size_t size, void* cookie) -> tftp_status {
1229                EXPECT_STR_EQ(filename, kRemoteFilename, "bad filename");
1230                EXPECT_EQ(size, 1024, "bad file size");
1231                return 0;
1232            }, NULL, NULL, NULL};
1233    tftp_session_set_file_interface(ts.session, &ifc);
1234
1235    char req_buf[256];
1236    req_buf[0] = 0x00;
1237    req_buf[1] = OPCODE_WRQ;
1238    size_t req_buf_sz = 2 + snprintf(&req_buf[2], sizeof(req_buf) - 2,
1239                                     "%s%cOCTET%cTSIZE%c%d",
1240                                     kRemoteFilename, '\0', '\0', '\0', 1024)
1241                          + 1;
1242
1243    ASSERT_LT(req_buf_sz, (int)sizeof(req_buf), "insufficient space for WRQ message");
1244    auto status = tftp_process_msg(ts.session, req_buf, req_buf_sz, ts.out, &ts.outlen,
1245                                   &ts.timeout, nullptr);
1246    ASSERT_EQ(TFTP_NO_ERROR, status, "receive write request failed");
1247    ASSERT_EQ(REQ_RECEIVED, ts.session->state, "tftp session in wrong state");
1248    ASSERT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response");
1249
1250    // This is block 2, meaning we missed block 1 somehow.
1251    uint8_t data_buf[516] = {
1252        0x00, 0x03,  // Opcode (DATA)
1253        0x00, 0x02,  // Block
1254        0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, // Data; compiler will fill out the rest with zeros
1255    };
1256    data_buf[515] = 0x79;  // set the last byte to make sure it all gets copied
1257
1258    ifc.write = mock_write;
1259    tftp_session_set_file_interface(ts.session, &ifc);
1260
1261    status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, nullptr);
1262    EXPECT_EQ(TFTP_NO_ERROR, status, "receive data failed");
1263    ASSERT_GT(ts.outlen, 0, "outlen must not be zero");
1264    auto msg = reinterpret_cast<tftp_data_msg*>(ts.out);
1265    EXPECT_EQ(ntohs(msg->opcode) & 0xff, OPCODE_ACK, "bad opcode");
1266    // The opcode prefix should have been advanced when we saw a dropped block
1267    EXPECT_EQ((ntohs(msg->opcode) & 0xff00) >> 8, 1, "bad opcode prefix");
1268    EXPECT_EQ(ntohs(msg->block), 0, "bad block number");
1269    EXPECT_EQ(0, ts.session->block_number, "tftp session block number mismatch");
1270    EXPECT_EQ(0, ts.session->window_index, "tftp session window index mismatch");
1271
1272    // Verify with the opcode prefix disabled
1273    tftp_session_set_opcode_prefix_use(ts.session, false);
1274    status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, nullptr);
1275    EXPECT_EQ(TFTP_NO_ERROR, status, "receive data failed");
1276    ASSERT_GT(ts.outlen, 0, "outlen must not be zero");
1277    msg = reinterpret_cast<tftp_data_msg*>(ts.out);
1278    EXPECT_EQ(ntohs(msg->opcode) & 0xff, OPCODE_ACK, "bad opcode");
1279    EXPECT_EQ((ntohs(msg->opcode) & 0xff00) >> 8, 0, "bad opcode prefix");
1280    EXPECT_EQ(ntohs(msg->block), 0, "bad block number");
1281
1282    END_TEST;
1283}
1284
1285static bool test_tftp_receive_data_windowsize_skipped_block(void) {
1286    BEGIN_TEST;
1287
1288    test_state ts;
1289    ts.reset(1024, 2048, 1500);
1290    tftp_file_interface ifc = {NULL,
1291            [](const char* filename, size_t size, void* cookie) -> tftp_status {
1292                EXPECT_STR_EQ(filename, kRemoteFilename, "bad filename");
1293                EXPECT_EQ(size, 2048, "bad file size");
1294                return 0;
1295            }, NULL, NULL, NULL};
1296    tftp_session_set_file_interface(ts.session, &ifc);
1297
1298    char req_buf[256];
1299    req_buf[0] = 0x00;
1300    req_buf[1] = OPCODE_WRQ;
1301    size_t req_buf_sz = 2 + snprintf(&req_buf[2], sizeof(req_buf) - 2,
1302                                     "%s%cOCTET%cTSIZE%c%d%cWINDOWSIZE%c%d",
1303                                     kRemoteFilename, '\0', '\0', '\0', 2048, '\0', '\0', 3)
1304                          + 1;
1305
1306    ASSERT_LT(req_buf_sz, (int)sizeof(req_buf), "insufficient space for WRQ message");
1307    auto status = tftp_process_msg(ts.session, req_buf, req_buf_sz, ts.out, &ts.outlen,
1308                                   &ts.timeout, nullptr);
1309    ASSERT_EQ(TFTP_NO_ERROR, status, "receive write request failed");
1310    ASSERT_EQ(REQ_RECEIVED, ts.session->state, "tftp session in wrong state");
1311    ASSERT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response");
1312
1313    uint8_t data_buf[516] = {
1314        0x00, 0x03,  // Opcode (DATA)
1315        0x00, 0x01,  // Block
1316        0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, // Data; compiler will fill out the rest with zeros
1317    };
1318    data_buf[515] = 0x79;  // set the last byte to make sure it all gets copied
1319
1320    ifc.write = mock_write;
1321    tftp_session_set_file_interface(ts.session, &ifc);
1322
1323    tx_test_data td;
1324    status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, &td);
1325    EXPECT_EQ(TFTP_NO_ERROR, status, "receive data failed");
1326    EXPECT_TRUE(verify_write_data(data_buf + 4, td), "bad write data");
1327    EXPECT_EQ(1, ts.session->block_number, "tftp session block number mismatch");
1328    EXPECT_EQ(1, ts.session->window_index, "tftp session window index mismatch");
1329
1330    // Update block number and first/last bytes of the data packet
1331    data_buf[3]++;
1332    data_buf[4]++;
1333    data_buf[515]++;
1334    td.expected.block++;
1335    td.expected.offset += DEFAULT_BLOCKSIZE;
1336
1337    status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, &td);
1338    EXPECT_EQ(TFTP_NO_ERROR, status, "receive data failed");
1339    EXPECT_TRUE(verify_write_data(data_buf + 4, td), "bad write data");
1340    EXPECT_EQ(2, ts.session->block_number, "tftp session block number mismatch");
1341    EXPECT_EQ(2, ts.session->window_index, "tftp session window index mismatch");
1342
1343
1344    // Update block number and first/last bytes of the data packet. Block number
1345    // goes up by 2 to indicate a skipped block.
1346    data_buf[3] = 4u;
1347    data_buf[4]++;
1348    data_buf[515]++;
1349    status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, nullptr);
1350    EXPECT_EQ(TFTP_NO_ERROR, status, "receive data failed");
1351    ASSERT_GT(ts.outlen, 0, "outlen must not be zero");
1352    auto msg = reinterpret_cast<tftp_data_msg*>(ts.out);
1353    EXPECT_EQ(ntohs(msg->opcode) & 0xff, OPCODE_ACK, "bad opcode");
1354    // Opcode prefix should have been incremented when a packet was not received
1355    EXPECT_EQ((ntohs(msg->opcode) & 0xff00) >> 8, 1, "bad opcode prefix");
1356    EXPECT_EQ(ntohs(msg->block), 2, "bad block number");
1357    EXPECT_EQ(0, td.actual.data[1024], "block 3 should be empty");
1358    EXPECT_EQ(2, ts.session->block_number, "tftp session block number mismatch");
1359    // Reset the window index after sending the ack with the last known block
1360    EXPECT_EQ(0, ts.session->window_index, "tftp session window index mismatch");
1361
1362    END_TEST;
1363}
1364
1365namespace {
1366
1367constexpr const unsigned long kWrapAt = 0x3ffff;
1368constexpr const int kBlockSize = 8;
1369constexpr const unsigned long kFileSize = (kWrapAt + 2) * kBlockSize;
1370
1371static bool test_tftp_receive_data_block_wrapping(void) {
1372    BEGIN_TEST;
1373
1374    test_state ts;
1375    static bool write_called;
1376    write_called = false;
1377    ts.reset(1024, 2048, 2048);
1378
1379    tftp_file_interface ifc = {NULL, NULL, NULL, NULL, NULL};
1380    ifc.open_write = [](const char* filename, size_t size, void* cookie) -> tftp_status {
1381                         EXPECT_STR_EQ(filename, kRemoteFilename,
1382                             "bad filename");
1383                         EXPECT_EQ(size, kFileSize, "bad file size");
1384                         return 0;
1385                     };
1386    ifc.write = [](const void* data, size_t* length, off_t offset, void* file_cookie)
1387                    -> tftp_status {
1388                    // Remember that the block count starts at zero, which makes the offset
1389                    // calculation a bit counter-intuitive (one might expect that we would
1390                    // be writing to (kWrapAt + 1) * kBlockSize).
1391                    EXPECT_EQ(kWrapAt * kBlockSize, offset, "block count failed to wrap");
1392                    write_called = true;
1393                    return TFTP_NO_ERROR;
1394                };
1395    tftp_session_set_file_interface(ts.session, &ifc);
1396
1397    char req_buf[1024];
1398    req_buf[0] = 0x00;
1399    req_buf[1] = OPCODE_WRQ;
1400    int req_buf_sz = 2 + snprintf(&req_buf[2], sizeof(req_buf) - 2,
1401                                  "%s%cOCTET%cTSIZE%c%lu%cBLKSIZE%c%d",
1402                                  kRemoteFilename, '\0', '\0', '\0', kFileSize, '\0', '\0',
1403                                  kBlockSize)
1404                       + 1;
1405
1406    ASSERT_LT(req_buf_sz, (int)sizeof(req_buf), "insufficient space for WRQ message");
1407    auto status = tftp_process_msg(ts.session, req_buf, req_buf_sz, ts.out, &ts.outlen,
1408                                   &ts.timeout, nullptr);
1409    ASSERT_EQ(TFTP_NO_ERROR, status, "receive write request failed");
1410    ASSERT_EQ(REQ_RECEIVED, ts.session->state, "tftp session in wrong state");
1411    ASSERT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response");
1412
1413    // Artificially advance to force block wrapping
1414    ts.session->block_number = kWrapAt;
1415    ts.session->window_index = 0;
1416
1417    uint8_t data_buf[] = {
1418        0x00, 0x03,  // Opcode (DATA)
1419        0x00, 0x00,  // Block
1420        0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08  // Data
1421    };
1422
1423    status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen,
1424                              &ts.timeout, NULL);
1425    EXPECT_EQ(TFTP_NO_ERROR, status, "failed to process data");
1426    EXPECT_TRUE(write_called, "no attempt to write data");
1427    EXPECT_EQ(kWrapAt + 1, ts.session->block_number, "failed to advance block number");
1428
1429    uint8_t expected_ack[] = {
1430        0x00, 0x04,  // Opcode (ACK)
1431        0x00, 0x00   // Block
1432    };
1433    EXPECT_EQ(sizeof(expected_ack), ts.outlen, "response size mismatch");
1434    EXPECT_EQ(0, memcmp(expected_ack, ts.out, sizeof(expected_ack)), "bad response");
1435
1436    END_TEST;
1437}
1438
1439}
1440
1441static bool test_tftp_send_data_receive_ack(void) {
1442    BEGIN_TEST;
1443
1444    test_state ts;
1445    ts.reset(1024, 1024, 1500);
1446
1447    auto status = tftp_generate_request(ts.session, SEND_FILE, kLocalFilename, kRemoteFilename,
1448        MODE_OCTET, ts.msg_size, NULL, NULL, NULL, ts.out, &ts.outlen, &ts.timeout);
1449    EXPECT_EQ(TFTP_NO_ERROR, status, "error generating write request");
1450    EXPECT_TRUE(verify_write_request(ts), "bad write request");
1451
1452    uint8_t oack_buf[] = {
1453        0x00, 0x06,                     // Opcode (OACK)
1454        'T', 'S', 'I', 'Z', 'E', 0x00,  // Option
1455        '1', '0', '2', '4', 0x00,       // TSIZE value
1456    };
1457
1458    tftp_file_interface ifc = {NULL, NULL, mock_read, NULL, NULL};
1459    tftp_session_set_file_interface(ts.session, &ifc);
1460
1461    tx_test_data td;
1462    status = tftp_process_msg(ts.session, oack_buf, sizeof(oack_buf), ts.out, &ts.outlen, &ts.timeout, &td);
1463    ASSERT_EQ(TFTP_NO_ERROR, status, "receive error");
1464    EXPECT_EQ(ts.outlen, sizeof(tftp_data_msg) + DEFAULT_BLOCKSIZE, "bad outlen");
1465    EXPECT_TRUE(verify_read_data(ts, td), "bad test data");
1466
1467    uint8_t ack_buf[] = {
1468        0x00, 0x04,  // Opcode (ACK)
1469        0x00, 0x01,  // Block
1470    };
1471
1472    td.expected.block = 2;
1473    td.expected.offset += DEFAULT_BLOCKSIZE;
1474    td.expected.data[1] = 'f';
1475    status = tftp_process_msg(ts.session, ack_buf, sizeof(ack_buf), ts.out, &ts.outlen, &ts.timeout, &td);
1476    EXPECT_EQ(TFTP_NO_ERROR, status, "receive error");
1477    EXPECT_EQ(SENDING_DATA, ts.session->state, "session should be in state SENDING_DATA");
1478    // The block number will not advance until we see an ACK for block 2
1479    EXPECT_EQ(1, ts.session->block_number, "tftp session block number mismatch");
1480    EXPECT_EQ(1, ts.session->window_index, "tftp session window index mismatch");
1481    EXPECT_EQ(ts.outlen, sizeof(tftp_data_msg) + DEFAULT_BLOCKSIZE, "bad outlen");
1482    EXPECT_TRUE(verify_read_data(ts, td), "bad test data");
1483
1484    END_TEST;
1485}
1486
1487static bool test_tftp_send_data_receive_final_ack(void) {
1488    BEGIN_TEST;
1489
1490    test_state ts;
1491    ts.reset(1024, 1024, 1500);
1492
1493    auto status = tftp_generate_request(ts.session, SEND_FILE, kLocalFilename, kRemoteFilename,
1494        MODE_OCTET, ts.msg_size, NULL, NULL, NULL, ts.out, &ts.outlen, &ts.timeout);
1495    EXPECT_EQ(TFTP_NO_ERROR, status, "error generating write request");
1496    EXPECT_TRUE(verify_write_request(ts), "bad write request");
1497
1498    uint8_t oack_buf[] = {
1499        0x00, 0x06,                     // Opcode (OACK)
1500        'T', 'S', 'I', 'Z', 'E', 0x00,  // Option
1501        '1', '0', '2', '4', 0x00,       // TSIZE value
1502    };
1503
1504    tftp_file_interface ifc = {NULL, NULL, mock_read, NULL, NULL};
1505    tftp_session_set_file_interface(ts.session, &ifc);
1506
1507    tx_test_data td;
1508    status = tftp_process_msg(ts.session, oack_buf, sizeof(oack_buf), ts.out, &ts.outlen, &ts.timeout, &td);
1509    ASSERT_EQ(TFTP_NO_ERROR, status, "receive error");
1510    ASSERT_EQ(ts.outlen, sizeof(tftp_data_msg) + DEFAULT_BLOCKSIZE, "bad outlen");
1511    ASSERT_TRUE(verify_read_data(ts, td), "bad test data");
1512
1513    uint8_t ack_buf[] = {
1514        0x00, 0x04,  // Opcode (ACK)
1515        0x00, 0x01,  // Block
1516    };
1517
1518    td.expected.block = 2;
1519    td.expected.offset += DEFAULT_BLOCKSIZE;
1520    status = tftp_process_msg(ts.session, ack_buf, sizeof(ack_buf), ts.out, &ts.outlen, &ts.timeout, &td);
1521    ASSERT_EQ(TFTP_NO_ERROR, status, "receive error");
1522    EXPECT_EQ(ts.outlen, sizeof(tftp_data_msg) + DEFAULT_BLOCKSIZE, "bad outlen");
1523    EXPECT_TRUE(verify_read_data(ts, td), "bad test data");
1524
1525    // second block
1526    ack_buf[3]++;
1527    status = tftp_process_msg(ts.session, ack_buf, sizeof(ack_buf), ts.out, &ts.outlen, &ts.timeout, &td);
1528    ASSERT_EQ(TFTP_NO_ERROR, status, "receive block 2 error");
1529    EXPECT_EQ(ts.outlen, sizeof(tftp_data_msg), "block 3 not empty");
1530
1531    // Do not expect any more sends.
1532    ack_buf[3]++;
1533    status = tftp_process_msg(ts.session, ack_buf, sizeof(ack_buf), ts.out, &ts.outlen, &ts.timeout, nullptr);
1534    EXPECT_EQ(TFTP_TRANSFER_COMPLETED, status, "tftp transfer should be complete");
1535    EXPECT_EQ(ts.outlen, 0, "no outgoing message expected");
1536
1537    END_TEST;
1538}
1539
1540static bool test_tftp_send_data_receive_ack_skipped_block(void) {
1541    BEGIN_TEST;
1542
1543    test_state ts;
1544    ts.reset(1024, 1024, 1500);
1545
1546    auto status = tftp_generate_request(ts.session, SEND_FILE, kLocalFilename, kRemoteFilename,
1547        MODE_OCTET, ts.msg_size, NULL, NULL, NULL, ts.out, &ts.outlen, &ts.timeout);
1548    EXPECT_EQ(TFTP_NO_ERROR, status, "error generating write request");
1549    EXPECT_TRUE(verify_write_request(ts), "bad write request");
1550
1551    uint8_t oack_buf[] = {
1552        0x00, 0x06,                     // Opcode (OACK)
1553        'T', 'S', 'I', 'Z', 'E', 0x00,  // Option
1554        '1', '0', '2', '4', 0x00,       // TSIZE value
1555    };
1556
1557    tftp_file_interface ifc = {NULL, NULL, mock_read, NULL, NULL};
1558    tftp_session_set_file_interface(ts.session, &ifc);
1559
1560    tx_test_data td;
1561    status = tftp_process_msg(ts.session, oack_buf, sizeof(oack_buf), ts.out, &ts.outlen, &ts.timeout, &td);
1562    ASSERT_EQ(TFTP_NO_ERROR, status, "receive error");
1563    ASSERT_EQ(ts.outlen, sizeof(tftp_data_msg) + DEFAULT_BLOCKSIZE, "bad outlen");
1564    ASSERT_TRUE(verify_read_data(ts, td), "bad test data");
1565
1566    uint8_t ack_buf[] = {
1567        0x00, 0x04,  // Opcode (ACK)
1568        0x00, 0x00,  // Block
1569    };
1570
1571    tx_test_data td2;
1572    status = tftp_process_msg(ts.session, ack_buf, sizeof(ack_buf), ts.out, &ts.outlen, &ts.timeout, &td2);
1573    EXPECT_EQ(TFTP_NO_ERROR, status, "receive error");
1574    EXPECT_EQ(SENDING_DATA, ts.session->state, "session should be in state SENDING_DATA");
1575    EXPECT_EQ(0, ts.session->block_number, "tftp session block number mismatch");
1576    EXPECT_EQ(1, ts.session->window_index, "tftp window index mismatch");
1577    EXPECT_EQ(ts.outlen, sizeof(tftp_data_msg) + DEFAULT_BLOCKSIZE, "bad outlen");
1578    EXPECT_TRUE(verify_read_data(ts, td2), "bad test data");
1579    END_TEST;
1580}
1581
1582static bool test_tftp_send_data_receive_ack_window_size(void) {
1583    uint16_t kWindowSize = 2;
1584    BEGIN_TEST;
1585
1586    test_state ts;
1587    ts.reset(1024, 2048, 1500);
1588
1589    auto status = tftp_generate_request(ts.session, SEND_FILE, kLocalFilename, kRemoteFilename,
1590        MODE_OCTET, ts.msg_size, NULL, NULL, &kWindowSize, ts.out, &ts.outlen, &ts.timeout);
1591    EXPECT_EQ(TFTP_NO_ERROR, status, "error generating write request");
1592    EXPECT_TRUE(verify_write_request(ts), "bad write request");
1593
1594    uint8_t oack_buf[] = {
1595        0x00, 0x06,                     // Opcode (OACK)
1596        'T', 'S', 'I', 'Z', 'E', 0x00,  // Option
1597        '2', '0', '4', '8', 0x00,       // TSIZE value
1598        'W', 'I', 'N', 'D', 'O', 'W', 'S', 'I', 'Z', 'E', 0x00,      // Option
1599        '2', 0x00,                                              // WINDOWSIZE value
1600    };
1601
1602    tftp_file_interface ifc = {NULL, NULL, mock_read, NULL, NULL};
1603    tftp_session_set_file_interface(ts.session, &ifc);
1604
1605    tx_test_data td;
1606    status = tftp_process_msg(ts.session, oack_buf, sizeof(oack_buf), ts.out, &ts.outlen, &ts.timeout, &td);
1607    ASSERT_EQ(TFTP_NO_ERROR, status, "receive error");
1608    ASSERT_EQ(0, ts.session->block_number, "tftp session block number mismatch");
1609    ASSERT_EQ(1, ts.session->window_index, "tftp session window index mismatch");
1610    ASSERT_EQ(ts.outlen, sizeof(tftp_data_msg) + DEFAULT_BLOCKSIZE, "bad outlen");
1611    ASSERT_TRUE(verify_read_data(ts, td), "bad test data");
1612    ASSERT_TRUE(tftp_session_has_pending(ts.session), "expected pending data to transmit");
1613
1614    td.expected.block++;
1615    td.expected.offset += DEFAULT_BLOCKSIZE;
1616    td.expected.data[0]++;
1617    status = tftp_prepare_data(ts.session, ts.out, &ts.outlen, &ts.timeout, &td);
1618    ASSERT_EQ(TFTP_NO_ERROR, status, "receive error");
1619    // Window index doesn't roll until we receive an ACK
1620    ASSERT_EQ(0, ts.session->block_number, "tftp session block number mismatch");
1621    ASSERT_EQ(2, ts.session->window_index, "tftp session window index mismatch");
1622    ASSERT_EQ(ts.outlen, sizeof(tftp_data_msg) + DEFAULT_BLOCKSIZE, "bad outlen");
1623    ASSERT_TRUE(verify_read_data(ts, td), "bad test data");
1624    ASSERT_FALSE(tftp_session_has_pending(ts.session), "expected to wait for ack");
1625
1626    uint8_t ack_buf[] = {
1627        0x00, 0x04,  // Opcode (ACK)
1628        0x00, 0x02,  // Block
1629    };
1630
1631    td.expected.block++;
1632    td.expected.offset += DEFAULT_BLOCKSIZE;
1633    td.expected.data[1]++;
1634    status = tftp_process_msg(ts.session, ack_buf, sizeof(ack_buf), ts.out, &ts.outlen, &ts.timeout, &td);
1635    EXPECT_EQ(TFTP_NO_ERROR, status, "receive error");
1636    EXPECT_EQ(SENDING_DATA, ts.session->state, "session should be in state SENDING_DATA");
1637    EXPECT_EQ(2, ts.session->block_number, "tftp session block number mismatch");
1638    EXPECT_EQ(1, ts.session->window_index, "tftp session window index mismatch");
1639    EXPECT_EQ(ts.outlen, sizeof(tftp_data_msg) + DEFAULT_BLOCKSIZE, "bad outlen");
1640    EXPECT_TRUE(verify_read_data(ts, td), "bad test data");
1641    EXPECT_TRUE(tftp_session_has_pending(ts.session), "expected pending data to transmit");
1642
1643    END_TEST;
1644}
1645
1646static bool test_tftp_send_data_receive_ack_block_wrapping(void) {
1647    BEGIN_TEST;
1648
1649    constexpr unsigned long kWrapAt = 0x3ffff;
1650    constexpr uint16_t kBlockSize = 8;
1651    constexpr unsigned long kFileSize = (kWrapAt + 2) * kBlockSize;
1652
1653    static int reads_performed;
1654    reads_performed = 0;
1655
1656    test_state ts;
1657    ts.reset(1024, 2048, 2048);
1658
1659    auto status = tftp_generate_request(ts.session, SEND_FILE, kLocalFilename, kRemoteFilename,
1660        MODE_OCTET, kFileSize, &kBlockSize, NULL, NULL, ts.out, &ts.outlen, &ts.timeout);
1661    EXPECT_EQ(TFTP_NO_ERROR, status, "error generating write request");
1662    EXPECT_TRUE(verify_write_request(ts), "bad write request");
1663
1664    char oack_buf[256];
1665    oack_buf[0] = 0x00;
1666    oack_buf[1] = 0x06;
1667    size_t oack_buf_sz = 2 + snprintf(&oack_buf[2], sizeof(oack_buf) - 2,
1668                                      "TSIZE%c%lu%cBLKSIZE%c%d",
1669                                      '\0', kFileSize, '\0', '\0', kBlockSize)
1670                           + 1;
1671
1672    ASSERT_LT(oack_buf_sz, (int)sizeof(oack_buf), "insufficient space for OACK message");
1673    tftp_file_interface ifc = {NULL, NULL, NULL, NULL, NULL};
1674    ifc.read = [](void* data, size_t* length, off_t offset, void* cookie) -> tftp_status {
1675                   EXPECT_EQ(0, offset, "incorrect initial read");
1676                   reads_performed++;
1677                   return TFTP_NO_ERROR;
1678               };
1679    tftp_session_set_file_interface(ts.session, &ifc);
1680
1681    status = tftp_process_msg(ts.session, oack_buf, oack_buf_sz, ts.out, &ts.outlen,
1682                              &ts.timeout, NULL);
1683    ASSERT_EQ(TFTP_NO_ERROR, status, "failure to process OACK");
1684    EXPECT_EQ(1, reads_performed, "failed to call read function");
1685
1686    // Artificially advance the session to a point where wrapping will occur
1687    ts.session->block_number = kWrapAt;
1688    ts.session->window_index = 0;
1689
1690    uint8_t data_buf[4 + kBlockSize];
1691    size_t data_buf_len = sizeof(data_buf);
1692    ifc.read = [](void* data, size_t* length, off_t offset, void* cookie) -> tftp_status {
1693                   // Keep in mind that the block index starts at 1, so the offset calculation
1694                   // is not necessarily intuitive
1695                   EXPECT_EQ(kWrapAt * kBlockSize, offset, "incorrect wrapping read");
1696                   reads_performed++;
1697                   return TFTP_NO_ERROR;
1698               };
1699    tftp_session_set_file_interface(ts.session, &ifc);
1700    status = tftp_prepare_data(ts.session, data_buf, &data_buf_len, &ts.timeout, NULL);
1701    ASSERT_EQ(TFTP_NO_ERROR, status, "failed to generate DATA packet");
1702    EXPECT_EQ(2, reads_performed, "failed to call read function");
1703    EXPECT_EQ(sizeof(data_buf), data_buf_len, "improperly formatted DATA packet");
1704    unsigned int opcode = data_buf[0] << 8 | data_buf[1];
1705    EXPECT_EQ(0x0003, opcode, "incorrect DATA packet opcode");
1706    unsigned int block = data_buf[2] << 8 | data_buf[3];
1707    EXPECT_EQ(0x0000, block, "incorrect DATA packet block");
1708
1709    END_TEST;
1710}
1711
1712static bool test_tftp_send_data_receive_ack_skip_block_wrap(void) {
1713    BEGIN_TEST;
1714
1715    constexpr unsigned long kLastBlockSent = 0x40003;
1716    constexpr unsigned long kAckBlock = 0x3fffb;
1717    constexpr uint16_t kBlockSize = 8;
1718    constexpr unsigned long kFileSize = 0x50000 * kBlockSize;
1719
1720    static int reads_performed;
1721    reads_performed = 0;
1722
1723    test_state ts;
1724    ts.reset(1024, 2048, 2048);
1725
1726    // Create a write request
1727    auto status = tftp_generate_request(ts.session, SEND_FILE, kLocalFilename, kRemoteFilename,
1728        MODE_OCTET, kFileSize, &kBlockSize, NULL, NULL, ts.out, &ts.outlen, &ts.timeout);
1729    EXPECT_EQ(TFTP_NO_ERROR, status, "error generating write request");
1730    EXPECT_TRUE(verify_write_request(ts), "bad write request");
1731
1732    // Simulate a response (OACK)
1733    char oack_buf[256];
1734    oack_buf[0] = 0x00;
1735    oack_buf[1] = 0x06;
1736    size_t oack_buf_sz = 2 + snprintf(&oack_buf[2], sizeof(oack_buf) - 2,
1737                                      "TSIZE%c%lu%cBLKSIZE%c%d",
1738                                      '\0', kFileSize, '\0', '\0', kBlockSize)
1739                           + 1;
1740
1741    ASSERT_LT(oack_buf_sz, (int)sizeof(oack_buf), "insufficient space for OACK message");
1742    tftp_file_interface ifc = {NULL, NULL, NULL, NULL, NULL};
1743    ifc.read = [](void* data, size_t* length, off_t offset, void* cookie) -> tftp_status {
1744                   EXPECT_EQ(0, offset, "incorrect initial read");
1745                   reads_performed++;
1746                   return TFTP_NO_ERROR;
1747               };
1748    tftp_session_set_file_interface(ts.session, &ifc);
1749
1750    // Process OACK and generate write of first block
1751    status = tftp_process_msg(ts.session, oack_buf, oack_buf_sz, ts.out, &ts.outlen,
1752                              &ts.timeout, NULL);
1753    ASSERT_EQ(TFTP_NO_ERROR, status, "failure to process OACK");
1754    EXPECT_EQ(1, reads_performed, "failed to call read function");
1755
1756    // Artificially advance the session so we can test wrapping
1757    ts.session->block_number = kLastBlockSent;
1758    ts.session->window_index = 0;
1759
1760    // Create a DATA packet for block kLastBlockSent + 1
1761    uint8_t data_buf[4 + kBlockSize] = {0};
1762    size_t data_buf_len = sizeof(data_buf);
1763    tftp_data_msg* msg = reinterpret_cast<tftp_data_msg*>(&data_buf[0]);
1764    ifc.read = [](void* data, size_t* length, off_t offset, void* cookie) -> tftp_status {
1765                   // Keep in mind that the block index starts at 1, so the offset calculation
1766                   // is not necessarily intuitive
1767                   EXPECT_EQ(kLastBlockSent * kBlockSize, offset, "incorrect read offset");
1768                   reads_performed++;
1769                   return TFTP_NO_ERROR;
1770               };
1771    tftp_session_set_file_interface(ts.session, &ifc);
1772    status = tftp_prepare_data(ts.session, data_buf, &data_buf_len, &ts.timeout, NULL);
1773    ASSERT_EQ(TFTP_NO_ERROR, status, "failed to generate DATA packet");
1774    EXPECT_EQ(2, reads_performed, "failed to call read function");
1775    EXPECT_EQ(sizeof(data_buf), data_buf_len, "improperly formatted DATA packet");
1776    unsigned int opcode = htons(msg->opcode);
1777    EXPECT_EQ(OPCODE_DATA, opcode, "incorrect DATA packet opcode");
1778    uint16_t offset = ntohs(msg->block);
1779    ASSERT_EQ((kLastBlockSent + 1) & 0xffff, offset, "incorrect DATA packet block");
1780
1781    // Simulate an ACK response that is before our last block wrap
1782    tftp_data_msg ack_msg;
1783    ack_msg.opcode = htons(OPCODE_ACK);
1784    ack_msg.block = htons(kAckBlock & 0xffff);
1785    ifc.read = [](void* data, size_t* length, off_t offset, void* cookie) -> tftp_status {
1786                   EXPECT_EQ(kAckBlock * kBlockSize, offset, "incorrect read offset");
1787                   reads_performed++;
1788                   return TFTP_NO_ERROR;
1789               };
1790    tftp_session_set_file_interface(ts.session, &ifc);
1791
1792    // Next DATA packet should backup to proper address (before wrap)
1793    status = tftp_process_msg(ts.session, reinterpret_cast<void*>(&ack_msg), sizeof(ack_msg),
1794                              ts.out, &ts.outlen, &ts.timeout, NULL);
1795    ASSERT_EQ(TFTP_NO_ERROR, status, "no ACK generated");
1796    EXPECT_EQ(3, reads_performed, "failed to call read function");
1797    ASSERT_EQ(ts.outlen, sizeof(tftp_data_msg) + kBlockSize, "improper DATA packet size");
1798    msg = reinterpret_cast<tftp_data_msg*>(ts.out);
1799    EXPECT_EQ(OPCODE_DATA, ntohs(msg->opcode) & 0xff, "incorrect DATA packet opcode");
1800    // Opcode prefix should have been incremented when a packet was dropped
1801    EXPECT_EQ(1, (ntohs(msg->opcode) & 0xff00) >> 8, "incorrect opcode prefix");
1802    EXPECT_EQ((kAckBlock + 1) & 0xffff, ntohs(msg->block), "incorrect DATA packet block");
1803    EXPECT_EQ(ts.session->block_number, kAckBlock, "session offset not rewound correctly");
1804    EXPECT_EQ(ts.session->window_index, 1, "window index not set correctly");
1805
1806    // Try again, this time disabling opcode prefixes
1807    ifc.read = [](void* data, size_t* length, off_t offset, void* cookie) -> tftp_status {
1808                   return TFTP_NO_ERROR;
1809               };
1810    tftp_session_set_file_interface(ts.session, &ifc);
1811    tftp_session_set_opcode_prefix_use(ts.session, false);
1812    ack_msg.block = htons((kAckBlock + 1) & 0xffff);
1813    status = tftp_process_msg(ts.session, reinterpret_cast<void*>(&ack_msg), sizeof(ack_msg),
1814                              ts.out, &ts.outlen, &ts.timeout, NULL);
1815    ASSERT_EQ(TFTP_NO_ERROR, status, "no ACK generated");
1816    ASSERT_EQ(ts.outlen, sizeof(tftp_data_msg) + kBlockSize, "improper DATA packet size");
1817    msg = reinterpret_cast<tftp_data_msg*>(ts.out);
1818    EXPECT_EQ(OPCODE_DATA, ntohs(msg->opcode) & 0xff, "incorrect DATA packet opcode");
1819    EXPECT_EQ(0, (ntohs(msg->opcode) & 0xff00) >> 8, "incorrect opcode prefix");
1820    EXPECT_EQ((kAckBlock + 2) & 0xffff, ntohs(msg->block), "incorrect DATA packet block");
1821
1822    END_TEST;
1823}
1824
1825static ssize_t open_read_should_wait(const char* filename, void* cookie) {
1826    return TFTP_ERR_SHOULD_WAIT;
1827}
1828
1829static tftp_status open_write_should_wait(const char* filename, size_t size, void* cookie) {
1830    return TFTP_ERR_SHOULD_WAIT;
1831}
1832
1833/* Verify behavior when one of our open_file interface functions returns TFTP_ERR_SHOULD_WAIT. */
1834static bool test_tftp_open_should_wait(tftp_file_direction dir) {
1835    BEGIN_TEST;
1836
1837    const uint16_t block_size = 456;
1838    const uint8_t timeout = 3;
1839    const uint16_t window_size = 128;
1840
1841    test_state ts;
1842    ts.reset(1024, 1024, 1500);
1843    tftp_file_interface ifc = {open_read_should_wait, open_write_should_wait, NULL, NULL, NULL};
1844    tftp_session_set_file_interface(ts.session, &ifc);
1845
1846    // Construct a RRQ or WRQ packet
1847    size_t req_file_size = (dir == SEND_FILE) ? 1024 : 0;
1848    char buf[256];
1849    buf[0] = 0x00;
1850    buf[1] = (dir == SEND_FILE) ? OPCODE_WRQ : OPCODE_RRQ;
1851    size_t buf_sz = 2;
1852    buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz,
1853                       "%s%cOCTET%cTSIZE%c%zu%cBLKSIZE%c%d%cTIMEOUT%c%d%cWINDOWSIZE%c%d",
1854                       kRemoteFilename, '\0',
1855                       '\0',
1856                       '\0', req_file_size, '\0',
1857                       '\0', block_size, '\0',
1858                       '\0', timeout, '\0',
1859                       '\0', window_size) + 1;
1860    ASSERT_LT(buf_sz, (int)sizeof(buf), "insufficient space for request");
1861
1862    tftp_status status = tftp_process_msg(ts.session, buf, buf_sz, ts.out, &ts.outlen, &ts.timeout,
1863                                          nullptr);
1864
1865    // Check API return value
1866    EXPECT_EQ(TFTP_ERR_SHOULD_WAIT, status, "expected SHOULD_WAIT status");
1867
1868    // tftp_process_msg should have generated an error packet response - verify its fields
1869    ASSERT_GT(ts.outlen, 0);
1870    auto msg = reinterpret_cast<tftp_err_msg*>(ts.out);
1871    EXPECT_EQ(msg->opcode, htons(OPCODE_ERROR));
1872    EXPECT_EQ(msg->err_code, htons(TFTP_ERR_CODE_BUSY));
1873    if (dir == SEND_FILE) {
1874        const char expected_err[] = "not ready to receive";
1875        EXPECT_STR_EQ(expected_err, msg->msg, "bad error message");
1876    } else {
1877        const char expected_err[] = "not ready to send";
1878        EXPECT_STR_EQ(expected_err, msg->msg, "bad error message");
1879    }
1880
1881    END_TEST;
1882}
1883
1884static bool test_tftp_open_read_should_wait(void) {
1885   // RECV is from the perspective of the client, not the server
1886   return test_tftp_open_should_wait(RECV_FILE);
1887}
1888
1889static bool test_tftp_open_write_should_wait(void) {
1890   // SEND is from the perspective of the client, not the server
1891   return test_tftp_open_should_wait(SEND_FILE);
1892}
1893
1894static bool test_tftp_recv_busy(tftp_file_direction dir) {
1895    BEGIN_TEST;
1896
1897    test_state ts;
1898    ts.reset(1024, 1024, 1500);
1899
1900    auto status = tftp_generate_request(ts.session, dir, kLocalFilename, kRemoteFilename,
1901                                        MODE_OCTET, dir == SEND_FILE ? ts.msg_size : 0,
1902                                        NULL, NULL, NULL, ts.out, &ts.outlen, &ts.timeout);
1903    ASSERT_EQ(TFTP_NO_ERROR, status, "error generating request");
1904    if (dir == SEND_FILE) {
1905        ASSERT_TRUE(verify_write_request(ts), "bad write request");
1906    } else {
1907        ASSERT_TRUE(verify_read_request(ts), "bad read request");
1908    }
1909
1910    // Simulate a BUSY error response
1911    char buf[256];
1912    buf[0] = 0x00;
1913    buf[1] = OPCODE_ERROR;
1914    buf[2] = (TFTP_ERR_CODE_BUSY & 0xff00) >> 8;
1915    buf[3] = TFTP_ERR_CODE_BUSY & 0xff;
1916    size_t buf_sz = 4;
1917    buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz, "not ready") + 1;
1918    ASSERT_LT(buf_sz, (int)sizeof(buf), "insufficient space for request");
1919
1920    status = tftp_process_msg(ts.session, buf, buf_sz, ts.out, &ts.outlen, &ts.timeout, nullptr);
1921
1922    // Check API return value
1923    EXPECT_EQ(TFTP_ERR_SHOULD_WAIT, status, "expected SHOULD_WAIT status");
1924
1925    // tftp_process_msg should not have generated a response
1926    EXPECT_EQ(ts.outlen, 0);
1927
1928    // Verify session state
1929    EXPECT_EQ(NONE, ts.session->state, "bad session: state");
1930
1931    END_TEST;
1932}
1933
1934/* Verify handling of a BUSY error packet when we send a WRQ. */
1935static bool test_tftp_recv_busy_from_wrq(void) {
1936    return test_tftp_recv_busy(SEND_FILE);
1937}
1938
1939/* Verify handling of a BUSY error packet when we send a RRQ. */
1940static bool test_tftp_recv_busy_from_rrq(void) {
1941    return test_tftp_recv_busy(RECV_FILE);
1942}
1943
1944/* Verify that receiving an error other than BUSY puts the session into an error state. */
1945static bool test_tftp_recv_other_err(void) {
1946    BEGIN_TEST;
1947
1948    test_state ts;
1949    ts.reset(1024, 1024, 1500);
1950
1951    auto status = tftp_generate_request(ts.session, SEND_FILE, kLocalFilename, kRemoteFilename,
1952                                        MODE_OCTET, ts.msg_size, NULL, NULL, NULL, ts.out,
1953                                        &ts.outlen, &ts.timeout);
1954    ASSERT_EQ(TFTP_NO_ERROR, status, "error generating request");
1955    ASSERT_TRUE(verify_write_request(ts), "bad write request");
1956
1957    // Simulate a BUSY error response
1958    char buf[256];
1959    buf[0] = 0x00;
1960    buf[1] = OPCODE_ERROR;
1961    buf[2] = (TFTP_ERR_CODE_DISK_FULL & 0xff00) >> 8;
1962    buf[3] = TFTP_ERR_CODE_DISK_FULL & 0xff;
1963    size_t buf_sz = 4;
1964    buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz, "disk full") + 1;
1965    ASSERT_LT(buf_sz, (int)sizeof(buf), "insufficient space for request");
1966
1967    status = tftp_process_msg(ts.session, buf, buf_sz, ts.out, &ts.outlen, &ts.timeout, nullptr);
1968
1969    // Check API return value
1970    EXPECT_EQ(TFTP_ERR_INTERNAL, status, "expected TFTP_ERR_INTERNAL status");
1971
1972    // tftp_process_msg should not have generated a response
1973    EXPECT_EQ(ts.outlen, 0);
1974
1975    // Verify session state
1976    EXPECT_EQ(ERROR, ts.session->state, "bad session: state");
1977
1978    END_TEST;
1979}
1980
1981BEGIN_TEST_CASE(tftp_setup)
1982RUN_TEST(test_tftp_init)
1983RUN_TEST(test_tftp_session_options)
1984END_TEST_CASE(tftp_setup)
1985
1986BEGIN_TEST_CASE(tftp_generate_wrq)
1987RUN_TEST(test_tftp_generate_wrq_default)
1988RUN_TEST(test_tftp_generate_wrq_options)
1989RUN_TEST(test_tftp_generate_wrq_override_blocksize)
1990RUN_TEST(test_tftp_generate_wrq_override_timeout)
1991RUN_TEST(test_tftp_generate_wrq_override_windowsize)
1992END_TEST_CASE(tftp_generate_wrq)
1993
1994BEGIN_TEST_CASE(tftp_generate_rrq)
1995RUN_TEST(test_tftp_generate_rrq_default)
1996RUN_TEST(test_tftp_generate_rrq_options)
1997RUN_TEST(test_tftp_generate_rrq_override_blocksize)
1998RUN_TEST(test_tftp_generate_rrq_override_timeout)
1999RUN_TEST(test_tftp_generate_rrq_override_windowsize)
2000END_TEST_CASE(tftp_generate_rrq)
2001
2002BEGIN_TEST_CASE(tftp_receive_wrq)
2003RUN_TEST(test_tftp_receive_wrq_unexpected)
2004RUN_TEST(test_tftp_receive_wrq_too_large)
2005RUN_TEST(test_tftp_receive_wrq_no_tsize)
2006RUN_TEST(test_tftp_receive_wrq_send_oack)
2007RUN_TEST(test_tftp_receive_wrq_blocksize)
2008RUN_TEST(test_tftp_receive_wrq_timeout)
2009RUN_TEST(test_tftp_receive_wrq_windowsize)
2010RUN_TEST(test_tftp_receive_wrq_have_overrides)
2011RUN_TEST(test_tftp_receive_force_wrq_no_overrides)
2012RUN_TEST(test_tftp_receive_force_wrq_have_overrides)
2013END_TEST_CASE(tftp_receive_wrq)
2014
2015BEGIN_TEST_CASE(tftp_receive_rrq)
2016RUN_TEST(test_tftp_receive_rrq_unexpected)
2017RUN_TEST(test_tftp_receive_rrq_too_large)
2018RUN_TEST(test_tftp_receive_rrq_no_tsize)
2019RUN_TEST(test_tftp_receive_rrq_send_oack)
2020RUN_TEST(test_tftp_receive_rrq_blocksize)
2021RUN_TEST(test_tftp_receive_rrq_timeout)
2022RUN_TEST(test_tftp_receive_rrq_windowsize)
2023RUN_TEST(test_tftp_receive_rrq_have_overrides)
2024RUN_TEST(test_tftp_receive_force_rrq_no_overrides)
2025RUN_TEST(test_tftp_receive_force_rrq_have_overrides)
2026END_TEST_CASE(tftp_receive_rrq)
2027
2028BEGIN_TEST_CASE(tftp_receive_oack)
2029RUN_TEST(test_tftp_receive_wrq_oack)
2030RUN_TEST(test_tftp_receive_wrq_oack_blocksize)
2031RUN_TEST(test_tftp_receive_wrq_oack_timeout)
2032RUN_TEST(test_tftp_receive_wrq_oack_windowsize)
2033RUN_TEST(test_tftp_receive_rrq_oack)
2034RUN_TEST(test_tftp_receive_rrq_oack_blocksize)
2035RUN_TEST(test_tftp_receive_rrq_oack_timeout)
2036RUN_TEST(test_tftp_receive_rrq_oack_windowsize)
2037RUN_TEST(test_tftp_receive_oack_overrides)
2038END_TEST_CASE(tftp_receive_oack)
2039
2040BEGIN_TEST_CASE(tftp_receive_data)
2041RUN_TEST(test_tftp_receive_data)
2042RUN_TEST(test_tftp_receive_data_final_block)
2043RUN_TEST(test_tftp_receive_data_blocksize)
2044RUN_TEST(test_tftp_receive_data_windowsize)
2045RUN_TEST(test_tftp_receive_data_skipped_block)
2046RUN_TEST(test_tftp_receive_data_windowsize_skipped_block)
2047RUN_TEST(test_tftp_receive_data_block_wrapping)
2048END_TEST_CASE(tftp_receive_data)
2049
2050BEGIN_TEST_CASE(tftp_send_data)
2051RUN_TEST(test_tftp_send_data_receive_ack)
2052RUN_TEST(test_tftp_send_data_receive_final_ack)
2053RUN_TEST(test_tftp_send_data_receive_ack_skipped_block)
2054RUN_TEST(test_tftp_send_data_receive_ack_window_size)
2055RUN_TEST(test_tftp_send_data_receive_ack_block_wrapping)
2056RUN_TEST(test_tftp_send_data_receive_ack_skip_block_wrap)
2057END_TEST_CASE(tftp_send_data)
2058
2059BEGIN_TEST_CASE(tftp_send_err)
2060RUN_TEST(test_tftp_open_read_should_wait)
2061RUN_TEST(test_tftp_open_write_should_wait)
2062END_TEST_CASE(tftp_send_err)
2063
2064BEGIN_TEST_CASE(tftp_recv_err)
2065RUN_TEST(test_tftp_recv_busy_from_rrq)
2066RUN_TEST(test_tftp_recv_busy_from_wrq)
2067RUN_TEST(test_tftp_recv_other_err)
2068END_TEST_CASE(tftp_recv_err)
2069
2070int main(int argc, char* argv[]) {
2071    return unittest_run_all_tests(argc, argv) ? 0 : -1;
2072}
2073