| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <arpa/inet.h> |
| #include <assert.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <memory> |
| |
| #include <fbl/algorithm.h> |
| #include <tftp/tftp.h> |
| #include <unittest/unittest.h> |
| |
| // For inspecting session state |
| #include "internal.h" |
| |
| constexpr char kLocalFilename[] = "local-filename"; |
| constexpr char kRemoteFilename[] = "remote-filename"; |
| |
| struct test_state { |
| // Return value used by ulib/unittest |
| bool reset(size_t ssize, size_t msize, size_t osize) { |
| sess_size = ssize; |
| msg_size = msize; |
| out_size = osize; |
| sess_buf.reset(new uint8_t[sess_size]); |
| msg_data.reset(new uint8_t[msg_size]); |
| out_scratch.reset(new uint8_t[out_size]); |
| auto init_status = tftp_init(&session, sess_buf.get(), sess_size); |
| ASSERT_EQ(TFTP_NO_ERROR, init_status, "could not initialize tftp_session"); |
| data = reinterpret_cast<void*>(msg_data.get()); |
| out = reinterpret_cast<void*>(out_scratch.get()); |
| outlen = out_size; |
| return true; |
| } |
| |
| tftp_session* session = nullptr; |
| size_t sess_size = 0; |
| size_t msg_size = 0; |
| size_t out_size = 0; |
| std::unique_ptr<uint8_t[]> sess_buf; |
| std::unique_ptr<uint8_t[]> msg_data; |
| std::unique_ptr<uint8_t[]> out_scratch; |
| void* data = nullptr; |
| void* out = nullptr; |
| size_t outlen = 0; |
| uint32_t timeout = 0; |
| }; |
| |
| static bool test_tftp_init(void) { |
| BEGIN_TEST; |
| |
| uint8_t buf[1024]; |
| tftp_session* session; |
| int status = tftp_init(&session, nullptr, 4096); |
| EXPECT_LT(status, 0, "tftp_init should fail for NULL buffer"); |
| status = tftp_init(&session, buf, 4); |
| EXPECT_LT(status, 0, "tftp_init should fail for too small buffer"); |
| status = tftp_init(&session, buf, sizeof(buf)); |
| EXPECT_EQ(status, TFTP_NO_ERROR, "error creating tftp session"); |
| EXPECT_EQ(sizeof(tftp_session), tftp_sizeof_session(), ""); |
| ASSERT_GE(sizeof(buf), tftp_sizeof_session(), "need to update test for larger tftp_session size"); |
| status = tftp_init(&session, buf, tftp_sizeof_session()); |
| EXPECT_EQ(status, TFTP_NO_ERROR, "tftp_init failed on correctly sized buffer"); |
| |
| END_TEST; |
| } |
| |
| static bool test_tftp_session_options(void) { |
| BEGIN_TEST; |
| |
| test_state ts; |
| ts.reset(1024, 1024, 1500); |
| |
| auto open_read_fn = [](const char* filename, void* cookie) -> ssize_t { return 0; }; |
| auto open_write_fn = [](const char* filename, size_t size, void* cookie) -> tftp_status { |
| return 0; |
| }; |
| auto read_fn = [](void* data, size_t* len, off_t offset, void* cookie) -> tftp_status { |
| return 0; |
| }; |
| auto write_fn = [](const void* data, size_t* len, off_t offset, void* cookie) -> tftp_status { |
| return 0; |
| }; |
| auto close_fn = [](void* cookie) { return; }; |
| tftp_file_interface ifc = {open_read_fn, open_write_fn, read_fn, write_fn, close_fn}; |
| auto status = tftp_session_set_file_interface(ts.session, &ifc); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "could not set file callbacks"); |
| EXPECT_EQ((tftp_file_open_read_cb)open_read_fn, ts.session->file_interface.open_read, |
| "bad open (read) function pointer"); |
| EXPECT_EQ((tftp_file_open_write_cb)open_write_fn, ts.session->file_interface.open_write, |
| "bad open (write) function pointer"); |
| EXPECT_EQ((tftp_file_read_cb)read_fn, ts.session->file_interface.read, |
| "bad read function pointer"); |
| EXPECT_EQ((tftp_file_write_cb)write_fn, ts.session->file_interface.write, |
| "bad write function pointer"); |
| EXPECT_EQ((tftp_file_close_cb)close_fn, ts.session->file_interface.close, |
| "bad write function pointer"); |
| |
| END_TEST; |
| } |
| |
| bool verify_write_request(const test_state& ts) { |
| auto msg = reinterpret_cast<tftp_msg*>(ts.out); |
| EXPECT_EQ(msg->opcode, htons(OPCODE_WRQ), "opcode should be 2 (WRQ)"); |
| EXPECT_STR_EQ(kRemoteFilename, msg->data, "bad filename"); |
| return true; |
| } |
| |
| bool verify_read_request(const test_state& ts) { |
| auto msg = reinterpret_cast<tftp_msg*>(ts.out); |
| EXPECT_EQ(msg->opcode, htons(OPCODE_RRQ), "opcode should be 1 (RRQ)"); |
| EXPECT_STR_EQ(kRemoteFilename, msg->data, "bad filename"); |
| return true; |
| } |
| |
| // Find a string (which may include NULL characters) inside a memory region. |
| static bool find_str_in_mem(const char* str, size_t str_len, const char* mem, size_t mem_len) { |
| while (str_len <= mem_len) { |
| if (!memcmp(str, mem, str_len)) { |
| return true; |
| } |
| mem_len--; |
| mem++; |
| } |
| return false; |
| } |
| |
| static bool test_tftp_generate_request(tftp_file_direction dir, size_t file_size, |
| const uint16_t* block_size, const uint8_t* timeout, |
| const uint16_t* window_size, |
| const uint16_t* block_size_override, |
| const uint8_t* timeout_override, |
| const uint16_t* window_size_override) { |
| BEGIN_TEST; |
| |
| tftp_status status; |
| test_state ts; |
| ts.reset(1024, file_size, 1500); |
| |
| // Test TFTP state, but not internal session state |
| status = tftp_set_options(ts.session, block_size, timeout, window_size); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "error setting session options"); |
| if (block_size) { |
| EXPECT_EQ(*block_size, ts.session->options.block_size, "bad session options: block size"); |
| } |
| if (timeout) { |
| EXPECT_EQ(*timeout, ts.session->options.timeout, "bad session options: timeout"); |
| } |
| if (window_size) { |
| EXPECT_EQ(*window_size, ts.session->options.window_size, "bad session options: window size"); |
| } |
| |
| size_t request_file_size = (dir == SEND_FILE) ? file_size : 0; |
| status = tftp_generate_request(ts.session, dir, kLocalFilename, kRemoteFilename, MODE_OCTET, |
| request_file_size, block_size_override, timeout_override, |
| window_size_override, ts.out, &ts.outlen, &ts.timeout); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "error generating request"); |
| if (dir == SEND_FILE) { |
| EXPECT_TRUE(verify_write_request(ts), "bad write request"); |
| } else { |
| EXPECT_TRUE(verify_read_request(ts), "bad read request"); |
| } |
| |
| EXPECT_EQ(REQ_SENT, ts.session->state, "bad session: state"); |
| if (dir == SEND_FILE) { |
| EXPECT_EQ(file_size, ts.session->file_size, "bad session: file size"); |
| } |
| EXPECT_EQ(DEFAULT_MODE, ts.session->mode, "bad session: mode"); |
| EXPECT_EQ(0, ts.session->offset, "bad session: offset"); |
| EXPECT_EQ(0, ts.session->block_number, "bad session: block number"); |
| |
| // Verify the options that were set in the request |
| const char* msg = static_cast<const char*>(ts.out); |
| char str[256]; |
| size_t str_sz; |
| |
| if (block_size_override) { |
| str_sz = snprintf(str, sizeof(str), "BLKSIZE!%c%d", '\0', *block_size_override) + 1; |
| EXPECT_TRUE(find_str_in_mem(str, str_sz, msg, ts.outlen), "block size not overridden"); |
| } else if (block_size) { |
| str_sz = snprintf(str, sizeof(str), "BLKSIZE%c%d", '\0', *block_size) + 1; |
| EXPECT_TRUE(find_str_in_mem(str, str_sz, msg, ts.outlen), "block size not properly requested"); |
| } else { |
| str_sz = snprintf(str, sizeof(str), "BLKSIZE") + 1; |
| EXPECT_FALSE(find_str_in_mem(str, str_sz, msg, ts.outlen), |
| "block size shouldn't appear in request"); |
| } |
| |
| if (timeout_override) { |
| str_sz = snprintf(str, sizeof(str), "TIMEOUT!%c%d", '\0', *timeout_override) + 1; |
| EXPECT_TRUE(find_str_in_mem(str, str_sz, msg, ts.outlen), "timeout not overridden"); |
| } else if (timeout) { |
| str_sz = snprintf(str, sizeof(str), "TIMEOUT%c%d", '\0', *timeout) + 1; |
| EXPECT_TRUE(find_str_in_mem(str, str_sz, msg, ts.outlen), "timeout not properly requested"); |
| } else { |
| str_sz = snprintf(str, sizeof(str), "TIMEOUT") + 1; |
| EXPECT_FALSE(find_str_in_mem(str, str_sz, msg, ts.outlen), |
| "timeout shouldn't appear in request"); |
| } |
| |
| if (window_size_override) { |
| str_sz = snprintf(str, sizeof(str), "WINDOWSIZE!%c%d", '\0', *window_size_override) + 1; |
| EXPECT_TRUE(find_str_in_mem(str, str_sz, msg, ts.outlen), "window size not overridden"); |
| } else if (window_size) { |
| str_sz = snprintf(str, sizeof(str), "WINDOWSIZE%c%d", '\0', *window_size) + 1; |
| EXPECT_TRUE(find_str_in_mem(str, str_sz, msg, ts.outlen), "window size not properly requested"); |
| } else { |
| str_sz = snprintf(str, sizeof(str), "WINDOWSIZE") + 1; |
| EXPECT_FALSE(find_str_in_mem(str, str_sz, msg, ts.outlen), |
| "window size shouldn't appear in request"); |
| } |
| |
| END_TEST; |
| } |
| |
| static bool test_tftp_generate_wrq_default(void) { |
| return test_tftp_generate_request(SEND_FILE, 1024, nullptr, nullptr, nullptr, nullptr, nullptr, |
| nullptr); |
| } |
| |
| static bool test_tftp_generate_wrq_options(void) { |
| constexpr uint16_t kBlockSize = 555; |
| constexpr uint8_t kTimeout = 3; |
| constexpr uint16_t kWindowSize = 44; |
| return test_tftp_generate_request(SEND_FILE, 1024, &kBlockSize, &kTimeout, &kWindowSize, nullptr, |
| nullptr, nullptr); |
| } |
| |
| static bool test_tftp_generate_wrq_override_blocksize(void) { |
| constexpr uint16_t kBlockSize = 1000; |
| return test_tftp_generate_request(SEND_FILE, 1024, nullptr, nullptr, nullptr, &kBlockSize, |
| nullptr, nullptr); |
| } |
| |
| static bool test_tftp_generate_wrq_override_timeout(void) { |
| uint8_t kTimeout = 60; |
| return test_tftp_generate_request(SEND_FILE, 1024, nullptr, nullptr, nullptr, nullptr, &kTimeout, |
| nullptr); |
| } |
| |
| static bool test_tftp_generate_wrq_override_windowsize(void) { |
| uint16_t kWindowSize = 32; |
| return test_tftp_generate_request(SEND_FILE, 1024, nullptr, nullptr, nullptr, nullptr, nullptr, |
| &kWindowSize); |
| } |
| |
| static bool test_tftp_generate_rrq_default(void) { |
| return test_tftp_generate_request(RECV_FILE, 1024, nullptr, nullptr, nullptr, nullptr, nullptr, |
| nullptr); |
| } |
| |
| static bool test_tftp_generate_rrq_options(void) { |
| constexpr uint16_t kBlockSize = 555; |
| constexpr uint8_t kTimeout = 3; |
| constexpr uint16_t kWindowSize = 44; |
| return test_tftp_generate_request(RECV_FILE, 1024, &kBlockSize, &kTimeout, &kWindowSize, nullptr, |
| nullptr, nullptr); |
| } |
| |
| static bool test_tftp_generate_rrq_override_blocksize(void) { |
| constexpr uint16_t kBlockSize = 1000; |
| return test_tftp_generate_request(RECV_FILE, 1024, nullptr, nullptr, nullptr, &kBlockSize, |
| nullptr, nullptr); |
| } |
| |
| static bool test_tftp_generate_rrq_override_timeout(void) { |
| uint8_t kTimeout = 60; |
| return test_tftp_generate_request(RECV_FILE, 1024, nullptr, nullptr, nullptr, nullptr, &kTimeout, |
| nullptr); |
| } |
| |
| static bool test_tftp_generate_rrq_override_windowsize(void) { |
| uint16_t kWindowSize = 32; |
| return test_tftp_generate_request(RECV_FILE, 1024, nullptr, nullptr, nullptr, nullptr, nullptr, |
| &kWindowSize); |
| } |
| |
| bool verify_response_opcode(const test_state& ts, uint16_t opcode) { |
| ASSERT_GT(ts.outlen, 0, "outlen must not be zero"); |
| auto msg = reinterpret_cast<tftp_msg*>(ts.out); |
| // The upper byte of the opcode is ignored |
| EXPECT_EQ(ntohs(msg->opcode) & 0xff, opcode, "bad opcode"); |
| return true; |
| } |
| |
| static bool test_tftp_receive_request_unexpected(tftp_file_direction dir) { |
| BEGIN_TEST; |
| |
| test_state ts; |
| ts.reset(1024, 1024, 1500); |
| |
| size_t req_file_size = (dir == SEND_FILE) ? ts.msg_size : 0; |
| auto status = |
| tftp_generate_request(ts.session, dir, kLocalFilename, kRemoteFilename, MODE_OCTET, |
| req_file_size, NULL, NULL, NULL, ts.out, &ts.outlen, &ts.timeout); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "could not generate request"); |
| if (dir == SEND_FILE) { |
| ASSERT_TRUE(verify_write_request(ts), "bad write request"); |
| } else { |
| ASSERT_TRUE(verify_read_request(ts), "bad read request"); |
| } |
| |
| ASSERT_LE(ts.outlen, 1500, "outlen too large"); |
| uint8_t buf[1500]; |
| memcpy(buf, ts.out, ts.outlen); |
| |
| // We are unprepared to service a request after we have sent one out |
| status = tftp_process_msg(ts.session, buf, sizeof(buf), ts.out, &ts.outlen, &ts.timeout, nullptr); |
| EXPECT_EQ(TFTP_ERR_BAD_STATE, status, "receive should fail"); |
| EXPECT_TRUE(verify_response_opcode(ts, OPCODE_ERROR), "bad error response"); |
| |
| END_TEST; |
| } |
| |
| static bool test_tftp_receive_wrq_unexpected(void) { |
| return test_tftp_receive_request_unexpected(SEND_FILE); |
| } |
| |
| static bool test_tftp_receive_rrq_unexpected(void) { |
| return test_tftp_receive_request_unexpected(RECV_FILE); |
| } |
| |
| static bool test_tftp_receive_request_too_large(tftp_file_direction dir) { |
| BEGIN_TEST; |
| |
| test_state ts; |
| ts.reset(1024, 1024, 1500); |
| |
| uint8_t buf[1024] = {0}; |
| buf[1] = (dir == SEND_FILE) ? OPCODE_WRQ : OPCODE_RRQ; |
| |
| auto status = |
| tftp_process_msg(ts.session, buf, sizeof(buf), ts.out, &ts.outlen, &ts.timeout, nullptr); |
| EXPECT_LT(status, 0, "receive should fail"); |
| EXPECT_TRUE(verify_response_opcode(ts, OPCODE_ERROR), "bad error response"); |
| |
| END_TEST; |
| } |
| |
| static bool test_tftp_receive_wrq_too_large(void) { |
| return test_tftp_receive_request_too_large(SEND_FILE); |
| } |
| |
| static bool test_tftp_receive_rrq_too_large(void) { |
| return test_tftp_receive_request_too_large(RECV_FILE); |
| } |
| |
| static bool test_tftp_receive_request_no_tsize(tftp_file_direction dir) { |
| BEGIN_TEST; |
| |
| test_state ts; |
| ts.reset(1024, 1024, 1500); |
| |
| char buf[256]; |
| buf[0] = 0x00; |
| buf[1] = (dir == SEND_FILE) ? OPCODE_WRQ : OPCODE_RRQ; |
| size_t buf_sz = 2 + snprintf(&buf[2], sizeof(buf) - 2, "%s%cOCTET", kRemoteFilename, '\0') + 1; |
| |
| ASSERT_LT(buf_sz, (int)sizeof(buf), "insufficient space for message"); |
| auto status = tftp_process_msg(ts.session, buf, buf_sz, ts.out, &ts.outlen, &ts.timeout, nullptr); |
| EXPECT_EQ(TFTP_ERR_BAD_STATE, status, "tftp session should fail"); |
| EXPECT_EQ(ERROR, ts.session->state, "tftp session in wrong state"); |
| EXPECT_EQ(0, ts.session->file_size, "tftp session bad file size"); |
| EXPECT_TRUE(verify_response_opcode(ts, OPCODE_ERROR), "bad error response"); |
| |
| END_TEST; |
| } |
| |
| static bool test_tftp_receive_wrq_no_tsize(void) { |
| return test_tftp_receive_request_no_tsize(SEND_FILE); |
| } |
| |
| static bool test_tftp_receive_rrq_no_tsize(void) { |
| return test_tftp_receive_request_no_tsize(RECV_FILE); |
| } |
| |
| static bool test_tftp_receive_request_send_oack(tftp_file_direction dir) { |
| BEGIN_TEST; |
| |
| test_state ts; |
| ts.reset(1024, 1024, 1500); |
| tftp_file_open_write_cb open_write_cb = [](const char* filename, size_t size, |
| void* cookie) -> tftp_status { |
| EXPECT_STR_EQ(filename, kRemoteFilename, "bad remote filename in open_write callback"); |
| EXPECT_EQ(size, 1024, "bad file size"); |
| return 0; |
| }; |
| tftp_file_open_read_cb open_read_cb = [](const char* filename, void* cookie) -> ssize_t { |
| EXPECT_STR_EQ(filename, kRemoteFilename, "bad remote filename in open_read callback"); |
| return 0; |
| }; |
| tftp_file_interface ifc = {open_read_cb, open_write_cb, NULL, NULL, NULL}; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| uint16_t default_block_size = 13; |
| uint8_t default_timeout = 2; |
| uint16_t default_window_size = 42; |
| tftp_set_options(ts.session, &default_block_size, &default_timeout, &default_window_size); |
| |
| size_t req_file_size = (dir == SEND_FILE) ? 1024 : 0; |
| char buf[256]; |
| buf[0] = 0x00; |
| buf[1] = (dir == SEND_FILE) ? OPCODE_WRQ : OPCODE_RRQ; |
| size_t buf_sz = 2 + |
| snprintf(&buf[2], sizeof(buf) - 2, "%s%cOCTET%cTSIZE%c%zu", kRemoteFilename, '\0', |
| '\0', '\0', req_file_size) + |
| 1; |
| |
| ASSERT_LT(buf_sz, (int)sizeof(buf), "insufficient space for WRQ message"); |
| auto status = tftp_process_msg(ts.session, buf, buf_sz, ts.out, &ts.outlen, &ts.timeout, nullptr); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "receive request failed"); |
| EXPECT_EQ(REQ_RECEIVED, ts.session->state, "tftp session in wrong state"); |
| EXPECT_EQ(req_file_size, ts.session->file_size, "tftp session bad file size"); |
| EXPECT_EQ(DEFAULT_BLOCKSIZE, ts.session->block_size, "bad session: block size"); |
| EXPECT_EQ(DEFAULT_TIMEOUT, ts.session->timeout, "bad session: timeout"); |
| EXPECT_EQ(DEFAULT_WINDOWSIZE, ts.session->window_size, "bad session: window size"); |
| EXPECT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response"); |
| |
| // The request was made from the client's perspective, so our state is the inverse |
| tftp_file_direction our_direction = (dir == SEND_FILE) ? RECV_FILE : SEND_FILE; |
| EXPECT_EQ(our_direction, ts.session->direction, "tftp session has bad direction"); |
| |
| const char* msg = static_cast<const char*>(ts.out); |
| const char win_sz_str[] = "WINDOWSIZE"; |
| EXPECT_FALSE(find_str_in_mem(win_sz_str, sizeof(win_sz_str), msg, ts.outlen), |
| "window size in oack, but not in request"); |
| const char timeout_str[] = "TIMEOUT"; |
| EXPECT_FALSE(find_str_in_mem(timeout_str, sizeof(timeout_str), msg, ts.outlen), |
| "timeout in oack, but not in request"); |
| const char block_sz_str[] = "BLKSIZE"; |
| EXPECT_FALSE(find_str_in_mem(block_sz_str, sizeof(block_sz_str), msg, ts.outlen), |
| "block size in oack, but not in request"); |
| |
| END_TEST; |
| } |
| |
| static bool test_tftp_receive_wrq_send_oack(void) { |
| return test_tftp_receive_request_send_oack(SEND_FILE); |
| } |
| |
| static bool test_tftp_receive_rrq_send_oack(void) { |
| return test_tftp_receive_request_send_oack(RECV_FILE); |
| } |
| |
| static ssize_t dummy_open_read(const char* filename, void* cookie) { return 1024; } |
| |
| static tftp_status dummy_open_write(const char* filename, size_t size, void* cookie) { return 0; } |
| |
| static bool test_tftp_receive_request_options( |
| tftp_file_direction dir, const uint16_t* server_block_size, const uint8_t* server_timeout, |
| const uint16_t* server_window_size, const uint16_t* client_block_size, |
| const uint8_t* client_timeout, const uint16_t* client_window_size, bool force_block_size, |
| bool force_timeout, bool force_window_size) { |
| BEGIN_TEST; |
| |
| test_state ts; |
| ts.reset(1024, 1024, 1500); |
| tftp_file_interface ifc = {dummy_open_read, dummy_open_write, NULL, NULL, NULL}; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| tftp_status status = |
| tftp_set_options(ts.session, server_block_size, server_timeout, server_window_size); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "failed to set server options"); |
| |
| size_t req_file_size = (dir == SEND_FILE) ? 1024 : 0; |
| char buf[256]; |
| buf[0] = 0x00; |
| buf[1] = (dir == SEND_FILE) ? OPCODE_WRQ : OPCODE_RRQ; |
| size_t buf_sz = 2; |
| buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz, "%s%cOCTET%cTSIZE%c%zu", kRemoteFilename, |
| '\0', '\0', '\0', req_file_size) + |
| 1; |
| if (client_block_size) { |
| buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz, "BLKSIZE%s%c%d", |
| force_block_size ? "!" : "", '\0', *client_block_size) + |
| 1; |
| } |
| if (client_timeout) { |
| buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz, "TIMEOUT%s%c%d", |
| force_timeout ? "!" : "", '\0', *client_timeout) + |
| 1; |
| } |
| if (client_window_size) { |
| buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz, "WINDOWSIZE%s%c%d", |
| force_window_size ? "!" : "", '\0', *client_window_size) + |
| 1; |
| } |
| |
| ASSERT_LT(buf_sz, (int)sizeof(buf), "insufficient space for request"); |
| status = tftp_process_msg(ts.session, buf, buf_sz, ts.out, &ts.outlen, &ts.timeout, nullptr); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "receive write request failed"); |
| EXPECT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response"); |
| |
| const char* msg = static_cast<const char*>(ts.out); |
| char opt_str[256]; |
| size_t opt_str_sz; |
| |
| if (client_block_size) { |
| uint16_t negotiated_block_size = |
| (server_block_size && !force_block_size) ? *server_block_size : *client_block_size; |
| opt_str_sz = snprintf(opt_str, sizeof(opt_str), "BLKSIZE%c%d", '\0', negotiated_block_size) + 1; |
| EXPECT_TRUE(find_str_in_mem(opt_str, opt_str_sz, msg, ts.outlen), |
| "block size not correct in oack"); |
| EXPECT_EQ(negotiated_block_size, ts.session->block_size, "bad session: block size"); |
| } else { |
| opt_str_sz = snprintf(opt_str, sizeof(opt_str), "BLKSIZE") + 1; |
| EXPECT_FALSE(find_str_in_mem(opt_str, opt_str_sz, msg, ts.outlen), |
| "block size appears in oack, but not in request"); |
| EXPECT_EQ(DEFAULT_BLOCKSIZE, ts.session->block_size, "bad session: block size"); |
| } |
| |
| if (client_timeout) { |
| uint8_t negotiated_timeout = |
| (server_timeout && !force_timeout) ? *server_timeout : *client_timeout; |
| opt_str_sz = snprintf(opt_str, sizeof(opt_str), "TIMEOUT%c%d", '\0', negotiated_timeout) + 1; |
| EXPECT_TRUE(find_str_in_mem(opt_str, opt_str_sz, msg, ts.outlen), |
| "timeout not correct in oack"); |
| EXPECT_EQ(negotiated_timeout, ts.session->timeout, "bad session: timeout"); |
| } else { |
| opt_str_sz = snprintf(opt_str, sizeof(opt_str), "TIMEOUT") + 1; |
| EXPECT_FALSE(find_str_in_mem(opt_str, opt_str_sz, msg, ts.outlen), |
| "timeout appears in oack, but not in request"); |
| EXPECT_EQ(DEFAULT_TIMEOUT, ts.session->timeout, "bad session: timeout"); |
| } |
| |
| if (client_window_size) { |
| uint16_t negotiated_window_size = |
| (server_window_size && !force_window_size) ? *server_window_size : *client_window_size; |
| opt_str_sz = |
| snprintf(opt_str, sizeof(opt_str), "WINDOWSIZE%c%d", '\0', negotiated_window_size) + 1; |
| EXPECT_TRUE(find_str_in_mem(opt_str, opt_str_sz, msg, ts.outlen), |
| "window size not correct in oack"); |
| EXPECT_EQ(negotiated_window_size, ts.session->window_size, "bad session: window size"); |
| } else { |
| opt_str_sz = snprintf(opt_str, sizeof(opt_str), "WINDOWSIZE") + 1; |
| EXPECT_FALSE(find_str_in_mem(opt_str, opt_str_sz, msg, ts.outlen), |
| "window size appears in oack, but not in request"); |
| EXPECT_EQ(DEFAULT_WINDOWSIZE, ts.session->window_size, "bad session: window size"); |
| } |
| |
| END_TEST; |
| } |
| |
| static bool test_tftp_receive_wrq_blocksize(void) { |
| constexpr uint8_t kDefaultTimeout = 4; |
| constexpr uint16_t kBlocksize = 1024; |
| return test_tftp_receive_request_options(SEND_FILE, nullptr, &kDefaultTimeout, nullptr, |
| &kBlocksize, nullptr, nullptr, false, false, false); |
| } |
| |
| static bool test_tftp_receive_wrq_timeout(void) { |
| constexpr uint16_t kDefaultBlocksize = 115; |
| constexpr uint8_t kTimeout = 3; |
| return test_tftp_receive_request_options(SEND_FILE, &kDefaultBlocksize, nullptr, nullptr, nullptr, |
| &kTimeout, nullptr, false, false, false); |
| } |
| |
| static bool test_tftp_receive_wrq_windowsize(void) { |
| constexpr uint16_t kWindowsize = 77; |
| return test_tftp_receive_request_options(SEND_FILE, nullptr, nullptr, nullptr, nullptr, nullptr, |
| &kWindowsize, false, false, false); |
| } |
| |
| // Verify that if override values are set, they supercede the values in a normal request |
| static bool test_tftp_receive_wrq_have_overrides(void) { |
| constexpr uint16_t kServerBlockSize = 302; |
| constexpr uint8_t kServerTimeout = 7; |
| constexpr uint16_t kServerWindowSize = 16; |
| constexpr uint16_t kClientBlockSize = 32; |
| constexpr uint8_t kClientTimeout = 9; |
| constexpr uint16_t kClientWindowSize = 143; |
| return test_tftp_receive_request_options(SEND_FILE, &kServerBlockSize, &kServerTimeout, |
| &kServerWindowSize, &kClientBlockSize, &kClientTimeout, |
| &kClientWindowSize, false, false, false); |
| } |
| |
| // Verify that if a WRQ has a '!' following an option it is honored, even if overrides are set |
| static bool test_tftp_receive_force_wrq_no_overrides(void) { |
| constexpr uint16_t kServerBlockSize = 302; |
| constexpr uint8_t kServerTimeout = 7; |
| constexpr uint16_t kServerWindowSize = 16; |
| constexpr uint16_t kClientBlockSize = 32; |
| constexpr uint8_t kClientTimeout = 9; |
| constexpr uint16_t kClientWindowSize = 143; |
| return test_tftp_receive_request_options(SEND_FILE, &kServerBlockSize, &kServerTimeout, |
| &kServerWindowSize, &kClientBlockSize, &kClientTimeout, |
| &kClientWindowSize, true, true, true); |
| } |
| |
| static bool test_tftp_receive_force_wrq_have_overrides(void) { |
| constexpr uint16_t kClientBlockSize = 32; |
| constexpr uint8_t kClientTimeout = 9; |
| constexpr uint16_t kClientWindowSize = 143; |
| return test_tftp_receive_request_options(SEND_FILE, nullptr, nullptr, nullptr, &kClientBlockSize, |
| &kClientTimeout, &kClientWindowSize, true, true, true); |
| } |
| |
| static bool test_tftp_receive_rrq_blocksize(void) { |
| constexpr uint8_t kDefaultTimeout = 4; |
| constexpr uint16_t kBlocksize = 1024; |
| return test_tftp_receive_request_options(RECV_FILE, nullptr, &kDefaultTimeout, nullptr, |
| &kBlocksize, nullptr, nullptr, false, false, false); |
| } |
| |
| static bool test_tftp_receive_rrq_timeout(void) { |
| constexpr uint16_t kDefaultBlocksize = 115; |
| constexpr uint8_t kTimeout = 3; |
| return test_tftp_receive_request_options(RECV_FILE, &kDefaultBlocksize, nullptr, nullptr, nullptr, |
| &kTimeout, nullptr, false, false, false); |
| } |
| |
| static bool test_tftp_receive_rrq_windowsize(void) { |
| constexpr uint16_t kWindowsize = 77; |
| return test_tftp_receive_request_options(RECV_FILE, nullptr, nullptr, nullptr, nullptr, nullptr, |
| &kWindowsize, false, false, false); |
| } |
| |
| // Verify that if override values are set, they supercede the values in a normal request |
| static bool test_tftp_receive_rrq_have_overrides(void) { |
| constexpr uint16_t kServerBlockSize = 302; |
| constexpr uint8_t kServerTimeout = 7; |
| constexpr uint16_t kServerWindowSize = 16; |
| constexpr uint16_t kClientBlockSize = 32; |
| constexpr uint8_t kClientTimeout = 9; |
| constexpr uint16_t kClientWindowSize = 143; |
| return test_tftp_receive_request_options(RECV_FILE, &kServerBlockSize, &kServerTimeout, |
| &kServerWindowSize, &kClientBlockSize, &kClientTimeout, |
| &kClientWindowSize, false, false, false); |
| } |
| |
| // Verify that if a WRQ has a '!' following an option it is honored, even if overrides are set |
| static bool test_tftp_receive_force_rrq_no_overrides(void) { |
| constexpr uint16_t kServerBlockSize = 302; |
| constexpr uint8_t kServerTimeout = 7; |
| constexpr uint16_t kServerWindowSize = 16; |
| constexpr uint16_t kClientBlockSize = 32; |
| constexpr uint8_t kClientTimeout = 9; |
| constexpr uint16_t kClientWindowSize = 143; |
| return test_tftp_receive_request_options(RECV_FILE, &kServerBlockSize, &kServerTimeout, |
| &kServerWindowSize, &kClientBlockSize, &kClientTimeout, |
| &kClientWindowSize, true, true, true); |
| } |
| |
| static bool test_tftp_receive_force_rrq_have_overrides(void) { |
| constexpr uint16_t kClientBlockSize = 32; |
| constexpr uint8_t kClientTimeout = 9; |
| constexpr uint16_t kClientWindowSize = 143; |
| return test_tftp_receive_request_options(RECV_FILE, nullptr, nullptr, nullptr, &kClientBlockSize, |
| &kClientTimeout, &kClientWindowSize, true, true, true); |
| } |
| |
| struct tx_test_data { |
| struct { |
| uint16_t block; |
| off_t offset; |
| size_t len; |
| uint8_t data[2048]; // for reads |
| } expected; |
| |
| struct { |
| // block is in the outgoing message buffer |
| off_t offset; |
| size_t len; |
| uint8_t data[2048]; // for writes |
| } actual; |
| |
| tx_test_data() { |
| expected.block = 1; |
| expected.offset = 0; |
| expected.len = DEFAULT_BLOCKSIZE; |
| expected.data[0] = 'F'; |
| expected.data[DEFAULT_BLOCKSIZE - 1] = 'X'; |
| actual.offset = -1; |
| actual.len = -1; |
| memset(actual.data, 0, sizeof(actual.data)); |
| } |
| }; |
| |
| bool verify_read_data(const test_state& ts, const tx_test_data& td) { |
| BEGIN_HELPER; |
| EXPECT_EQ(td.expected.offset, td.actual.offset, "read offset mismatch"); |
| EXPECT_EQ(td.expected.len, td.actual.len, "read length mismatch"); |
| auto msg = static_cast<tftp_data_msg*>(ts.out); |
| // The upper byte of the opcode is ignored |
| EXPECT_EQ(OPCODE_DATA, ntohs(msg->opcode) & 0xff, "bad opcode"); |
| // Don't continue if we have a bad block number or we risk OOB reads |
| ASSERT_EQ(td.expected.block, ntohs(msg->block), "bad block number"); |
| EXPECT_BYTES_EQ(td.expected.data, msg->data, td.actual.len, "read data mismatch"); |
| END_HELPER; |
| } |
| |
| tftp_status mock_open_write(const char* filename, size_t size, void* file_cookie) { |
| return TFTP_NO_ERROR; |
| } |
| |
| tftp_status mock_read(void* data, size_t* len, off_t offset, void* cookie) { |
| tx_test_data* td = static_cast<tx_test_data*>(cookie); |
| td->actual.len = *len; |
| td->actual.offset = offset; |
| memcpy(data, td->expected.data, *len); |
| return static_cast<tftp_status>(*len); |
| } |
| |
| static bool test_tftp_receive_wrq_oack(const size_t file_size, const uint16_t* block_size_ptr, |
| const uint8_t* timeout_ptr, |
| const uint16_t* window_size_ptr) { |
| BEGIN_TEST; |
| |
| uint16_t block_size = block_size_ptr ? *block_size_ptr : DEFAULT_BLOCKSIZE; |
| uint8_t timeout = timeout_ptr ? *timeout_ptr : DEFAULT_TIMEOUT; |
| uint16_t window_size = window_size_ptr ? *window_size_ptr : DEFAULT_WINDOWSIZE; |
| |
| test_state ts; |
| ts.reset(1024, file_size, 1500); |
| |
| auto status = tftp_generate_request(ts.session, SEND_FILE, kLocalFilename, kRemoteFilename, |
| MODE_OCTET, ts.msg_size, block_size_ptr, timeout_ptr, |
| window_size_ptr, ts.out, &ts.outlen, &ts.timeout); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "error generating write request"); |
| ASSERT_TRUE(verify_write_request(ts), "bad write request"); |
| |
| char buf[256]; |
| buf[0] = 0x00; |
| buf[1] = 0x06; // Opcode (OACK) |
| size_t buf_sz = 2; |
| buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz, "%s%cOCTET%cTSIZE%c%zu", kRemoteFilename, |
| '\0', '\0', '\0', file_size) + |
| 1; |
| if (block_size_ptr) { |
| buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz, "BLKSIZE%c%d", '\0', block_size) + 1; |
| } |
| if (timeout_ptr) { |
| buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz, "TIMEOUT%c%d", '\0', timeout) + 1; |
| } |
| if (window_size_ptr) { |
| buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz, "WINDOWSIZE%c%d", '\0', window_size) + 1; |
| } |
| |
| tftp_file_interface ifc = {NULL, NULL, mock_read, NULL, NULL}; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| tx_test_data td; |
| td.expected.len = block_size; |
| td.expected.data[block_size - 1] = 'X'; |
| status = tftp_process_msg(ts.session, buf, buf_sz, ts.out, &ts.outlen, &ts.timeout, &td); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "tftp_process_msg failed"); |
| bool has_pending = (block_size < file_size) && (window_size > 1); |
| EXPECT_EQ(has_pending, tftp_session_has_pending(ts.session), |
| "Unexpected tftp_session_has_pending()"); |
| EXPECT_EQ(FIRST_DATA, ts.session->state, "session should be in state FIRST_DATA"); |
| EXPECT_EQ(file_size, ts.session->file_size, "tftp session bad file size"); |
| EXPECT_EQ(block_size, ts.session->block_size, "bad session: block size"); |
| EXPECT_EQ(timeout, ts.session->timeout, "bad session: timeout"); |
| EXPECT_EQ(window_size, ts.session->window_size, "bad session: window size"); |
| |
| EXPECT_EQ(ts.outlen, sizeof(tftp_data_msg) + block_size, "bad outlen"); |
| EXPECT_EQ(ts.timeout, timeout * 1000U, "timeout should be set"); |
| EXPECT_TRUE(verify_read_data(ts, td), "bad test data"); |
| |
| if (has_pending) { |
| // Since pending is true, call for a second data packet to transmit |
| // Updated the read offset and change a few bytes |
| td.expected.block = 2; |
| size_t second_block_size = fbl::min<size_t>(file_size - block_size, block_size); |
| td.expected.offset = block_size; |
| td.expected.data[0] = 'X'; |
| if (second_block_size > 0) { |
| td.expected.data[second_block_size - 1] = 'F'; |
| } |
| |
| status = tftp_prepare_data(ts.session, ts.out, &ts.outlen, &ts.timeout, &td); |
| EXPECT_EQ(ts.outlen, sizeof(tftp_data_msg) + block_size, "bad outlen"); |
| EXPECT_TRUE(verify_read_data(ts, td), "bad test data"); |
| has_pending = (block_size + second_block_size < file_size) && (window_size > 2); |
| EXPECT_EQ(has_pending, tftp_session_has_pending(ts.session), |
| "Unexpected tftp_session_has_pending()"); |
| } |
| END_TEST; |
| } |
| |
| static bool test_tftp_receive_wrq_oack(void) { |
| return test_tftp_receive_wrq_oack(1024, NULL, NULL, NULL); |
| } |
| |
| static bool test_tftp_receive_wrq_oack_blocksize(void) { |
| constexpr uint16_t kBlockSize = 1024; |
| return test_tftp_receive_wrq_oack(2048, &kBlockSize, nullptr, nullptr); |
| } |
| |
| static bool test_tftp_receive_wrq_oack_timeout(void) { |
| constexpr uint8_t kTimeout = 5; |
| return test_tftp_receive_wrq_oack(1024, nullptr, &kTimeout, nullptr); |
| } |
| |
| static bool test_tftp_receive_wrq_oack_windowsize(void) { |
| constexpr uint16_t kWindowSize = 2; |
| return test_tftp_receive_wrq_oack(4096, nullptr, nullptr, &kWindowSize); |
| } |
| |
| static bool test_tftp_receive_rrq_oack(size_t file_size, const uint16_t* block_size_ptr, |
| const uint8_t* timeout_ptr, |
| const uint16_t* window_size_ptr) { |
| BEGIN_TEST; |
| |
| uint16_t block_size = block_size_ptr ? *block_size_ptr : DEFAULT_BLOCKSIZE; |
| uint8_t timeout = timeout_ptr ? *timeout_ptr : DEFAULT_TIMEOUT; |
| uint16_t window_size = window_size_ptr ? *window_size_ptr : DEFAULT_WINDOWSIZE; |
| |
| test_state ts; |
| ts.reset(1024, file_size, 1500); |
| |
| auto status = tftp_generate_request(ts.session, RECV_FILE, kLocalFilename, kRemoteFilename, |
| MODE_OCTET, 0, block_size_ptr, timeout_ptr, window_size_ptr, |
| ts.out, &ts.outlen, &ts.timeout); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "error generating read request"); |
| ASSERT_TRUE(verify_read_request(ts), "bad read request"); |
| |
| char buf[256]; |
| buf[0] = 0x00; |
| buf[1] = 0x06; // Opcode (OACK) |
| size_t buf_sz = 2; |
| buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz, "TSIZE%c%zu", '\0', file_size) + 1; |
| if (block_size_ptr) { |
| buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz, "BLKSIZE%c%d", '\0', block_size) + 1; |
| } |
| if (timeout_ptr) { |
| buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz, "TIMEOUT%c%d", '\0', timeout) + 1; |
| } |
| if (window_size_ptr) { |
| buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz, "WINDOWSIZE%c%d", '\0', window_size) + 1; |
| } |
| tftp_file_interface ifc = {NULL, mock_open_write, NULL, NULL, NULL}; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| status = tftp_process_msg(ts.session, buf, buf_sz, ts.out, &ts.outlen, &ts.timeout, NULL); |
| EXPECT_FALSE(tftp_session_has_pending(ts.session), "session should not have pending data"); |
| EXPECT_EQ(FIRST_DATA, ts.session->state, "session should be in state FIRST_DATA"); |
| EXPECT_EQ(file_size, ts.session->file_size, "tftp session bad file size"); |
| EXPECT_EQ(block_size, ts.session->block_size, "bad session: block size"); |
| EXPECT_EQ(timeout, ts.session->timeout, "bad session: timeout"); |
| EXPECT_EQ(window_size, ts.session->window_size, "bad session: window size"); |
| |
| uint8_t expected_ack[] = { |
| 0x00, 0x04, // Opcode (ACK) |
| 0x00, 0x00 // Block |
| }; |
| EXPECT_EQ(sizeof(expected_ack), ts.outlen, "response size mismatch"); |
| EXPECT_EQ(0, memcmp(expected_ack, ts.out, sizeof(expected_ack)), "bad response"); |
| END_TEST; |
| } |
| |
| static bool test_tftp_receive_rrq_oack(void) { |
| return test_tftp_receive_rrq_oack(1024, nullptr, nullptr, nullptr); |
| } |
| |
| static bool test_tftp_receive_rrq_oack_blocksize(void) { |
| constexpr uint16_t kBlockSize = 1024; |
| return test_tftp_receive_rrq_oack(2048, &kBlockSize, nullptr, nullptr); |
| } |
| |
| static bool test_tftp_receive_rrq_oack_timeout(void) { |
| constexpr uint8_t kTimeout = 5; |
| return test_tftp_receive_rrq_oack(1024, nullptr, &kTimeout, nullptr); |
| } |
| |
| static bool test_tftp_receive_rrq_oack_windowsize(void) { |
| constexpr uint16_t kWindowSize = 412; |
| return test_tftp_receive_rrq_oack(1024, nullptr, nullptr, &kWindowSize); |
| } |
| |
| // Verify that if the server overrides our settings we use the oack'd settings it provides |
| static bool test_tftp_receive_oack_overrides(void) { |
| BEGIN_TEST; |
| |
| test_state ts; |
| ts.reset(1024, 4096, 1500); |
| |
| uint16_t kBlockSize = 14; |
| uint8_t kTimeout = 12; |
| uint16_t kWindowSize = 6; |
| |
| auto status = tftp_generate_request(ts.session, SEND_FILE, kLocalFilename, kRemoteFilename, |
| MODE_OCTET, ts.msg_size, &kBlockSize, &kTimeout, &kWindowSize, |
| ts.out, &ts.outlen, &ts.timeout); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "error generating write request"); |
| |
| uint8_t buf[] = { |
| 0x00, 0x06, // Opcode (OACK) |
| 'T', 'S', 'I', 'Z', 'E', 0x00, // Option |
| '4', '0', '9', '6', 0x00, // TSIZE value |
| 'B', 'L', 'K', 'S', 'I', 'Z', 'E', 0x00, // Option |
| '5', '5', 0x00, // BLKSIZE value |
| 'T', 'I', 'M', 'E', 'O', 'U', 'T', 0x00, // Option |
| '3', 0x00, // TIMEOUT value |
| 'W', 'I', 'N', 'D', 'O', 'W', 'S', 'I', 'Z', 'E', 0x00, // Option |
| '2', '1', '4', 0x00, // WINDOWSIZE value |
| }; |
| |
| tftp_file_interface ifc = {NULL, NULL, mock_read, NULL, NULL}; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| tx_test_data td; |
| status = tftp_process_msg(ts.session, buf, sizeof(buf), ts.out, &ts.outlen, &ts.timeout, &td); |
| EXPECT_EQ(FIRST_DATA, ts.session->state, "session should be in state FIRST_DATA"); |
| EXPECT_EQ(4096, ts.session->file_size, "tftp session bad file size"); |
| EXPECT_EQ(55, ts.session->block_size, "bad session: block size"); |
| EXPECT_EQ(3, ts.session->timeout, "bad session: timeout"); |
| EXPECT_EQ(214, ts.session->window_size, "bad session: window size"); |
| EXPECT_EQ(ts.outlen, sizeof(tftp_data_msg) + ts.session->block_size, "bad outlen"); |
| |
| END_TEST; |
| } |
| |
| tftp_status mock_write(const void* data, size_t* len, off_t offset, void* cookie) { |
| tx_test_data* td = static_cast<tx_test_data*>(cookie); |
| td->actual.len = *len; |
| td->actual.offset = offset; |
| memcpy(td->actual.data + offset, data, *len); |
| return static_cast<tftp_status>(*len); |
| } |
| |
| bool verify_write_data(const uint8_t* expected, const tx_test_data& td) { |
| BEGIN_HELPER; |
| ASSERT_EQ(td.expected.offset, td.actual.offset, "write offset mismatch"); |
| ASSERT_EQ(td.expected.len, td.actual.len, "write length mismatch"); |
| EXPECT_BYTES_EQ(expected, td.actual.data + td.actual.offset, td.actual.len, |
| "write data mismatch"); |
| END_HELPER; |
| } |
| |
| static bool test_tftp_receive_data(void) { |
| BEGIN_TEST; |
| |
| test_state ts; |
| ts.reset(1024, 1024, 1500); |
| tftp_file_interface ifc = {NULL, |
| [](const char* filename, size_t size, void* cookie) -> tftp_status { |
| EXPECT_STR_EQ(filename, kRemoteFilename, "bad filename"); |
| EXPECT_EQ(size, 1024, "bad file size"); |
| return 0; |
| }, |
| NULL, NULL, NULL}; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| char req_buf[256]; |
| req_buf[0] = 0x00; |
| req_buf[1] = OPCODE_WRQ; |
| size_t req_buf_sz = 2 + |
| snprintf(&req_buf[2], sizeof(req_buf) - 2, "%s%cOCTET%cTSIZE%c%d", |
| kRemoteFilename, '\0', '\0', '\0', 1024) + |
| 1; |
| |
| ASSERT_LT(req_buf_sz, (int)sizeof(req_buf), "insufficient space for WRQ message"); |
| auto status = |
| tftp_process_msg(ts.session, req_buf, req_buf_sz, ts.out, &ts.outlen, &ts.timeout, nullptr); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "receive write request failed"); |
| ASSERT_EQ(REQ_RECEIVED, ts.session->state, "tftp session in wrong state"); |
| ASSERT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response"); |
| |
| uint8_t data_buf[516] = { |
| 0x00, 0x03, // Opcode (DATA) |
| 0x00, 0x01, // Block |
| 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, // Data; compiler will fill out the rest with zeros |
| }; |
| data_buf[515] = 0x79; // set the last byte to make sure it all gets copied |
| |
| ifc.write = mock_write; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| tx_test_data td; |
| status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, |
| &td); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "receive data failed"); |
| EXPECT_TRUE(verify_response_opcode(ts, OPCODE_ACK), "bad response"); |
| EXPECT_TRUE(verify_write_data(data_buf + 4, td), "bad write data"); |
| EXPECT_EQ(1, ts.session->block_number, "tftp session block number mismatch"); |
| EXPECT_EQ(0, ts.session->window_index, "tftp session window index mismatch"); |
| |
| END_TEST; |
| } |
| |
| static bool test_tftp_receive_data_final_block(void) { |
| BEGIN_TEST; |
| |
| test_state ts; |
| ts.reset(1024, 1024, 1500); |
| tftp_file_interface ifc = {NULL, |
| [](const char* filename, size_t size, void* cookie) -> tftp_status { |
| EXPECT_STR_EQ(filename, kRemoteFilename, "bad filename"); |
| EXPECT_EQ(size, 1024, "bad file size"); |
| return 0; |
| }, |
| NULL, NULL, NULL}; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| char req_buf[256]; |
| req_buf[0] = 0x00; |
| req_buf[1] = OPCODE_WRQ; |
| size_t req_buf_sz = 2 + |
| snprintf(&req_buf[2], sizeof(req_buf) - 2, "%s%cOCTET%cTSIZE%c%d", |
| kRemoteFilename, '\0', '\0', '\0', 1024) + |
| 1; |
| |
| ASSERT_LT(req_buf_sz, (int)sizeof(req_buf), "insufficient space for WRQ message"); |
| auto status = |
| tftp_process_msg(ts.session, req_buf, req_buf_sz, ts.out, &ts.outlen, &ts.timeout, nullptr); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "receive write request failed"); |
| ASSERT_EQ(REQ_RECEIVED, ts.session->state, "tftp session in wrong state"); |
| ASSERT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response"); |
| |
| uint8_t data_buf[516] = { |
| 0x00, 0x03, // Opcode (DATA) |
| 0x00, 0x01, // Block |
| 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, // Data; compiler will fill out the rest with zeros |
| }; |
| data_buf[515] = 0x79; // set the last byte to make sure it all gets copied |
| |
| ifc.write = mock_write; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| tx_test_data td; |
| status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, |
| &td); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "receive data failed"); |
| ASSERT_TRUE(verify_response_opcode(ts, OPCODE_ACK), "bad response"); |
| ASSERT_TRUE(verify_write_data(data_buf + 4, td), "bad write data"); |
| |
| // Update block number and first/last bytes of the data packet |
| data_buf[3]++; |
| data_buf[4]++; |
| data_buf[515]++; |
| td.expected.block++; |
| td.expected.offset = DEFAULT_BLOCKSIZE; |
| |
| status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, |
| &td); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "receive data failed"); |
| ASSERT_TRUE(verify_response_opcode(ts, OPCODE_ACK), "bad response"); |
| EXPECT_TRUE(verify_write_data(data_buf + 4, td), "bad write data"); |
| |
| // Last data packet. Empty, indicating end of data. |
| data_buf[3]++; |
| status = tftp_process_msg(ts.session, data_buf, 4, ts.out, &ts.outlen, &ts.timeout, nullptr); |
| EXPECT_EQ(TFTP_TRANSFER_COMPLETED, status, "receive data failed"); |
| ASSERT_TRUE(verify_response_opcode(ts, OPCODE_ACK), "bad response"); |
| END_TEST; |
| } |
| |
| static bool test_tftp_receive_data_blocksize(void) { |
| BEGIN_TEST; |
| |
| test_state ts; |
| ts.reset(1024, 2048, 1500); |
| tftp_file_interface ifc = {NULL, |
| [](const char* filename, size_t size, void* cookie) -> tftp_status { |
| EXPECT_STR_EQ(filename, kRemoteFilename, "bad filename"); |
| EXPECT_EQ(size, 2048, "bad file size"); |
| return 0; |
| }, |
| NULL, NULL, NULL}; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| char req_buf[256]; |
| req_buf[0] = 0x00; |
| req_buf[1] = OPCODE_WRQ; |
| size_t req_buf_sz = |
| 2 + |
| snprintf(&req_buf[2], sizeof(req_buf) - 2, "%s%cOCTET%cTSIZE%c%d%cBLKSIZE%c%d", |
| kRemoteFilename, '\0', '\0', '\0', 2048, '\0', '\0', 1024) + |
| 1; |
| |
| ASSERT_LT(req_buf_sz, (int)sizeof(req_buf), "insufficient space for WRQ message"); |
| auto status = |
| tftp_process_msg(ts.session, req_buf, req_buf_sz, ts.out, &ts.outlen, &ts.timeout, nullptr); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "receive write request failed"); |
| ASSERT_EQ(REQ_RECEIVED, ts.session->state, "tftp session in wrong state"); |
| ASSERT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response"); |
| |
| uint8_t data_buf[1028] = { |
| 0x00, 0x03, // Opcode (DATA) |
| 0x00, 0x01, // Block |
| 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, // Data; compiler will fill out the rest with zeros |
| }; |
| data_buf[1027] = 0x79; // set the last byte to make sure it all gets copied |
| |
| ifc.write = mock_write; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| tx_test_data td; |
| td.expected.len = 1024; |
| status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, |
| &td); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "receive data failed"); |
| EXPECT_TRUE(verify_response_opcode(ts, OPCODE_ACK), "bad response"); |
| EXPECT_TRUE(verify_write_data(data_buf + 4, td), "bad write data"); |
| EXPECT_EQ(1, ts.session->block_number, "tftp session block number mismatch"); |
| EXPECT_EQ(0, ts.session->window_index, "tftp session window index mismatch"); |
| |
| END_TEST; |
| } |
| |
| static bool test_tftp_receive_data_windowsize(void) { |
| BEGIN_TEST; |
| |
| test_state ts; |
| ts.reset(1024, 1025, 1500); |
| tftp_file_interface ifc = {NULL, |
| [](const char* filename, size_t size, void* cookie) -> tftp_status { |
| EXPECT_STR_EQ(filename, kRemoteFilename, "bad filename"); |
| EXPECT_EQ(size, 1025, "bad file size"); |
| return 0; |
| }, |
| NULL, NULL, NULL}; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| char req_buf[256]; |
| req_buf[0] = 0x00; |
| req_buf[1] = OPCODE_WRQ; |
| size_t req_buf_sz = |
| 2 + |
| snprintf(&req_buf[2], sizeof(req_buf) - 2, "%s%cOCTET%cTSIZE%c%d%cWINDOWSIZE%c%d", |
| kRemoteFilename, '\0', '\0', '\0', 1025, '\0', '\0', 2) + |
| 1; |
| |
| ASSERT_LT(req_buf_sz, (int)sizeof(req_buf), "insufficient space for WRQ message"); |
| auto status = |
| tftp_process_msg(ts.session, req_buf, req_buf_sz, ts.out, &ts.outlen, &ts.timeout, nullptr); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "receive write request failed"); |
| ASSERT_EQ(REQ_RECEIVED, ts.session->state, "tftp session in wrong state"); |
| ASSERT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response"); |
| |
| uint8_t data_buf[516] = { |
| 0x00, 0x03, // Opcode (DATA) |
| 0x00, 0x01, // Block |
| 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, // Data; compiler will fill out the rest with zeros |
| }; |
| data_buf[515] = 0x79; // set the last byte to make sure it all gets copied |
| |
| ifc.write = mock_write; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| tx_test_data td; |
| status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, |
| &td); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "receive data failed"); |
| EXPECT_EQ(0, ts.outlen, "no response expected"); |
| EXPECT_TRUE(verify_write_data(data_buf + 4, td), "bad write data"); |
| EXPECT_EQ(1, ts.session->block_number, "tftp session block number mismatch"); |
| EXPECT_EQ(1, ts.session->window_index, "tftp session window index mismatch"); |
| |
| // Update block number and first/last bytes of the data packet |
| data_buf[3]++; |
| data_buf[4]++; |
| data_buf[515]++; |
| td.expected.block++; |
| td.expected.offset += DEFAULT_BLOCKSIZE; |
| |
| status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, |
| &td); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "receive data failed"); |
| EXPECT_TRUE(verify_response_opcode(ts, OPCODE_ACK), "bad response"); |
| EXPECT_TRUE(verify_write_data(data_buf + 4, td), "bad write data"); |
| EXPECT_EQ(2, ts.session->block_number, "tftp session block number mismatch"); |
| EXPECT_EQ(0, ts.session->window_index, "tftp session window index mismatch"); |
| |
| END_TEST; |
| } |
| |
| static bool test_tftp_receive_data_skipped_block(void) { |
| BEGIN_TEST; |
| |
| test_state ts; |
| ts.reset(1024, 1024, 1500); |
| tftp_file_interface ifc = {NULL, |
| [](const char* filename, size_t size, void* cookie) -> tftp_status { |
| EXPECT_STR_EQ(filename, kRemoteFilename, "bad filename"); |
| EXPECT_EQ(size, 1024, "bad file size"); |
| return 0; |
| }, |
| NULL, NULL, NULL}; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| char req_buf[256]; |
| req_buf[0] = 0x00; |
| req_buf[1] = OPCODE_WRQ; |
| size_t req_buf_sz = 2 + |
| snprintf(&req_buf[2], sizeof(req_buf) - 2, "%s%cOCTET%cTSIZE%c%d", |
| kRemoteFilename, '\0', '\0', '\0', 1024) + |
| 1; |
| |
| ASSERT_LT(req_buf_sz, (int)sizeof(req_buf), "insufficient space for WRQ message"); |
| auto status = |
| tftp_process_msg(ts.session, req_buf, req_buf_sz, ts.out, &ts.outlen, &ts.timeout, nullptr); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "receive write request failed"); |
| ASSERT_EQ(REQ_RECEIVED, ts.session->state, "tftp session in wrong state"); |
| ASSERT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response"); |
| |
| // This is block 2, meaning we missed block 1 somehow. |
| uint8_t data_buf[516] = { |
| 0x00, 0x03, // Opcode (DATA) |
| 0x00, 0x02, // Block |
| 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, // Data; compiler will fill out the rest with zeros |
| }; |
| data_buf[515] = 0x79; // set the last byte to make sure it all gets copied |
| |
| ifc.write = mock_write; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, |
| nullptr); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "receive data failed"); |
| ASSERT_GT(ts.outlen, 0, "outlen must not be zero"); |
| auto msg = reinterpret_cast<tftp_data_msg*>(ts.out); |
| EXPECT_EQ(ntohs(msg->opcode) & 0xff, OPCODE_ACK, "bad opcode"); |
| // The opcode prefix should have been advanced when we saw a dropped block |
| EXPECT_EQ((ntohs(msg->opcode) & 0xff00) >> 8, 1, "bad opcode prefix"); |
| EXPECT_EQ(ntohs(msg->block), 0, "bad block number"); |
| EXPECT_EQ(0, ts.session->block_number, "tftp session block number mismatch"); |
| EXPECT_EQ(0, ts.session->window_index, "tftp session window index mismatch"); |
| |
| // Verify with the opcode prefix disabled |
| tftp_session_set_opcode_prefix_use(ts.session, false); |
| status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, |
| nullptr); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "receive data failed"); |
| ASSERT_GT(ts.outlen, 0, "outlen must not be zero"); |
| msg = reinterpret_cast<tftp_data_msg*>(ts.out); |
| EXPECT_EQ(ntohs(msg->opcode) & 0xff, OPCODE_ACK, "bad opcode"); |
| EXPECT_EQ((ntohs(msg->opcode) & 0xff00) >> 8, 0, "bad opcode prefix"); |
| EXPECT_EQ(ntohs(msg->block), 0, "bad block number"); |
| |
| END_TEST; |
| } |
| |
| static bool test_tftp_receive_data_windowsize_skipped_block(void) { |
| BEGIN_TEST; |
| |
| test_state ts; |
| ts.reset(1024, 2048, 1500); |
| tftp_file_interface ifc = {NULL, |
| [](const char* filename, size_t size, void* cookie) -> tftp_status { |
| EXPECT_STR_EQ(filename, kRemoteFilename, "bad filename"); |
| EXPECT_EQ(size, 2048, "bad file size"); |
| return 0; |
| }, |
| NULL, NULL, NULL}; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| char req_buf[256]; |
| req_buf[0] = 0x00; |
| req_buf[1] = OPCODE_WRQ; |
| size_t req_buf_sz = |
| 2 + |
| snprintf(&req_buf[2], sizeof(req_buf) - 2, "%s%cOCTET%cTSIZE%c%d%cWINDOWSIZE%c%d", |
| kRemoteFilename, '\0', '\0', '\0', 2048, '\0', '\0', 3) + |
| 1; |
| |
| ASSERT_LT(req_buf_sz, (int)sizeof(req_buf), "insufficient space for WRQ message"); |
| auto status = |
| tftp_process_msg(ts.session, req_buf, req_buf_sz, ts.out, &ts.outlen, &ts.timeout, nullptr); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "receive write request failed"); |
| ASSERT_EQ(REQ_RECEIVED, ts.session->state, "tftp session in wrong state"); |
| ASSERT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response"); |
| |
| uint8_t data_buf[516] = { |
| 0x00, 0x03, // Opcode (DATA) |
| 0x00, 0x01, // Block |
| 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, // Data; compiler will fill out the rest with zeros |
| }; |
| data_buf[515] = 0x79; // set the last byte to make sure it all gets copied |
| |
| ifc.write = mock_write; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| tx_test_data td; |
| status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, |
| &td); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "receive data failed"); |
| EXPECT_TRUE(verify_write_data(data_buf + 4, td), "bad write data"); |
| EXPECT_EQ(1, ts.session->block_number, "tftp session block number mismatch"); |
| EXPECT_EQ(1, ts.session->window_index, "tftp session window index mismatch"); |
| |
| // Update block number and first/last bytes of the data packet |
| data_buf[3]++; |
| data_buf[4]++; |
| data_buf[515]++; |
| td.expected.block++; |
| td.expected.offset += DEFAULT_BLOCKSIZE; |
| |
| status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, |
| &td); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "receive data failed"); |
| EXPECT_TRUE(verify_write_data(data_buf + 4, td), "bad write data"); |
| EXPECT_EQ(2, ts.session->block_number, "tftp session block number mismatch"); |
| EXPECT_EQ(2, ts.session->window_index, "tftp session window index mismatch"); |
| |
| // Update block number and first/last bytes of the data packet. Block number |
| // goes up by 2 to indicate a skipped block. |
| data_buf[3] = 4u; |
| data_buf[4]++; |
| data_buf[515]++; |
| status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, |
| nullptr); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "receive data failed"); |
| ASSERT_GT(ts.outlen, 0, "outlen must not be zero"); |
| auto msg = reinterpret_cast<tftp_data_msg*>(ts.out); |
| EXPECT_EQ(ntohs(msg->opcode) & 0xff, OPCODE_ACK, "bad opcode"); |
| // Opcode prefix should have been incremented when a packet was not received |
| EXPECT_EQ((ntohs(msg->opcode) & 0xff00) >> 8, 1, "bad opcode prefix"); |
| EXPECT_EQ(ntohs(msg->block), 2, "bad block number"); |
| EXPECT_EQ(0, td.actual.data[1024], "block 3 should be empty"); |
| EXPECT_EQ(2, ts.session->block_number, "tftp session block number mismatch"); |
| // Reset the window index after sending the ack with the last known block |
| EXPECT_EQ(0, ts.session->window_index, "tftp session window index mismatch"); |
| |
| END_TEST; |
| } |
| |
| namespace { |
| |
| constexpr const unsigned long kWrapAt = 0x3ffff; |
| constexpr const int kBlockSize = 8; |
| constexpr const unsigned long kFileSize = (kWrapAt + 2) * kBlockSize; |
| |
| static bool test_tftp_receive_data_block_wrapping(void) { |
| BEGIN_TEST; |
| |
| test_state ts; |
| static bool write_called; |
| write_called = false; |
| ts.reset(1024, 2048, 2048); |
| |
| tftp_file_interface ifc = {NULL, NULL, NULL, NULL, NULL}; |
| ifc.open_write = [](const char* filename, size_t size, void* cookie) -> tftp_status { |
| EXPECT_STR_EQ(filename, kRemoteFilename, "bad filename"); |
| EXPECT_EQ(size, kFileSize, "bad file size"); |
| return 0; |
| }; |
| ifc.write = [](const void* data, size_t* length, off_t offset, void* file_cookie) -> tftp_status { |
| // Remember that the block count starts at zero, which makes the offset |
| // calculation a bit counter-intuitive (one might expect that we would |
| // be writing to (kWrapAt + 1) * kBlockSize). |
| EXPECT_EQ(kWrapAt * kBlockSize, offset, "block count failed to wrap"); |
| write_called = true; |
| return TFTP_NO_ERROR; |
| }; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| char req_buf[1024]; |
| req_buf[0] = 0x00; |
| req_buf[1] = OPCODE_WRQ; |
| int req_buf_sz = 2 + |
| snprintf(&req_buf[2], sizeof(req_buf) - 2, "%s%cOCTET%cTSIZE%c%lu%cBLKSIZE%c%d", |
| kRemoteFilename, '\0', '\0', '\0', kFileSize, '\0', '\0', kBlockSize) + |
| 1; |
| |
| ASSERT_LT(req_buf_sz, (int)sizeof(req_buf), "insufficient space for WRQ message"); |
| auto status = |
| tftp_process_msg(ts.session, req_buf, req_buf_sz, ts.out, &ts.outlen, &ts.timeout, nullptr); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "receive write request failed"); |
| ASSERT_EQ(REQ_RECEIVED, ts.session->state, "tftp session in wrong state"); |
| ASSERT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response"); |
| |
| // Artificially advance to force block wrapping |
| ts.session->block_number = kWrapAt; |
| ts.session->window_index = 0; |
| |
| uint8_t data_buf[] = { |
| 0x00, 0x03, // Opcode (DATA) |
| 0x00, 0x00, // Block |
| 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 // Data |
| }; |
| |
| status = tftp_process_msg(ts.session, data_buf, sizeof(data_buf), ts.out, &ts.outlen, &ts.timeout, |
| NULL); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "failed to process data"); |
| EXPECT_TRUE(write_called, "no attempt to write data"); |
| EXPECT_EQ(kWrapAt + 1, ts.session->block_number, "failed to advance block number"); |
| |
| uint8_t expected_ack[] = { |
| 0x00, 0x04, // Opcode (ACK) |
| 0x00, 0x00 // Block |
| }; |
| EXPECT_EQ(sizeof(expected_ack), ts.outlen, "response size mismatch"); |
| EXPECT_EQ(0, memcmp(expected_ack, ts.out, sizeof(expected_ack)), "bad response"); |
| |
| END_TEST; |
| } |
| |
| } // namespace |
| |
| static bool test_tftp_send_data_receive_ack(void) { |
| BEGIN_TEST; |
| |
| test_state ts; |
| ts.reset(1024, 1024, 1500); |
| |
| auto status = |
| tftp_generate_request(ts.session, SEND_FILE, kLocalFilename, kRemoteFilename, MODE_OCTET, |
| ts.msg_size, NULL, NULL, NULL, ts.out, &ts.outlen, &ts.timeout); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "error generating write request"); |
| EXPECT_TRUE(verify_write_request(ts), "bad write request"); |
| |
| uint8_t oack_buf[] = { |
| 0x00, 0x06, // Opcode (OACK) |
| 'T', 'S', 'I', 'Z', 'E', 0x00, // Option |
| '1', '0', '2', '4', 0x00, // TSIZE value |
| }; |
| |
| tftp_file_interface ifc = {NULL, NULL, mock_read, NULL, NULL}; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| tx_test_data td; |
| status = tftp_process_msg(ts.session, oack_buf, sizeof(oack_buf), ts.out, &ts.outlen, &ts.timeout, |
| &td); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "receive error"); |
| EXPECT_EQ(ts.outlen, sizeof(tftp_data_msg) + DEFAULT_BLOCKSIZE, "bad outlen"); |
| EXPECT_TRUE(verify_read_data(ts, td), "bad test data"); |
| |
| uint8_t ack_buf[] = { |
| 0x00, 0x04, // Opcode (ACK) |
| 0x00, 0x01, // Block |
| }; |
| |
| td.expected.block = 2; |
| td.expected.offset += DEFAULT_BLOCKSIZE; |
| td.expected.data[1] = 'f'; |
| status = |
| tftp_process_msg(ts.session, ack_buf, sizeof(ack_buf), ts.out, &ts.outlen, &ts.timeout, &td); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "receive error"); |
| EXPECT_EQ(SENDING_DATA, ts.session->state, "session should be in state SENDING_DATA"); |
| // The block number will not advance until we see an ACK for block 2 |
| EXPECT_EQ(1, ts.session->block_number, "tftp session block number mismatch"); |
| EXPECT_EQ(1, ts.session->window_index, "tftp session window index mismatch"); |
| EXPECT_EQ(ts.outlen, sizeof(tftp_data_msg) + DEFAULT_BLOCKSIZE, "bad outlen"); |
| EXPECT_TRUE(verify_read_data(ts, td), "bad test data"); |
| |
| END_TEST; |
| } |
| |
| static bool test_tftp_send_data_receive_final_ack(void) { |
| BEGIN_TEST; |
| |
| test_state ts; |
| ts.reset(1024, 1024, 1500); |
| |
| auto status = |
| tftp_generate_request(ts.session, SEND_FILE, kLocalFilename, kRemoteFilename, MODE_OCTET, |
| ts.msg_size, NULL, NULL, NULL, ts.out, &ts.outlen, &ts.timeout); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "error generating write request"); |
| EXPECT_TRUE(verify_write_request(ts), "bad write request"); |
| |
| uint8_t oack_buf[] = { |
| 0x00, 0x06, // Opcode (OACK) |
| 'T', 'S', 'I', 'Z', 'E', 0x00, // Option |
| '1', '0', '2', '4', 0x00, // TSIZE value |
| }; |
| |
| tftp_file_interface ifc = {NULL, NULL, mock_read, NULL, NULL}; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| tx_test_data td; |
| status = tftp_process_msg(ts.session, oack_buf, sizeof(oack_buf), ts.out, &ts.outlen, &ts.timeout, |
| &td); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "receive error"); |
| ASSERT_EQ(ts.outlen, sizeof(tftp_data_msg) + DEFAULT_BLOCKSIZE, "bad outlen"); |
| ASSERT_TRUE(verify_read_data(ts, td), "bad test data"); |
| |
| uint8_t ack_buf[] = { |
| 0x00, 0x04, // Opcode (ACK) |
| 0x00, 0x01, // Block |
| }; |
| |
| td.expected.block = 2; |
| td.expected.offset += DEFAULT_BLOCKSIZE; |
| status = |
| tftp_process_msg(ts.session, ack_buf, sizeof(ack_buf), ts.out, &ts.outlen, &ts.timeout, &td); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "receive error"); |
| EXPECT_EQ(ts.outlen, sizeof(tftp_data_msg) + DEFAULT_BLOCKSIZE, "bad outlen"); |
| EXPECT_TRUE(verify_read_data(ts, td), "bad test data"); |
| |
| // second block |
| ack_buf[3]++; |
| status = |
| tftp_process_msg(ts.session, ack_buf, sizeof(ack_buf), ts.out, &ts.outlen, &ts.timeout, &td); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "receive block 2 error"); |
| EXPECT_EQ(ts.outlen, sizeof(tftp_data_msg), "block 3 not empty"); |
| |
| // Do not expect any more sends. |
| ack_buf[3]++; |
| status = tftp_process_msg(ts.session, ack_buf, sizeof(ack_buf), ts.out, &ts.outlen, &ts.timeout, |
| nullptr); |
| EXPECT_EQ(TFTP_TRANSFER_COMPLETED, status, "tftp transfer should be complete"); |
| EXPECT_EQ(ts.outlen, 0, "no outgoing message expected"); |
| |
| END_TEST; |
| } |
| |
| static bool test_tftp_send_data_receive_ack_skipped_block(void) { |
| BEGIN_TEST; |
| |
| test_state ts; |
| ts.reset(1024, 1024, 1500); |
| |
| auto status = |
| tftp_generate_request(ts.session, SEND_FILE, kLocalFilename, kRemoteFilename, MODE_OCTET, |
| ts.msg_size, NULL, NULL, NULL, ts.out, &ts.outlen, &ts.timeout); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "error generating write request"); |
| EXPECT_TRUE(verify_write_request(ts), "bad write request"); |
| |
| uint8_t oack_buf[] = { |
| 0x00, 0x06, // Opcode (OACK) |
| 'T', 'S', 'I', 'Z', 'E', 0x00, // Option |
| '1', '0', '2', '4', 0x00, // TSIZE value |
| }; |
| |
| tftp_file_interface ifc = {NULL, NULL, mock_read, NULL, NULL}; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| tx_test_data td; |
| status = tftp_process_msg(ts.session, oack_buf, sizeof(oack_buf), ts.out, &ts.outlen, &ts.timeout, |
| &td); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "receive error"); |
| ASSERT_EQ(ts.outlen, sizeof(tftp_data_msg) + DEFAULT_BLOCKSIZE, "bad outlen"); |
| ASSERT_TRUE(verify_read_data(ts, td), "bad test data"); |
| |
| uint8_t ack_buf[] = { |
| 0x00, 0x04, // Opcode (ACK) |
| 0x00, 0x00, // Block |
| }; |
| |
| tx_test_data td2; |
| status = |
| tftp_process_msg(ts.session, ack_buf, sizeof(ack_buf), ts.out, &ts.outlen, &ts.timeout, &td2); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "receive error"); |
| EXPECT_EQ(SENDING_DATA, ts.session->state, "session should be in state SENDING_DATA"); |
| EXPECT_EQ(0, ts.session->block_number, "tftp session block number mismatch"); |
| EXPECT_EQ(1, ts.session->window_index, "tftp window index mismatch"); |
| EXPECT_EQ(ts.outlen, sizeof(tftp_data_msg) + DEFAULT_BLOCKSIZE, "bad outlen"); |
| EXPECT_TRUE(verify_read_data(ts, td2), "bad test data"); |
| END_TEST; |
| } |
| |
| static bool test_tftp_send_data_receive_ack_window_size(void) { |
| uint16_t kWindowSize = 2; |
| BEGIN_TEST; |
| |
| test_state ts; |
| ts.reset(1024, 2048, 1500); |
| |
| auto status = |
| tftp_generate_request(ts.session, SEND_FILE, kLocalFilename, kRemoteFilename, MODE_OCTET, |
| ts.msg_size, NULL, NULL, &kWindowSize, ts.out, &ts.outlen, &ts.timeout); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "error generating write request"); |
| EXPECT_TRUE(verify_write_request(ts), "bad write request"); |
| |
| uint8_t oack_buf[] = { |
| 0x00, 0x06, // Opcode (OACK) |
| 'T', 'S', 'I', 'Z', 'E', 0x00, // Option |
| '2', '0', '4', '8', 0x00, // TSIZE value |
| 'W', 'I', 'N', 'D', 'O', 'W', 'S', 'I', 'Z', 'E', 0x00, // Option |
| '2', 0x00, // WINDOWSIZE value |
| }; |
| |
| tftp_file_interface ifc = {NULL, NULL, mock_read, NULL, NULL}; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| tx_test_data td; |
| status = tftp_process_msg(ts.session, oack_buf, sizeof(oack_buf), ts.out, &ts.outlen, &ts.timeout, |
| &td); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "receive error"); |
| ASSERT_EQ(0, ts.session->block_number, "tftp session block number mismatch"); |
| ASSERT_EQ(1, ts.session->window_index, "tftp session window index mismatch"); |
| ASSERT_EQ(ts.outlen, sizeof(tftp_data_msg) + DEFAULT_BLOCKSIZE, "bad outlen"); |
| ASSERT_TRUE(verify_read_data(ts, td), "bad test data"); |
| ASSERT_TRUE(tftp_session_has_pending(ts.session), "expected pending data to transmit"); |
| |
| td.expected.block++; |
| td.expected.offset += DEFAULT_BLOCKSIZE; |
| td.expected.data[0]++; |
| status = tftp_prepare_data(ts.session, ts.out, &ts.outlen, &ts.timeout, &td); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "receive error"); |
| // Window index doesn't roll until we receive an ACK |
| ASSERT_EQ(0, ts.session->block_number, "tftp session block number mismatch"); |
| ASSERT_EQ(2, ts.session->window_index, "tftp session window index mismatch"); |
| ASSERT_EQ(ts.outlen, sizeof(tftp_data_msg) + DEFAULT_BLOCKSIZE, "bad outlen"); |
| ASSERT_TRUE(verify_read_data(ts, td), "bad test data"); |
| ASSERT_FALSE(tftp_session_has_pending(ts.session), "expected to wait for ack"); |
| |
| uint8_t ack_buf[] = { |
| 0x00, 0x04, // Opcode (ACK) |
| 0x00, 0x02, // Block |
| }; |
| |
| td.expected.block++; |
| td.expected.offset += DEFAULT_BLOCKSIZE; |
| td.expected.data[1]++; |
| status = |
| tftp_process_msg(ts.session, ack_buf, sizeof(ack_buf), ts.out, &ts.outlen, &ts.timeout, &td); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "receive error"); |
| EXPECT_EQ(SENDING_DATA, ts.session->state, "session should be in state SENDING_DATA"); |
| EXPECT_EQ(2, ts.session->block_number, "tftp session block number mismatch"); |
| EXPECT_EQ(1, ts.session->window_index, "tftp session window index mismatch"); |
| EXPECT_EQ(ts.outlen, sizeof(tftp_data_msg) + DEFAULT_BLOCKSIZE, "bad outlen"); |
| EXPECT_TRUE(verify_read_data(ts, td), "bad test data"); |
| EXPECT_TRUE(tftp_session_has_pending(ts.session), "expected pending data to transmit"); |
| |
| END_TEST; |
| } |
| |
| static bool test_tftp_send_data_receive_ack_block_wrapping(void) { |
| BEGIN_TEST; |
| |
| constexpr unsigned long kWrapAt = 0x3ffff; |
| constexpr uint16_t kBlockSize = 8; |
| constexpr unsigned long kFileSize = (kWrapAt + 2) * kBlockSize; |
| |
| static int reads_performed; |
| reads_performed = 0; |
| |
| test_state ts; |
| ts.reset(1024, 2048, 2048); |
| |
| auto status = |
| tftp_generate_request(ts.session, SEND_FILE, kLocalFilename, kRemoteFilename, MODE_OCTET, |
| kFileSize, &kBlockSize, NULL, NULL, ts.out, &ts.outlen, &ts.timeout); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "error generating write request"); |
| EXPECT_TRUE(verify_write_request(ts), "bad write request"); |
| |
| char oack_buf[256]; |
| oack_buf[0] = 0x00; |
| oack_buf[1] = 0x06; |
| size_t oack_buf_sz = 2 + |
| snprintf(&oack_buf[2], sizeof(oack_buf) - 2, "TSIZE%c%lu%cBLKSIZE%c%d", '\0', |
| kFileSize, '\0', '\0', kBlockSize) + |
| 1; |
| |
| ASSERT_LT(oack_buf_sz, (int)sizeof(oack_buf), "insufficient space for OACK message"); |
| tftp_file_interface ifc = {NULL, NULL, NULL, NULL, NULL}; |
| ifc.read = [](void* data, size_t* length, off_t offset, void* cookie) -> tftp_status { |
| EXPECT_EQ(0, offset, "incorrect initial read"); |
| reads_performed++; |
| return TFTP_NO_ERROR; |
| }; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| status = |
| tftp_process_msg(ts.session, oack_buf, oack_buf_sz, ts.out, &ts.outlen, &ts.timeout, NULL); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "failure to process OACK"); |
| EXPECT_EQ(1, reads_performed, "failed to call read function"); |
| |
| // Artificially advance the session to a point where wrapping will occur |
| ts.session->block_number = kWrapAt; |
| ts.session->window_index = 0; |
| |
| uint8_t data_buf[4 + kBlockSize]; |
| size_t data_buf_len = sizeof(data_buf); |
| ifc.read = [](void* data, size_t* length, off_t offset, void* cookie) -> tftp_status { |
| // Keep in mind that the block index starts at 1, so the offset calculation |
| // is not necessarily intuitive |
| EXPECT_EQ(kWrapAt * kBlockSize, offset, "incorrect wrapping read"); |
| reads_performed++; |
| return TFTP_NO_ERROR; |
| }; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| status = tftp_prepare_data(ts.session, data_buf, &data_buf_len, &ts.timeout, NULL); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "failed to generate DATA packet"); |
| EXPECT_EQ(2, reads_performed, "failed to call read function"); |
| EXPECT_EQ(sizeof(data_buf), data_buf_len, "improperly formatted DATA packet"); |
| unsigned int opcode = data_buf[0] << 8 | data_buf[1]; |
| EXPECT_EQ(0x0003, opcode, "incorrect DATA packet opcode"); |
| unsigned int block = data_buf[2] << 8 | data_buf[3]; |
| EXPECT_EQ(0x0000, block, "incorrect DATA packet block"); |
| |
| END_TEST; |
| } |
| |
| static bool test_tftp_send_data_receive_ack_skip_block_wrap(void) { |
| BEGIN_TEST; |
| |
| constexpr unsigned long kLastBlockSent = 0x40003; |
| constexpr unsigned long kAckBlock = 0x3fffb; |
| constexpr uint16_t kBlockSize = 8; |
| constexpr unsigned long kFileSize = 0x50000 * kBlockSize; |
| |
| static int reads_performed; |
| reads_performed = 0; |
| |
| test_state ts; |
| ts.reset(1024, 2048, 2048); |
| |
| // Create a write request |
| auto status = |
| tftp_generate_request(ts.session, SEND_FILE, kLocalFilename, kRemoteFilename, MODE_OCTET, |
| kFileSize, &kBlockSize, NULL, NULL, ts.out, &ts.outlen, &ts.timeout); |
| EXPECT_EQ(TFTP_NO_ERROR, status, "error generating write request"); |
| EXPECT_TRUE(verify_write_request(ts), "bad write request"); |
| |
| // Simulate a response (OACK) |
| char oack_buf[256]; |
| oack_buf[0] = 0x00; |
| oack_buf[1] = 0x06; |
| size_t oack_buf_sz = 2 + |
| snprintf(&oack_buf[2], sizeof(oack_buf) - 2, "TSIZE%c%lu%cBLKSIZE%c%d", '\0', |
| kFileSize, '\0', '\0', kBlockSize) + |
| 1; |
| |
| ASSERT_LT(oack_buf_sz, (int)sizeof(oack_buf), "insufficient space for OACK message"); |
| tftp_file_interface ifc = {NULL, NULL, NULL, NULL, NULL}; |
| ifc.read = [](void* data, size_t* length, off_t offset, void* cookie) -> tftp_status { |
| EXPECT_EQ(0, offset, "incorrect initial read"); |
| reads_performed++; |
| return TFTP_NO_ERROR; |
| }; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| // Process OACK and generate write of first block |
| status = |
| tftp_process_msg(ts.session, oack_buf, oack_buf_sz, ts.out, &ts.outlen, &ts.timeout, NULL); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "failure to process OACK"); |
| EXPECT_EQ(1, reads_performed, "failed to call read function"); |
| |
| // Artificially advance the session so we can test wrapping |
| ts.session->block_number = kLastBlockSent; |
| ts.session->window_index = 0; |
| |
| // Create a DATA packet for block kLastBlockSent + 1 |
| uint8_t data_buf[4 + kBlockSize] = {0}; |
| size_t data_buf_len = sizeof(data_buf); |
| tftp_data_msg* msg = reinterpret_cast<tftp_data_msg*>(&data_buf[0]); |
| ifc.read = [](void* data, size_t* length, off_t offset, void* cookie) -> tftp_status { |
| // Keep in mind that the block index starts at 1, so the offset calculation |
| // is not necessarily intuitive |
| EXPECT_EQ(kLastBlockSent * kBlockSize, offset, "incorrect read offset"); |
| reads_performed++; |
| return TFTP_NO_ERROR; |
| }; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| status = tftp_prepare_data(ts.session, data_buf, &data_buf_len, &ts.timeout, NULL); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "failed to generate DATA packet"); |
| EXPECT_EQ(2, reads_performed, "failed to call read function"); |
| EXPECT_EQ(sizeof(data_buf), data_buf_len, "improperly formatted DATA packet"); |
| unsigned int opcode = htons(msg->opcode); |
| EXPECT_EQ(OPCODE_DATA, opcode, "incorrect DATA packet opcode"); |
| uint16_t offset = ntohs(msg->block); |
| ASSERT_EQ((kLastBlockSent + 1) & 0xffff, offset, "incorrect DATA packet block"); |
| |
| // Simulate an ACK response that is before our last block wrap |
| tftp_data_msg ack_msg; |
| ack_msg.opcode = htons(OPCODE_ACK); |
| ack_msg.block = htons(kAckBlock & 0xffff); |
| ifc.read = [](void* data, size_t* length, off_t offset, void* cookie) -> tftp_status { |
| EXPECT_EQ(kAckBlock * kBlockSize, offset, "incorrect read offset"); |
| reads_performed++; |
| return TFTP_NO_ERROR; |
| }; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| // Next DATA packet should backup to proper address (before wrap) |
| status = tftp_process_msg(ts.session, reinterpret_cast<void*>(&ack_msg), sizeof(ack_msg), ts.out, |
| &ts.outlen, &ts.timeout, NULL); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "no ACK generated"); |
| EXPECT_EQ(3, reads_performed, "failed to call read function"); |
| ASSERT_EQ(ts.outlen, sizeof(tftp_data_msg) + kBlockSize, "improper DATA packet size"); |
| msg = reinterpret_cast<tftp_data_msg*>(ts.out); |
| EXPECT_EQ(OPCODE_DATA, ntohs(msg->opcode) & 0xff, "incorrect DATA packet opcode"); |
| // Opcode prefix should have been incremented when a packet was dropped |
| EXPECT_EQ(1, (ntohs(msg->opcode) & 0xff00) >> 8, "incorrect opcode prefix"); |
| EXPECT_EQ((kAckBlock + 1) & 0xffff, ntohs(msg->block), "incorrect DATA packet block"); |
| EXPECT_EQ(ts.session->block_number, kAckBlock, "session offset not rewound correctly"); |
| EXPECT_EQ(ts.session->window_index, 1, "window index not set correctly"); |
| |
| // Try again, this time disabling opcode prefixes |
| ifc.read = [](void* data, size_t* length, off_t offset, void* cookie) -> tftp_status { |
| return TFTP_NO_ERROR; |
| }; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| tftp_session_set_opcode_prefix_use(ts.session, false); |
| ack_msg.block = htons((kAckBlock + 1) & 0xffff); |
| status = tftp_process_msg(ts.session, reinterpret_cast<void*>(&ack_msg), sizeof(ack_msg), ts.out, |
| &ts.outlen, &ts.timeout, NULL); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "no ACK generated"); |
| ASSERT_EQ(ts.outlen, sizeof(tftp_data_msg) + kBlockSize, "improper DATA packet size"); |
| msg = reinterpret_cast<tftp_data_msg*>(ts.out); |
| EXPECT_EQ(OPCODE_DATA, ntohs(msg->opcode) & 0xff, "incorrect DATA packet opcode"); |
| EXPECT_EQ(0, (ntohs(msg->opcode) & 0xff00) >> 8, "incorrect opcode prefix"); |
| EXPECT_EQ((kAckBlock + 2) & 0xffff, ntohs(msg->block), "incorrect DATA packet block"); |
| |
| END_TEST; |
| } |
| |
| static ssize_t open_read_should_wait(const char* filename, void* cookie) { |
| return TFTP_ERR_SHOULD_WAIT; |
| } |
| |
| static tftp_status open_write_should_wait(const char* filename, size_t size, void* cookie) { |
| return TFTP_ERR_SHOULD_WAIT; |
| } |
| |
| /* Verify behavior when one of our open_file interface functions returns TFTP_ERR_SHOULD_WAIT. */ |
| static bool test_tftp_open_should_wait(tftp_file_direction dir) { |
| BEGIN_TEST; |
| |
| const uint16_t block_size = 456; |
| const uint8_t timeout = 3; |
| const uint16_t window_size = 128; |
| |
| test_state ts; |
| ts.reset(1024, 1024, 1500); |
| tftp_file_interface ifc = {open_read_should_wait, open_write_should_wait, NULL, NULL, NULL}; |
| tftp_session_set_file_interface(ts.session, &ifc); |
| |
| // Construct a RRQ or WRQ packet |
| size_t req_file_size = (dir == SEND_FILE) ? 1024 : 0; |
| char buf[256]; |
| buf[0] = 0x00; |
| buf[1] = (dir == SEND_FILE) ? OPCODE_WRQ : OPCODE_RRQ; |
| size_t buf_sz = 2; |
| buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz, |
| "%s%cOCTET%cTSIZE%c%zu%cBLKSIZE%c%d%cTIMEOUT%c%d%cWINDOWSIZE%c%d", |
| kRemoteFilename, '\0', '\0', '\0', req_file_size, '\0', '\0', block_size, '\0', |
| '\0', timeout, '\0', '\0', window_size) + |
| 1; |
| ASSERT_LT(buf_sz, (int)sizeof(buf), "insufficient space for request"); |
| |
| tftp_status status = |
| tftp_process_msg(ts.session, buf, buf_sz, ts.out, &ts.outlen, &ts.timeout, nullptr); |
| |
| // Check API return value |
| EXPECT_EQ(TFTP_ERR_SHOULD_WAIT, status, "expected SHOULD_WAIT status"); |
| |
| // tftp_process_msg should have generated an error packet response - verify its fields |
| ASSERT_GT(ts.outlen, 0); |
| auto msg = reinterpret_cast<tftp_err_msg*>(ts.out); |
| EXPECT_EQ(msg->opcode, htons(OPCODE_ERROR)); |
| EXPECT_EQ(msg->err_code, htons(TFTP_ERR_CODE_BUSY)); |
| if (dir == SEND_FILE) { |
| const char expected_err[] = "not ready to receive"; |
| EXPECT_STR_EQ(expected_err, msg->msg, "bad error message"); |
| } else { |
| const char expected_err[] = "not ready to send"; |
| EXPECT_STR_EQ(expected_err, msg->msg, "bad error message"); |
| } |
| |
| END_TEST; |
| } |
| |
| static bool test_tftp_open_read_should_wait(void) { |
| // RECV is from the perspective of the client, not the server |
| return test_tftp_open_should_wait(RECV_FILE); |
| } |
| |
| static bool test_tftp_open_write_should_wait(void) { |
| // SEND is from the perspective of the client, not the server |
| return test_tftp_open_should_wait(SEND_FILE); |
| } |
| |
| static bool test_tftp_recv_busy(tftp_file_direction dir) { |
| BEGIN_TEST; |
| |
| test_state ts; |
| ts.reset(1024, 1024, 1500); |
| |
| auto status = tftp_generate_request(ts.session, dir, kLocalFilename, kRemoteFilename, MODE_OCTET, |
| dir == SEND_FILE ? ts.msg_size : 0, NULL, NULL, NULL, ts.out, |
| &ts.outlen, &ts.timeout); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "error generating request"); |
| if (dir == SEND_FILE) { |
| ASSERT_TRUE(verify_write_request(ts), "bad write request"); |
| } else { |
| ASSERT_TRUE(verify_read_request(ts), "bad read request"); |
| } |
| |
| // Simulate a BUSY error response |
| char buf[256]; |
| buf[0] = 0x00; |
| buf[1] = OPCODE_ERROR; |
| buf[2] = (TFTP_ERR_CODE_BUSY & 0xff00) >> 8; |
| buf[3] = TFTP_ERR_CODE_BUSY & 0xff; |
| size_t buf_sz = 4; |
| buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz, "not ready") + 1; |
| ASSERT_LT(buf_sz, (int)sizeof(buf), "insufficient space for request"); |
| |
| status = tftp_process_msg(ts.session, buf, buf_sz, ts.out, &ts.outlen, &ts.timeout, nullptr); |
| |
| // Check API return value |
| EXPECT_EQ(TFTP_ERR_SHOULD_WAIT, status, "expected SHOULD_WAIT status"); |
| |
| // tftp_process_msg should not have generated a response |
| EXPECT_EQ(ts.outlen, 0); |
| |
| // Verify session state |
| EXPECT_EQ(NONE, ts.session->state, "bad session: state"); |
| |
| END_TEST; |
| } |
| |
| /* Verify handling of a BUSY error packet when we send a WRQ. */ |
| static bool test_tftp_recv_busy_from_wrq(void) { return test_tftp_recv_busy(SEND_FILE); } |
| |
| /* Verify handling of a BUSY error packet when we send a RRQ. */ |
| static bool test_tftp_recv_busy_from_rrq(void) { return test_tftp_recv_busy(RECV_FILE); } |
| |
| /* Verify that receiving an error other than BUSY puts the session into an error state. */ |
| static bool test_tftp_recv_other_err(void) { |
| BEGIN_TEST; |
| |
| test_state ts; |
| ts.reset(1024, 1024, 1500); |
| |
| auto status = |
| tftp_generate_request(ts.session, SEND_FILE, kLocalFilename, kRemoteFilename, MODE_OCTET, |
| ts.msg_size, NULL, NULL, NULL, ts.out, &ts.outlen, &ts.timeout); |
| ASSERT_EQ(TFTP_NO_ERROR, status, "error generating request"); |
| ASSERT_TRUE(verify_write_request(ts), "bad write request"); |
| |
| // Simulate a BUSY error response |
| char buf[256]; |
| buf[0] = 0x00; |
| buf[1] = OPCODE_ERROR; |
| buf[2] = (TFTP_ERR_CODE_DISK_FULL & 0xff00) >> 8; |
| buf[3] = TFTP_ERR_CODE_DISK_FULL & 0xff; |
| size_t buf_sz = 4; |
| buf_sz += snprintf(&buf[buf_sz], sizeof(buf) - buf_sz, "disk full") + 1; |
| ASSERT_LT(buf_sz, (int)sizeof(buf), "insufficient space for request"); |
| |
| status = tftp_process_msg(ts.session, buf, buf_sz, ts.out, &ts.outlen, &ts.timeout, nullptr); |
| |
| // Check API return value |
| EXPECT_EQ(TFTP_ERR_INTERNAL, status, "expected TFTP_ERR_INTERNAL status"); |
| |
| // tftp_process_msg should not have generated a response |
| EXPECT_EQ(ts.outlen, 0); |
| |
| // Verify session state |
| EXPECT_EQ(ERROR, ts.session->state, "bad session: state"); |
| |
| END_TEST; |
| } |
| |
| BEGIN_TEST_CASE(tftp_setup) |
| RUN_TEST(test_tftp_init) |
| RUN_TEST(test_tftp_session_options) |
| END_TEST_CASE(tftp_setup) |
| |
| BEGIN_TEST_CASE(tftp_generate_wrq) |
| RUN_TEST(test_tftp_generate_wrq_default) |
| RUN_TEST(test_tftp_generate_wrq_options) |
| RUN_TEST(test_tftp_generate_wrq_override_blocksize) |
| RUN_TEST(test_tftp_generate_wrq_override_timeout) |
| RUN_TEST(test_tftp_generate_wrq_override_windowsize) |
| END_TEST_CASE(tftp_generate_wrq) |
| |
| BEGIN_TEST_CASE(tftp_generate_rrq) |
| RUN_TEST(test_tftp_generate_rrq_default) |
| RUN_TEST(test_tftp_generate_rrq_options) |
| RUN_TEST(test_tftp_generate_rrq_override_blocksize) |
| RUN_TEST(test_tftp_generate_rrq_override_timeout) |
| RUN_TEST(test_tftp_generate_rrq_override_windowsize) |
| END_TEST_CASE(tftp_generate_rrq) |
| |
| BEGIN_TEST_CASE(tftp_receive_wrq) |
| RUN_TEST(test_tftp_receive_wrq_unexpected) |
| RUN_TEST(test_tftp_receive_wrq_too_large) |
| RUN_TEST(test_tftp_receive_wrq_no_tsize) |
| RUN_TEST(test_tftp_receive_wrq_send_oack) |
| RUN_TEST(test_tftp_receive_wrq_blocksize) |
| RUN_TEST(test_tftp_receive_wrq_timeout) |
| RUN_TEST(test_tftp_receive_wrq_windowsize) |
| RUN_TEST(test_tftp_receive_wrq_have_overrides) |
| RUN_TEST(test_tftp_receive_force_wrq_no_overrides) |
| RUN_TEST(test_tftp_receive_force_wrq_have_overrides) |
| END_TEST_CASE(tftp_receive_wrq) |
| |
| BEGIN_TEST_CASE(tftp_receive_rrq) |
| RUN_TEST(test_tftp_receive_rrq_unexpected) |
| RUN_TEST(test_tftp_receive_rrq_too_large) |
| RUN_TEST(test_tftp_receive_rrq_no_tsize) |
| RUN_TEST(test_tftp_receive_rrq_send_oack) |
| RUN_TEST(test_tftp_receive_rrq_blocksize) |
| RUN_TEST(test_tftp_receive_rrq_timeout) |
| RUN_TEST(test_tftp_receive_rrq_windowsize) |
| RUN_TEST(test_tftp_receive_rrq_have_overrides) |
| RUN_TEST(test_tftp_receive_force_rrq_no_overrides) |
| RUN_TEST(test_tftp_receive_force_rrq_have_overrides) |
| END_TEST_CASE(tftp_receive_rrq) |
| |
| BEGIN_TEST_CASE(tftp_receive_oack) |
| RUN_TEST(test_tftp_receive_wrq_oack) |
| RUN_TEST(test_tftp_receive_wrq_oack_blocksize) |
| RUN_TEST(test_tftp_receive_wrq_oack_timeout) |
| RUN_TEST(test_tftp_receive_wrq_oack_windowsize) |
| RUN_TEST(test_tftp_receive_rrq_oack) |
| RUN_TEST(test_tftp_receive_rrq_oack_blocksize) |
| RUN_TEST(test_tftp_receive_rrq_oack_timeout) |
| RUN_TEST(test_tftp_receive_rrq_oack_windowsize) |
| RUN_TEST(test_tftp_receive_oack_overrides) |
| END_TEST_CASE(tftp_receive_oack) |
| |
| BEGIN_TEST_CASE(tftp_receive_data) |
| RUN_TEST(test_tftp_receive_data) |
| RUN_TEST(test_tftp_receive_data_final_block) |
| RUN_TEST(test_tftp_receive_data_blocksize) |
| RUN_TEST(test_tftp_receive_data_windowsize) |
| RUN_TEST(test_tftp_receive_data_skipped_block) |
| RUN_TEST(test_tftp_receive_data_windowsize_skipped_block) |
| RUN_TEST(test_tftp_receive_data_block_wrapping) |
| END_TEST_CASE(tftp_receive_data) |
| |
| BEGIN_TEST_CASE(tftp_send_data) |
| RUN_TEST(test_tftp_send_data_receive_ack) |
| RUN_TEST(test_tftp_send_data_receive_final_ack) |
| RUN_TEST(test_tftp_send_data_receive_ack_skipped_block) |
| RUN_TEST(test_tftp_send_data_receive_ack_window_size) |
| RUN_TEST(test_tftp_send_data_receive_ack_block_wrapping) |
| RUN_TEST(test_tftp_send_data_receive_ack_skip_block_wrap) |
| END_TEST_CASE(tftp_send_data) |
| |
| BEGIN_TEST_CASE(tftp_send_err) |
| RUN_TEST(test_tftp_open_read_should_wait) |
| RUN_TEST(test_tftp_open_write_should_wait) |
| END_TEST_CASE(tftp_send_err) |
| |
| BEGIN_TEST_CASE(tftp_recv_err) |
| RUN_TEST(test_tftp_recv_busy_from_rrq) |
| RUN_TEST(test_tftp_recv_busy_from_wrq) |
| RUN_TEST(test_tftp_recv_other_err) |
| END_TEST_CASE(tftp_recv_err) |