blob: 7a6f0851a0a465799b9029ccf8bc97a1252517a2 [file] [log] [blame]
// 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 <tftp/tftp.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fbl/unique_ptr.h>
#include <unittest/unittest.h>
// For inspecting session state
#include "internal.h"
constexpr char kFilename[] = "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;
fbl::unique_ptr<uint8_t[]> sess_buf;
fbl::unique_ptr<uint8_t[]> msg_data;
fbl::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("filename", msg->data, ts.outlen, "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_wrq_default(void) {
BEGIN_TEST;
test_state ts;
ts.reset(1024, 1024, 1500);
auto status = tftp_generate_write_request(ts.session, kFilename, 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");
// Test TFTP state, but not internal session state
EXPECT_EQ(DEFAULT_BLOCKSIZE, ts.session->block_size, "bad session options: block size");
EXPECT_EQ(DEFAULT_TIMEOUT, ts.session->timeout, "bad session options: timeout");
EXPECT_EQ(DEFAULT_WINDOWSIZE, ts.session->window_size, "bad session options: window size");
EXPECT_EQ(SENT_WRQ, ts.session->state, "bad session: state");
EXPECT_EQ(ts.msg_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");
EXPECT_EQ(DEFAULT_TIMEOUT * 1000, ts.timeout, "timeout not set correctly");
// Verify that no options were specified in the request
const char* msg = static_cast<const char*>(ts.out);
const char block_sz_str[] = "BLKSIZE";
EXPECT_FALSE(find_str_in_mem(block_sz_str, sizeof(block_sz_str), msg, ts.outlen),
"block size shouldn't appear in request");
const char timeout_str[] = "TIMEOUT";
EXPECT_FALSE(find_str_in_mem(timeout_str, sizeof(timeout_str), msg, ts.outlen),
"timeout shouldn't appear in request");
const char win_sz_str[] = "WINDOWSIZE";
EXPECT_FALSE(find_str_in_mem(win_sz_str, sizeof(win_sz_str), msg, ts.outlen),
"window size shouldn't appear in request");
END_TEST;
}
static bool test_tftp_generate_wrq_settings(void) {
BEGIN_TEST;
test_state ts;
ts.reset(1024, 1024, 1500);
tftp_status status;
constexpr uint16_t kBlockSize = 555;
constexpr uint8_t kTimeout = 3;
constexpr uint16_t kWindowSize = 44;
uint16_t new_block_size = kBlockSize;
uint8_t new_timeout = kTimeout;
uint16_t new_window_size = kWindowSize;
status = tftp_set_options(ts.session, &new_block_size, &new_timeout, &new_window_size);
EXPECT_EQ(TFTP_NO_ERROR, status, "error setting session options");
EXPECT_EQ(kBlockSize, ts.session->options.block_size, "bad session options: block size");
EXPECT_EQ(kTimeout, ts.session->options.timeout, "bad session options: timeout");
EXPECT_EQ(kWindowSize, ts.session->options.window_size, "bad session options: window size");
status = tftp_generate_write_request(ts.session, kFilename, 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");
const char* msg = static_cast<const char*>(ts.out);
const char block_sz_str[] = { 'B', 'L', 'K', 'S', 'I', 'Z', 'E', '\0', '5', '5', '5', '\0' };
EXPECT_TRUE(find_str_in_mem(block_sz_str, sizeof(block_sz_str), msg, ts.outlen),
"block size not properly requested");
const char timeout_str[] = { 'T', 'I', 'M', 'E', 'O', 'U', 'T', '\0', '3', '\0' };
EXPECT_TRUE(find_str_in_mem(timeout_str, sizeof(timeout_str), msg, ts.outlen),
"timeout not properly requested");
const char win_sz_str[] = { 'W', 'I', 'N', 'D', 'O', 'W', 'S', 'I', 'Z', 'E', '\0', '4', '4', '\0' };
EXPECT_TRUE(find_str_in_mem(win_sz_str, sizeof(win_sz_str), msg, ts.outlen),
"window size not properly requested");
END_TEST;
}
static bool test_tftp_generate_wrq_blocksize(void) {
constexpr uint16_t kBlockSize = 1000;
BEGIN_TEST;
test_state ts;
ts.reset(1024, 1024, 1500);
auto status = tftp_generate_write_request(ts.session, kFilename, MODE_OCTET,
ts.msg_size, &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");
EXPECT_EQ(DEFAULT_MODE, ts.session->mode, "bad session: mode");
// Options we are requesting
EXPECT_EQ(BLOCKSIZE_OPTION, ts.session->client_sent_opts.mask, "bad session option mask");
EXPECT_EQ(kBlockSize, ts.session->client_sent_opts.block_size, "bad session options: block size");
// Default options
EXPECT_EQ(DEFAULT_TIMEOUT, ts.session->timeout, "bad session options: timeout");
EXPECT_EQ(DEFAULT_WINDOWSIZE, ts.session->window_size, "bad session options: window size");
const char* msg = static_cast<const char*>(ts.out);
const char block_sz_str[] = { 'B', 'L', 'K', 'S', 'I', 'Z', 'E', '!', '\0', '1', '0', '0', '0', '\0' };
EXPECT_TRUE(find_str_in_mem(block_sz_str, sizeof(block_sz_str), msg, ts.outlen),
"block size not properly requested");
END_TEST;
}
static bool test_tftp_generate_wrq_timeout(void) {
uint8_t kTimeout = 60;
BEGIN_TEST;
test_state ts;
ts.reset(1024, 1024, 1500);
auto status = tftp_generate_write_request(ts.session, kFilename, MODE_OCTET,
ts.msg_size, NULL, &kTimeout, 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");
EXPECT_EQ(DEFAULT_MODE, ts.session->mode, "bad session: mode");
// Options we are requesting
EXPECT_EQ(TIMEOUT_OPTION, ts.session->client_sent_opts.mask, "bad session option mask");
EXPECT_EQ(kTimeout, ts.session->client_sent_opts.timeout, "bad session options: timeout");
// Default options
EXPECT_EQ(DEFAULT_BLOCKSIZE, ts.session->block_size, "bad session options: block size");
EXPECT_EQ(DEFAULT_WINDOWSIZE, ts.session->window_size, "bad session options: window size");
// We still have to negotiate the timeout, so we use the default here.
EXPECT_EQ(DEFAULT_TIMEOUT * 1000, ts.timeout, "timeout not set correctly");
const char* msg = static_cast<const char*>(ts.out);
const char timeout_str[] = { 'T', 'I', 'M', 'E', 'O', 'U', 'T', '!', '\0', '6', '0', '\0' };
EXPECT_TRUE(find_str_in_mem(timeout_str, sizeof(timeout_str), msg, ts.outlen),
"timeout not properly requested");
END_TEST;
}
static bool test_tftp_generate_wrq_windowsize(void) {
uint16_t kWindowSize = 32;
BEGIN_TEST;
test_state ts;
ts.reset(1024, 1024, 1500);
auto status = tftp_generate_write_request(ts.session, kFilename, 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");
EXPECT_EQ(DEFAULT_MODE, ts.session->mode, "bad session: mode");
// Options we are requesting
EXPECT_EQ(WINDOWSIZE_OPTION, ts.session->client_sent_opts.mask, "bad session option mask");
EXPECT_EQ(kWindowSize, ts.session->client_sent_opts.window_size, "bad session options: window size");
// Default options
EXPECT_EQ(DEFAULT_BLOCKSIZE, ts.session->block_size, "bad session options: block size");
EXPECT_EQ(DEFAULT_TIMEOUT, ts.session->timeout, "bad session options: timeout");
const char* msg = static_cast<const char*>(ts.out);
const char win_sz_str[] = { 'W', 'I', 'N', 'D', 'O', 'W', 'S', 'I', 'Z', 'E', '!', '\0', '3', '2', '\0' };
EXPECT_TRUE(find_str_in_mem(win_sz_str, sizeof(win_sz_str), msg, ts.outlen),
"window size not properly requested");
END_TEST;
}
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_wrq_unexpected(void) {
BEGIN_TEST;
test_state ts;
ts.reset(1024, 1024, 1500);
auto status = tftp_generate_write_request(ts.session, kFilename, MODE_OCTET,
ts.msg_size, NULL, NULL, NULL, ts.out, &ts.outlen, &ts.timeout);
ASSERT_EQ(TFTP_NO_ERROR, status, "could not generate write request");
ASSERT_TRUE(verify_write_request(ts), "bad write request");
ASSERT_LE(ts.outlen, 1500, "outlen too large");
uint8_t buf[1500];
memcpy(buf, ts.out, ts.outlen);
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_too_large(void) {
BEGIN_TEST;
test_state ts;
ts.reset(1024, 1024, 1500);
uint8_t buf[1024] = { 0, 2, };
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_no_tsize(void) {
BEGIN_TEST;
test_state ts;
ts.reset(1024, 1024, 1500);
uint8_t buf[] = {
0x00, 0x02, // Opcode (WRQ)
'f', 'i', 'l', 'e', 'n', 'a', 'm', 'e', 0x00, // Filename
'O', 'C', 'T', 'E', 'T', 0x00, // Mode
};
auto status = tftp_process_msg(ts.session, buf, sizeof(buf), 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_send_oack(void) {
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, kFilename, strlen(kFilename), "bad filename");
EXPECT_EQ(size, 1024, "bad file size");
return 0;
};
tftp_file_interface ifc = {NULL, 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);
uint8_t buf[] = {
0x00, 0x02, // Opcode (WRQ)
'f', 'i', 'l', 'e', 'n', 'a', 'm', 'e', 0x00, // Filename
'O', 'C', 'T', 'E', 'T', 0x00, // Mode
'T', 'S', 'I', 'Z', 'E', 0x00, // Option
'1', '0', '2', '4', 0x00, // TSIZE value
};
auto status = tftp_process_msg(ts.session, buf, sizeof(buf), ts.out, &ts.outlen, &ts.timeout, nullptr);
EXPECT_EQ(TFTP_NO_ERROR, status, "receive write request failed");
EXPECT_EQ(RECV_WRQ, ts.session->state, "tftp session in wrong state");
EXPECT_EQ(1024, 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");
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 wrq");
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 wrq");
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 wrq");
END_TEST;
}
static tftp_status dummy_open_write(const char* filename, size_t size, void* cookie) {
return 0;
}
static bool test_tftp_receive_wrq_blocksize(void) {
constexpr size_t kBlocksize = 1024;
BEGIN_TEST;
test_state ts;
ts.reset(1024, 1024, 1500);
tftp_file_interface ifc = {NULL, dummy_open_write, NULL, NULL, NULL};
tftp_session_set_file_interface(ts.session, &ifc);
uint16_t window_size = 33;
tftp_status status = tftp_set_options(ts.session, NULL, NULL, &window_size);
ASSERT_EQ(TFTP_NO_ERROR, status, "failed to set server options");
uint8_t buf[] = {
0x00, 0x02, // Opcode (WRQ)
'f', 'i', 'l', 'e', 'n', 'a', 'm', 'e', 0x00, // Filename
'O', 'C', 'T', 'E', 'T', 0x00, // Mode
'T', 'S', 'I', 'Z', 'E', 0x00, // Option
'1', '0', '2', '4', 0x00, // TSIZE value
'B', 'L', 'K', 'S', 'I', 'Z', 'E', 0x00, // Option
'1', '0', '2', '4', 0x00, // BLKSIZE value
};
status = tftp_process_msg(ts.session, buf, sizeof(buf), ts.out, &ts.outlen, &ts.timeout, nullptr);
EXPECT_EQ(TFTP_NO_ERROR, status, "receive write request failed");
EXPECT_EQ(kBlocksize, ts.session->block_size, "bad session: block size");
EXPECT_EQ(DEFAULT_TIMEOUT, ts.session->timeout, "bad session: timeout");
// Verify that server options are ignored when the client doesn't specify them
EXPECT_EQ(DEFAULT_WINDOWSIZE, ts.session->window_size, "bad session: window size");
EXPECT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response");
const char* msg = static_cast<const char*>(ts.out);
const char block_sz_str[] = { 'B', 'L', 'K', 'S', 'I', 'Z', 'E', '\0', '1', '0', '2', '4', '\0' };
EXPECT_TRUE(find_str_in_mem(block_sz_str, sizeof(block_sz_str), msg, ts.outlen),
"block size not acknowledged");
END_TEST;
}
static bool test_tftp_receive_wrq_timeout(void) {
constexpr uint8_t kTimeout = 5;
BEGIN_TEST;
test_state ts;
ts.reset(1024, 1024, 1500);
tftp_file_interface ifc = {NULL, dummy_open_write, NULL, NULL, NULL};
tftp_session_set_file_interface(ts.session, &ifc);
uint8_t buf[] = {
0x00, 0x02, // Opcode (WRQ)
'f', 'i', 'l', 'e', 'n', 'a', 'm', 'e', 0x00, // Filename
'O', 'C', 'T', 'E', 'T', 0x00, // Mode
'T', 'S', 'I', 'Z', 'E', 0x00, // Option
'1', '0', '2', '4', 0x00, // TSIZE value
'T', 'I', 'M', 'E', 'O', 'U', 'T', 0x00, // Option
'5', 0x00, // TIMEOUT value
};
auto status = tftp_process_msg(ts.session, buf, sizeof(buf), ts.out, &ts.outlen, &ts.timeout, nullptr);
EXPECT_EQ(TFTP_NO_ERROR, status, "receive write request failed");
EXPECT_EQ(DEFAULT_BLOCKSIZE, ts.session->block_size, "bad session: block size");
EXPECT_EQ(kTimeout, 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");
const char* msg = static_cast<const char*>(ts.out);
const char timeout_str[] = { 'T', 'I', 'M', 'E', 'O', 'U', 'T', '\0', '5', '\0' };
EXPECT_TRUE(find_str_in_mem(timeout_str, sizeof(timeout_str), msg, ts.outlen),
"timeout value not acknowledged");
END_TEST;
}
static bool test_tftp_receive_wrq_windowsize(void) {
constexpr uint8_t kWindowsize = 32;
BEGIN_TEST;
test_state ts;
ts.reset(1024, 1024, 1500);
tftp_file_interface ifc = {NULL, dummy_open_write, NULL, NULL, NULL};
tftp_session_set_file_interface(ts.session, &ifc);
uint8_t buf[] = {
0x00, 0x02, // Opcode (WRQ)
'f', 'i', 'l', 'e', 'n', 'a', 'm', 'e', 0x00, // Filename
'O', 'C', 'T', 'E', 'T', 0x00, // Mode
'T', 'S', 'I', 'Z', 'E', 0x00, // Option
'1', '0', '2', '4', 0x00, // TSIZE value
'W', 'I', 'N', 'D', 'O', 'W', 'S', 'I', 'Z', 'E', 0x00, // Option
'3', '2', 0x00, // WINDOWSIZE value
};
auto status = tftp_process_msg(ts.session, buf, sizeof(buf), ts.out, &ts.outlen, &ts.timeout, nullptr);
EXPECT_EQ(TFTP_NO_ERROR, status, "receive write request failed");
EXPECT_EQ(DEFAULT_BLOCKSIZE, ts.session->block_size, "bad session: block size");
EXPECT_EQ(DEFAULT_TIMEOUT, ts.session->timeout, "bad session: timeout");
EXPECT_EQ(kWindowsize, ts.session->window_size, "bad session: window size");
EXPECT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response");
const char* msg = static_cast<const char*>(ts.out);
const char win_sz_str[] = { 'W', 'I', 'N', 'D', 'O', 'W', 'S', 'I', 'Z', 'E', '\0', '3', '2', '\0' };
EXPECT_TRUE(find_str_in_mem(win_sz_str, sizeof(win_sz_str), msg, ts.outlen),
"window size not acknowledged");
END_TEST;
}
// 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 kWindowSize = 16;
constexpr uint8_t kTimeout = 7;
constexpr uint16_t kBlockSize = 302;
BEGIN_TEST;
test_state ts;
ts.reset(1024, 1024, 1500);
tftp_file_interface ifc = {NULL, dummy_open_write, NULL, NULL, NULL};
tftp_session_set_file_interface(ts.session, &ifc);
uint16_t win_sz_override = kWindowSize;
uint8_t timeout_override = kTimeout;
uint16_t blk_sz_override = kBlockSize;
tftp_status status;
status = tftp_set_options(ts.session, &blk_sz_override, &timeout_override, &win_sz_override);
EXPECT_EQ(TFTP_NO_ERROR, status, "unable to set override options");
EXPECT_EQ(kBlockSize, ts.session->options.block_size, "override block size not set");
EXPECT_EQ(kTimeout, ts.session->options.timeout, "override timeout not set");
EXPECT_EQ(kWindowSize, ts.session->options.window_size, "override window size not set");
uint8_t buf[] = {
0x00, 0x02, // Opcode (WRQ)
'f', 'i', 'l', 'e', 'n', 'a', 'm', 'e', 0x00, // Filename
'O', 'C', 'T', 'E', 'T', 0x00, // Mode
'T', 'S', 'I', 'Z', 'E', 0x00, // Option
'1', '0', '2', '4', 0x00, // TSIZE value
'W', 'I', 'N', 'D', 'O', 'W', 'S', 'I', 'Z', 'E', 0x00, // Option
'3', '2', 0x00, // WINDOWSIZE value
'T', 'I', 'M', 'E', 'O', 'U', 'T', 0x00, // Option
'9', 0x00, // TIMEOUT value
'B', 'L', 'K', 'S', 'I', 'Z', 'E', 0x00, // Option
'1', '4', '3', 0x00, // BLKSIZE value
};
status = tftp_process_msg(ts.session, buf, sizeof(buf), ts.out, &ts.outlen, &ts.timeout, nullptr);
EXPECT_EQ(TFTP_NO_ERROR, status, "receive write request failed");
EXPECT_EQ(kBlockSize, ts.session->block_size, "bad session: block size");
EXPECT_EQ(kTimeout, ts.session->timeout, "bad session: timeout");
EXPECT_EQ(kWindowSize, ts.session->window_size, "bad session: window size");
EXPECT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response");
const char* msg = static_cast<const char*>(ts.out);
const char win_sz_str[] = { 'W', 'I', 'N', 'D', 'O', 'W', 'S', 'I', 'Z', 'E', '\0', '1', '6', '\0' };
EXPECT_TRUE(find_str_in_mem(win_sz_str, sizeof(win_sz_str), msg, ts.outlen),
"window size override value not in response");
const char timeout_str[] = { 'T', 'I', 'M', 'E', 'O', 'U', 'T', '\0', '7', '\0' };
EXPECT_TRUE(find_str_in_mem(timeout_str, sizeof(timeout_str), msg, ts.outlen),
"timeout override value not in response");
const char block_sz_str[] = { 'B', 'L', 'K', 'S', 'I', 'Z', 'E', '\0', '3', '0', '2', '\0' };
EXPECT_TRUE(find_str_in_mem(block_sz_str, sizeof(block_sz_str), msg, ts.outlen),
"block size override value not in response");
END_TEST;
}
// 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) {
BEGIN_TEST;
constexpr uint16_t kWindowSize = 55;
constexpr uint8_t kTimeout = 6;
constexpr uint16_t kBlockSize = 1111;
test_state ts;
ts.reset(1024, 1024, 1500);
tftp_file_interface ifc = {NULL, dummy_open_write, NULL, NULL, NULL};
tftp_session_set_file_interface(ts.session, &ifc);
uint16_t win_sz_override = kWindowSize;
uint8_t timeout_override = kTimeout;
uint16_t blk_sz_override = kBlockSize;
tftp_status status;
status = tftp_set_options(ts.session, &blk_sz_override, &timeout_override, &win_sz_override);
EXPECT_EQ(TFTP_NO_ERROR, status, "unable to set override options");
EXPECT_EQ(kBlockSize, ts.session->options.block_size, "override block size not set");
EXPECT_EQ(kTimeout, ts.session->options.timeout, "override timeout not set");
EXPECT_EQ(kWindowSize, ts.session->options.window_size, "override window size not set");
uint8_t buf[] = {
0x00, 0x02, // Opcode (WRQ)
'f', 'i', 'l', 'e', 'n', 'a', 'm', 'e', 0x00, // Filename
'O', 'C', 'T', 'E', 'T', 0x00, // Mode
'T', 'S', 'I', 'Z', 'E', 0x00, // Option
'1', '0', '2', '4', 0x00, // TSIZE value
'W', 'I', 'N', 'D', 'O', 'W', 'S', 'I', 'Z', 'E', '!', 0x00, // Option
'3', '2', 0x00, // WINDOWSIZE value
'T', 'I', 'M', 'E', 'O', 'U', 'T', '!', 0x00, // Option
'9', 0x00, // TIMEOUT value
'B', 'L', 'K', 'S', 'I', 'Z', 'E', '!', 0x00, // Option
'1', '4', '3', 0x00, // BLKSIZE value
};
status = tftp_process_msg(ts.session, buf, sizeof(buf), ts.out, &ts.outlen, &ts.timeout, nullptr);
EXPECT_EQ(TFTP_NO_ERROR, status, "receive write request failed");
EXPECT_EQ(32, ts.session->window_size, "bad session: window size");
EXPECT_EQ(9, ts.session->timeout, "bad session: timeout");
EXPECT_EQ(143, ts.session->block_size, "bad session: block size");
EXPECT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response");
const char* msg = static_cast<const char*>(ts.out);
const char win_sz_str[] = { 'W', 'I', 'N', 'D', 'O', 'W', 'S', 'I', 'Z', 'E', '\0', '3', '2', '\0' };
EXPECT_TRUE(find_str_in_mem(win_sz_str, sizeof(win_sz_str), msg, ts.outlen),
"window size confirmation not in response");
const char timeout_str[] = { 'T', 'I', 'M', 'E', 'O', 'U', 'T', '\0', '9', '\0' };
EXPECT_TRUE(find_str_in_mem(timeout_str, sizeof(timeout_str), msg, ts.outlen),
"timeout value confirmation not in response");
const char block_sz_str[] = { 'B', 'L', 'K', 'S', 'I', 'Z', 'E', '\0', '1', '4', '3', '\0' };
EXPECT_TRUE(find_str_in_mem(block_sz_str, sizeof(block_sz_str), msg, ts.outlen),
"block size confirmation not in response");
END_TEST;
}
static bool test_tftp_receive_force_wrq_have_overrides(void) {
BEGIN_TEST;
test_state ts;
ts.reset(1024, 1024, 1500);
tftp_file_interface ifc = {NULL, dummy_open_write, NULL, NULL, NULL};
tftp_session_set_file_interface(ts.session, &ifc);
// Option strings should be case insensitive
uint8_t buf[] = {
0x00, 0x02, // Opcode (WRQ)
'f', 'i', 'l', 'e', 'n', 'a', 'm', 'e', 0x00, // Filename
'o', 'c', 't', 'e', 't', 0x00, // Mode
't', 's', 'i', 'z', 'e', 0x00, // Option
'1', '0', '2', '4', 0x00, // TSIZE value
'W', 'i', 'n', 'd', 'o', 'w', 'S', 'i', 'z', 'e', '!', 0x00, // Option
'3', '2', 0x00, // WINDOWSIZE value
'T', 'i', 'm', 'e', 'o', 'u', 't', '!', 0x00, // Option
'9', 0x00, // TIMEOUT value
'B', 'l', 'k', 'S', 'i', 'z', 'e', '!', 0x00, // Option
'1', '4', '3', 0x00, // BLKSIZE value
};
auto status = tftp_process_msg(ts.session, buf, sizeof(buf), ts.out, &ts.outlen, &ts.timeout, nullptr);
EXPECT_EQ(TFTP_NO_ERROR, status, "receive write request failed");
EXPECT_EQ(32, ts.session->window_size, "bad session: window size");
EXPECT_EQ(9, ts.session->timeout, "bad session: timeout");
EXPECT_EQ(143, ts.session->block_size, "bad session: block size");
EXPECT_TRUE(verify_response_opcode(ts, OPCODE_OACK), "bad response");
const char* msg = static_cast<const char*>(ts.out);
const char win_sz_str[] = { 'W', 'I', 'N', 'D', 'O', 'W', 'S', 'I', 'Z', 'E', '\0', '3', '2', '\0' };
EXPECT_TRUE(find_str_in_mem(win_sz_str, sizeof(win_sz_str), msg, ts.outlen),
"window size confirmation not in response");
const char timeout_str[] = { 'T', 'I', 'M', 'E', 'O', 'U', 'T', '\0', '9', '\0' };
EXPECT_TRUE(find_str_in_mem(timeout_str, sizeof(timeout_str), msg, ts.outlen),
"timeout value confirmation not in response");
const char block_sz_str[] = { 'B', 'L', 'K', 'S', 'I', 'Z', 'E', '\0', '1', '4', '3', '\0' };
EXPECT_TRUE(find_str_in_mem(block_sz_str, sizeof(block_sz_str), msg, ts.outlen),
"block size confirmation not in response");
END_TEST;
}
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");
EXPECT_EQ(td.expected.block, msg->block, "bad block number");
EXPECT_BYTES_EQ(td.expected.data, msg->data, td.actual.len, "read data mismatch");
END_HELPER;
}
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_oack(void) {
BEGIN_TEST;
test_state ts;
ts.reset(1024, 1024, 1500);
auto status = tftp_generate_write_request(ts.session, kFilename, MODE_OCTET,
ts.msg_size, NULL, NULL, NULL, 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");
uint8_t 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, buf, sizeof(buf), ts.out, &ts.outlen, &ts.timeout, &td);
EXPECT_FALSE(tftp_session_has_pending(ts.session), "session should not have pending data");
EXPECT_EQ(SENT_FIRST_DATA, ts.session->state, "session should be in state SENT_FIRST_DATA");
EXPECT_EQ(1024, 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_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_receive_oack_blocksize(void) {
uint16_t kBlockSize = 1024;
BEGIN_TEST;
test_state ts;
ts.reset(1024, 2048, 1500);
auto status = tftp_generate_write_request(ts.session, kFilename, MODE_OCTET,
ts.msg_size, &kBlockSize, NULL, NULL, 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
'2', '0', '4', '8', 0x00, // TSIZE value
'B', 'L', 'K', 'S', 'I', 'Z', 'E', 0x00, // Option
'1', '0', '2', '4', 0x00, // BLKSIZE value
};
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 = kBlockSize;
td.expected.data[kBlockSize - 1] = 'X';
status = tftp_process_msg(ts.session, buf, sizeof(buf), ts.out, &ts.outlen, &ts.timeout, &td);
EXPECT_EQ(SENT_FIRST_DATA, ts.session->state, "session should be in state SENT_FIRST_DATA");
EXPECT_EQ(2048, ts.session->file_size, "tftp session bad file size");
EXPECT_EQ(kBlockSize, 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_EQ(ts.outlen, sizeof(tftp_data_msg) + kBlockSize, "bad outlen");
EXPECT_TRUE(verify_read_data(ts, td), "bad test data");
END_TEST;
}
static bool test_tftp_receive_oack_timeout(void) {
uint8_t kTimeout = 5;
BEGIN_TEST;
test_state ts;
ts.reset(1024, 1024, 1500);
auto status = tftp_generate_write_request(ts.session, kFilename, MODE_OCTET,
ts.msg_size, NULL, &kTimeout, NULL, 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
'1', '0', '2', '4', 0x00, // TSIZE value
'T', 'I', 'M', 'E', 'O', 'U', 'T', 0x00, // Option
'5', 0x00, // TIMEOUT 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(SENT_FIRST_DATA, ts.session->state, "session should be in state SENT_FIRST_DATA");
EXPECT_EQ(1024, ts.session->file_size, "tftp session bad file size");
EXPECT_EQ(DEFAULT_BLOCKSIZE, ts.session->block_size, "bad session: block size");
EXPECT_EQ(kTimeout, ts.session->timeout, "bad session: timeout");
EXPECT_EQ(DEFAULT_WINDOWSIZE, ts.session->window_size, "bad session: window size");
EXPECT_EQ(ts.timeout, kTimeout * 1000U, "timeout should be set");
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_receive_oack_windowsize(void) {
uint16_t kWindowSize = 2;
BEGIN_TEST;
test_state ts;
ts.reset(1024, 4096, 1500);
auto status = tftp_generate_write_request(ts.session, kFilename, MODE_OCTET,
ts.msg_size, NULL, NULL, &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
'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, buf, sizeof(buf), ts.out, &ts.outlen, &ts.timeout, &td);
EXPECT_EQ(SENT_FIRST_DATA, ts.session->state, "session should be in state SENT_FIRST_DATA");
EXPECT_EQ(4096, 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(kWindowSize, ts.session->window_size, "bad session: window size");
EXPECT_EQ(ts.outlen, sizeof(tftp_data_msg) + DEFAULT_BLOCKSIZE, "bad outlen");
EXPECT_TRUE(verify_read_data(ts, td), "bad test data");
ASSERT_TRUE(tftp_session_has_pending(ts.session), "session should have 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;
td.expected.offset = DEFAULT_BLOCKSIZE;
td.expected.data[1] = 'X';
td.expected.data[DEFAULT_BLOCKSIZE - 2] = 'F';
status = tftp_prepare_data(ts.session, ts.out, &ts.outlen, &ts.timeout, &td);
EXPECT_EQ(ts.outlen, sizeof(tftp_data_msg) + DEFAULT_BLOCKSIZE, "bad outlen");
EXPECT_TRUE(verify_read_data(ts, td), "bad test data");
EXPECT_FALSE(tftp_session_has_pending(ts.session), "session should not have pending");
END_TEST;
}
// 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_write_request(ts.session, kFilename, 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(SENT_FIRST_DATA, ts.session->state, "session should be in state SENT_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;
EXPECT_EQ(td.expected.offset, td.actual.offset, "write offset mismatch");
EXPECT_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, kFilename, strlen(kFilename), "bad filename");
EXPECT_EQ(size, 1024, "bad file size");
return 0;
}, NULL, NULL, NULL};
tftp_session_set_file_interface(ts.session, &ifc);
uint8_t req_buf[] = {
0x00, 0x02, // Opcode (WRQ)
'f', 'i', 'l', 'e', 'n', 'a', 'm', 'e', 0x00, // Filename
'O', 'C', 'T', 'E', 'T', 0x00, // Mode
'T', 'S', 'I', 'Z', 'E', 0x00, // Option
'1', '0', '2', '4', 0x00, // TSIZE value
};
auto status = tftp_process_msg(ts.session, req_buf, sizeof(req_buf), ts.out, &ts.outlen, &ts.timeout, nullptr);
ASSERT_EQ(TFTP_NO_ERROR, status, "receive write request failed");
ASSERT_EQ(RECV_WRQ, 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)
0x01, 0x00, // 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, kFilename, strlen(kFilename), "bad filename");
EXPECT_EQ(size, 1024, "bad file size");
return 0;
}, NULL, NULL, NULL};
tftp_session_set_file_interface(ts.session, &ifc);
uint8_t req_buf[] = {
0x00, 0x02, // Opcode (WRQ)
'f', 'i', 'l', 'e', 'n', 'a', 'm', 'e', 0x00, // Filename
'O', 'C', 'T', 'E', 'T', 0x00, // Mode
'T', 'S', 'I', 'Z', 'E', 0x00, // Option
'1', '0', '2', '4', 0x00, // TSIZE value
};
auto status = tftp_process_msg(ts.session, req_buf, sizeof(req_buf), ts.out, &ts.outlen, &ts.timeout, nullptr);
ASSERT_EQ(TFTP_NO_ERROR, status, "receive write request failed");
ASSERT_EQ(RECV_WRQ, 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)
0x01, 0x00, // 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[2]++;
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[2]++;
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, kFilename, strlen(kFilename), "bad filename");
EXPECT_EQ(size, 2048, "bad file size");
return 0;
}, NULL, NULL, NULL};
tftp_session_set_file_interface(ts.session, &ifc);
uint8_t req_buf[] = {
0x00, 0x02, // Opcode (WRQ)
'f', 'i', 'l', 'e', 'n', 'a', 'm', 'e', 0x00, // Filename
'O', 'C', 'T', 'E', 'T', 0x00, // Mode
'T', 'S', 'I', 'Z', 'E', 0x00, // Option
'2', '0', '4', '8', 0x00, // TSIZE value
'B', 'L', 'K', 'S', 'I', 'Z', 'E', 0x00, // Option
'1', '0', '2', '4', 0x00, // BLKSIZE value
};
auto status = tftp_process_msg(ts.session, req_buf, sizeof(req_buf), ts.out, &ts.outlen, &ts.timeout, nullptr);
ASSERT_EQ(TFTP_NO_ERROR, status, "receive write request failed");
ASSERT_EQ(RECV_WRQ, 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)
0x01, 0x00, // 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, kFilename, strlen(kFilename), "bad filename");
EXPECT_EQ(size, 1025, "bad file size");
return 0;
}, NULL, NULL, NULL};
tftp_session_set_file_interface(ts.session, &ifc);
uint8_t req_buf[] = {
0x00, 0x02, // Opcode (WRQ)
'f', 'i', 'l', 'e', 'n', 'a', 'm', 'e', 0x00, // Filename
'O', 'C', 'T', 'E', 'T', 0x00, // Mode
'T', 'S', 'I', 'Z', 'E', 0x00, // Option
'1', '0', '2', '5', 0x00, // TSIZE value
'W', 'I', 'N', 'D', 'O', 'W', 'S', 'I', 'Z', 'E', 0x00, // Option
'2', 0x00, // WINDOWSIZE value
};
auto status = tftp_process_msg(ts.session, req_buf, sizeof(req_buf), ts.out, &ts.outlen, &ts.timeout, nullptr);
ASSERT_EQ(TFTP_NO_ERROR, status, "receive write request failed");
ASSERT_EQ(RECV_WRQ, 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)
0x01, 0x00, // 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[2]++;
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, kFilename, strlen(kFilename), "bad filename");
EXPECT_EQ(size, 1024, "bad file size");
return 0;
}, NULL, NULL, NULL};
tftp_session_set_file_interface(ts.session, &ifc);
uint8_t req_buf[] = {
0x00, 0x02, // Opcode (WRQ)
'f', 'i', 'l', 'e', 'n', 'a', 'm', 'e', 0x00, // Filename
'O', 'C', 'T', 'E', 'T', 0x00, // Mode
'T', 'S', 'I', 'Z', 'E', 0x00, // Option
'1', '0', '2', '4', 0x00, // TSIZE value
};
auto status = tftp_process_msg(ts.session, req_buf, sizeof(req_buf), ts.out, &ts.outlen, &ts.timeout, nullptr);
ASSERT_EQ(TFTP_NO_ERROR, status, "receive write request failed");
ASSERT_EQ(RECV_WRQ, 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)
0x02, 0x00, // 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(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(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, kFilename, strlen(kFilename), "bad filename");
EXPECT_EQ(size, 2048, "bad file size");
return 0;
}, NULL, NULL, NULL};
tftp_session_set_file_interface(ts.session, &ifc);
uint8_t req_buf[] = {
0x00, 0x02, // Opcode (WRQ)
'f', 'i', 'l', 'e', 'n', 'a', 'm', 'e', 0x00, // Filename
'O', 'C', 'T', 'E', 'T', 0x00, // Mode
'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
'3', 0x00, // WINDOWSIZE value
};
auto status = tftp_process_msg(ts.session, req_buf, sizeof(req_buf), ts.out, &ts.outlen, &ts.timeout, nullptr);
ASSERT_EQ(TFTP_NO_ERROR, status, "receive write request failed");
ASSERT_EQ(RECV_WRQ, 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)
0x01, 0x00, // 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[2]++;
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[2] = 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(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;
}
static bool test_tftp_receive_data_block_wrapping(void) {
BEGIN_TEST;
constexpr unsigned long kWrapAt = 0x3ffff;
constexpr int kBlockSize = 8;
constexpr unsigned long kFileSize = (kWrapAt + 2) * kBlockSize;
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, kFilename, strlen(kFilename), "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] = 0x02; // Opcode (WRQ)
int req_buf_sz = 2 + snprintf(&req_buf[2], sizeof(req_buf) - 2,
"%s%cOCTET%cTSIZE%c%lu%cBLKSIZE%c%d", kFilename,
'\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(RECV_WRQ, 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;
}
static bool test_tftp_send_data_receive_ack(void) {
BEGIN_TEST;
test_state ts;
ts.reset(1024, 1024, 1500);
auto status = tftp_generate_write_request(ts.session, kFilename, 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)
0x01, 0x00, // 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(SENT_DATA, ts.session->state, "session should be in state SENT_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_write_request(ts.session, kFilename, 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)
0x01, 0x00, // 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[2]++;
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[2]++;
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_write_request(ts.session, kFilename, 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(SENT_DATA, ts.session->state, "session should be in state SENT_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_write_request(ts.session, kFilename, 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)
0x02, 0x00, // 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(SENT_DATA, ts.session->state, "session should be in state SENT_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_write_request(ts.session, kFilename, 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;
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_write_request(ts.session, kFilename, 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;
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 = msg->block;
EXPECT_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 = 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, 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 = (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, msg->block, "incorrect DATA packet block");
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_settings)
RUN_TEST(test_tftp_generate_wrq_blocksize)
RUN_TEST(test_tftp_generate_wrq_timeout)
RUN_TEST(test_tftp_generate_wrq_windowsize)
END_TEST_CASE(tftp_generate_wrq)
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_oack)
RUN_TEST(test_tftp_receive_oack)
RUN_TEST(test_tftp_receive_oack_blocksize)
RUN_TEST(test_tftp_receive_oack_timeout)
RUN_TEST(test_tftp_receive_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)
int main(int argc, char* argv[]) {
return unittest_run_all_tests(argc, argv) ? 0 : -1;
}