From 3e2b4ec8575d33becb06b57cf1a0fbbd09a53f68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Frank=20K=C3=BChndel?= Date: Wed, 1 Jun 2022 19:14:02 +0200 Subject: TFTPFS: Add tests Update #4666. --- testsuites/fstests/tftpfs/init.c | 6980 ++++++++++++++++++++ testsuites/fstests/tftpfs/tftpfs_interactions.c | 984 +++ testsuites/fstests/tftpfs/tftpfs_interactions.h | 213 + .../fstests/tftpfs/tftpfs_udp_network_fake.c | 983 +++ .../fstests/tftpfs/tftpfs_udp_network_fake.h | 315 + 5 files changed, 9475 insertions(+) create mode 100644 testsuites/fstests/tftpfs/init.c create mode 100644 testsuites/fstests/tftpfs/tftpfs_interactions.c create mode 100644 testsuites/fstests/tftpfs/tftpfs_interactions.h create mode 100644 testsuites/fstests/tftpfs/tftpfs_udp_network_fake.c create mode 100644 testsuites/fstests/tftpfs/tftpfs_udp_network_fake.h (limited to 'testsuites') diff --git a/testsuites/fstests/tftpfs/init.c b/testsuites/fstests/tftpfs/init.c new file mode 100644 index 0000000000..12d6c92cbd --- /dev/null +++ b/testsuites/fstests/tftpfs/init.c @@ -0,0 +1,6980 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +/** + * @file + * + * @ingroup RTEMSTestSuiteTestsTFTPFS + * + * @brief This source file contains the implementation of tests for libtftpfs. + * + * The tested source files are: + * + @ref tftpfs.c "tftpfs.c: TFTP file system" + * + @ref tftpDriver.c "tftpDriver.c: TFTP client library" + * These tests focus on testing the UDP network interaction of libtftpfs. + */ + +/* + * Copyright (C) 2022 embedded brains GmbH (http://www.embedded-brains.de) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include /* malloc(), free() */ +#include /* isprint() */ +#include +#include /* mkdir(), open() */ +#include /* mkdir(), open() */ +#include /* AF_INET, SOCK_DGRAM */ +#include /* open() */ +#include /* read(), close(), rmdir() */ + +#include +#include /* mount(), RTEMS_FILESYSTEM_TYPE_TFTPFS */ +#include +#include +#include /* RTEMS_TEST_VERBOSITY */ +#include + +#include "tftpfs_udp_network_fake.h" +#include "tftpfs_interactions.h" +#include "tftp_driver.h" + +#define SERV_PORT 12345 +#define FIRST_TIMEOUT_MILLISECONDS 400 +#define TIMEOUT_MILLISECONDS 1000 +#define LARGE_BLOCK_SIZE TFTP_BLOCK_SIZE_MAX +#define SMALL_BLOCK_SIZE 12 +#define SMALL_WINDOW_SIZE 4 +#define T_no_more_interactions() T_assert_true( \ + _Tftp_Has_no_more_interactions(), \ + "The TFTP client skiped some final network interactions." \ +) + +/* + * Test fixture and text context + */ + +typedef struct tftp_test_context { + int fd0; /* File descriptor of a file read from or written to the tftpsfs */ + void *tftp_handle; /* TFTP client handle for this file transfer */ +} tftp_test_context; + +static const char *tftpfs_mount_point = "/tftp"; +static const char *tftpfs_ipv4_loopback = TFTP_KNOWN_IPV4_ADDR0_STR; +static const char *tftpfs_server0_name = TFTP_KNOWN_SERVER0_NAME; +static const char *tftpfs_server0_ipv4 = TFTP_KNOWN_SERVER0_IPV4; +static const char *tftpfs_file = "file.txt"; +static tftp_test_context tftp_context; + +static void mount_tftp_fs( const char *mount_point, const char *options ) +{ + int result; + + result = mkdir( mount_point, S_IRWXU | S_IRWXG | S_IRWXO ); + T_assert_eq_int( result, 0 ); + + result = mount( + "", + mount_point, + RTEMS_FILESYSTEM_TYPE_TFTPFS, + RTEMS_FILESYSTEM_READ_WRITE, + options + ); + T_assert_eq_int( result, 0 ); +} + +static void umount_tftp_fs( const char *mount_point ) +{ + int result; + + result = unmount( mount_point ); + T_assert_eq_int( result, 0 ); + + result = rmdir( mount_point ); + T_assert_eq_int( result, 0 ); +} + +static void setup_rfc1350( void *context ) +{ + tftp_test_context *ctx = context; + _Tftp_Reset(); + ctx->fd0 = -1; + ctx->tftp_handle = NULL; + mount_tftp_fs( tftpfs_mount_point, "verbose,rfc1350" ); +} + +static void teardown( void *context ) +{ + tftp_test_context *ctx = context; + if ( ctx->fd0 >= 0 ) { + close( ctx->fd0 ); + } + tftp_close( ctx->tftp_handle ); /* is a no-op if NULL */ + umount_tftp_fs( tftpfs_mount_point ); + _Tftp_Reset(); +} + +static const T_fixture fixture_rfc1350 = { + .setup = setup_rfc1350, + .stop = NULL, + .teardown = teardown, + .scope = NULL, + .initial_context = &tftp_context +}; + +static void setup_default_options( void *context ) +{ + tftp_test_context *ctx = context; + _Tftp_Reset(); + ctx->fd0 = -1; + ctx->tftp_handle = NULL; + mount_tftp_fs( tftpfs_mount_point, NULL ); +} + +static const T_fixture fixture_default_options = { + .setup = setup_default_options, + .stop = NULL, + .teardown = teardown, + .scope = NULL, + .initial_context = &tftp_context +}; + +static void setup_large_blocksize( void *context ) +{ + tftp_test_context *ctx = context; + _Tftp_Reset(); + ctx->fd0 = -1; + ctx->tftp_handle = NULL; + mount_tftp_fs( + tftpfs_mount_point, + "verbose,blocksize=" RTEMS_XSTRING(LARGE_BLOCK_SIZE) ",windowsize=1" + ); +} + +static const T_fixture fixture_large_blocksize = { + .setup = setup_large_blocksize, + .stop = NULL, + .teardown = teardown, + .scope = NULL, + .initial_context = &tftp_context +}; + +static void setup_small_opt_size( void *context ) +{ + tftp_test_context *ctx = context; + _Tftp_Reset(); + ctx->fd0 = -1; + ctx->tftp_handle = NULL; + mount_tftp_fs( + tftpfs_mount_point, + "blocksize=" RTEMS_XSTRING(SMALL_BLOCK_SIZE) + ",windowsize=" RTEMS_XSTRING(SMALL_WINDOW_SIZE) + ); +} + +static const T_fixture fixture_small_opt_size = { + .setup = setup_small_opt_size, + .stop = NULL, + .teardown = teardown, + .scope = NULL, + .initial_context = &tftp_context +}; + +static void setup_mount_point( void *context ) +{ + int result; + + _Tftp_Reset(); + result = mkdir( tftpfs_mount_point, S_IRWXU | S_IRWXG | S_IRWXO ); + T_assert_eq_int( result, 0 ); +} + +static void teardown_mount_point( void *context ) +{ + int result; + + result = rmdir( tftpfs_mount_point ); + T_assert_eq_int( result, 0 ); + _Tftp_Reset(); +} + +static const T_fixture fixture_mount_point = { + .setup = setup_mount_point, + .stop = NULL, + .teardown = teardown_mount_point, + .scope = NULL, + .initial_context = &tftp_context +}; + +/* + * Test helper functions + */ + +/* + * Produce an artificial file content to be able to compare the + * sent and the received file later on. + */ +static uint8_t get_file_content( size_t pos ) +{ + static const size_t frame_size = 100; + static const size_t num_size = 11; + static const size_t alpha_size = 53; + char buf[10]; + size_t remainder = pos % frame_size; + + switch ( remainder ) { + case 0: + case 1: + case 2: + sprintf( buf, "%9zu", pos - remainder ); + return buf[remainder]; + case 3: + case 7: + return '\''; + case 4: + case 5: + case 6: + sprintf( buf, "%9zu", pos - remainder ); + return buf[remainder-1]; + case 8: + case 9: + case 10: + sprintf( buf, "%9zu", pos - remainder ); + return buf[remainder-2]; + default: + pos -= ( pos / frame_size + 1 ) * num_size; + remainder = pos % alpha_size; + return ( remainder <= 'Z' - '@' ) ? + remainder + '@' : remainder - ( 'Z' - '@' + 1) + 'a'; + } +} + +/* + * Produce bad file content. + */ +static uint8_t get_bad_file_content( size_t pos ) +{ + static const char buf[] = "BAD!"; + return (uint8_t) buf[ pos % strlen( buf ) ]; +} + +static const char *create_tftpfs_path( + const char *sever_addr, + const char *file_name +) +{ + static char buffer[100]; + int len; + + len = snprintf( + buffer, + sizeof( buffer ), + "%s/%s:%s", + tftpfs_mount_point, + sever_addr, + file_name + ); + + T_quiet_gt_int( len, 0 ); + T_quiet_lt_int( len, (int) sizeof( buffer ) ); + return buffer; +} + +static int read_tftp_file( + const char *path, + size_t buffer_size, + size_t max_bytes, + int *fd +) +{ + char *data_buffer; + int result = 0; + int res; + ssize_t i; + ssize_t bytes = 1; + ssize_t bytes_total = 0; + int errno_store; + + T_log( T_VERBOSE, "File system: open( %s, O_RDONLY )", path ); + errno = 0; + *fd = open( path, O_RDONLY ); + errno_store = errno; + T_log( + T_VERBOSE, + "File system: [open( %s, O_RDONLY )] = fd:%d (errno = %d)", + path, + *fd, + errno + ); + + if ( *fd < 0 ) { + /* open() may intentionally fail (e.g. test for invalid server address) */ + T_log( T_VERBOSE, "File system: cannot open \"%s\" for reading", path ); + errno = errno_store; + result = -1; + } + + if ( *fd >= 0 ) { + data_buffer = malloc( buffer_size ); + + while ( bytes > 0 && max_bytes >= bytes ) { + errno = 0; + bytes = read( + *fd, + data_buffer, + ( max_bytes > buffer_size ) ? buffer_size : max_bytes + ); + errno_store = errno; + T_log( + T_VERBOSE, + "File system: [read( fd:%d, size=%zu )] = %zd (errno = %d)", + *fd, + ( max_bytes > buffer_size ) ? buffer_size : max_bytes, + bytes, + errno + ); + + if ( bytes > 0 ) { + max_bytes -= bytes; + for ( i = 0; i < bytes; ++i ) { + if ( data_buffer[i] != get_file_content( bytes_total + i ) ) { + T_true( + false, + "File system: wrong file content '%c' (expected '%c') " + "at position %zd", + (int) ( isprint( (int) data_buffer[i] ) ? data_buffer[i] : '?' ), + (int) get_file_content( bytes_total + i ), + bytes_total + i + ); + bytes = 0; + break; + } + } /* for */ + bytes_total += bytes; + } + if ( bytes == 0 ) { + result = (int) bytes_total; + } + if ( bytes < 0 ) { + /* read() may intentionally fail (e.g. test lost network connection) */ + T_log( + T_VERBOSE, + "File system: error reading from \"%s\" after %zd bytes", + path, + bytes_total + ); + result = (int) bytes_total; + } + } /* while */ + + free( data_buffer ); + } /* if */ + + if ( bytes > 0 ) { + T_log( + T_VERBOSE, + "File system: reader closes \"%s\" after %zd bytes", + path, + bytes_total + ); + result = (int) bytes_total; + } + + if ( *fd >= 0 ) { + res = close( *fd ); + T_log( + T_VERBOSE, + "File system: [close( %s (fd:%d) )] = %d", + path, + *fd, + res + ); + *fd = -1; + T_eq_int( res, 0 ); + } + + errno = errno_store; + return result; +} + +static int write_tftp_file( + const char *path, + size_t file_size, + size_t buffer_size, + int *fd ) +{ + char *data_buffer; + int result = 0; + int res; + ssize_t i; + ssize_t bytes; + ssize_t bytes_total = 0; + int errno_store; + + errno = 0; + T_log( T_VERBOSE, "File system: open( %s, O_WRONLY )", path ); + *fd = open( path, O_WRONLY ); + errno_store = errno; + T_log( + T_VERBOSE, + "File system: [open( %s, O_WRONLY )] = fd:%d (errno = %d)", + path, + *fd, + errno + ); + if ( *fd < 0 ) { + /* open() may intentionally fail (e.g. test for invalid server address) */ + T_log( T_VERBOSE, "File system: cannot open \"%s\" for writing", path ); + errno = errno_store; + result = -1; + } + + if ( *fd >= 0 ) { + data_buffer = malloc( buffer_size ); + + do { /* Try also to write files with 0 bytes size */ + bytes = ( file_size - bytes_total >= buffer_size ) ? + buffer_size : file_size - bytes_total; + for ( i = 0; i < bytes; ++i ) { + data_buffer[i] = get_file_content( bytes_total + i ); + } + errno = 0; + bytes = write( *fd, data_buffer, i ); + errno_store = errno; + T_log( + T_VERBOSE, + "File system: [write( fd:%d, size=%zd )] = %zd (errno = %d)", + *fd, + i, + bytes, + errno + ); + if ( bytes > 0 ) { + bytes_total += bytes; + result = (int) bytes_total; + } + if ( bytes != i ) { + /* write() may intentionally fail (e.g. test lost network connection) */ + T_log( + T_VERBOSE, + "File system: error writing to \"%s\" after %zd bytes", + path, + bytes_total + ); + break; + } + } while( bytes_total < file_size ); + + free( data_buffer ); + } /* if */ + + if ( *fd >= 0 ) { + res = close( *fd ); + if (res != 0) { + errno_store = errno; + result = res; + } + T_log( + T_VERBOSE, + "File system: [close( %s (fd:%d) )] = %d", + path, + *fd, + res + ); + *fd = -1; + } + + errno = errno_store; + return result; +} + +static int rdwt_tftp_client_file( + const char *hostname, + const char *filename, + bool is_for_reading, + ssize_t file_size, /* Only used when `is_for_reading == false` */ + const tftp_net_config *config, + void **tftp_handle +) +{ + const static size_t buffer_size = 4001; + char *data_buffer; + int res = 0; + ssize_t i; + ssize_t bytes = 1; + ssize_t bytes_total = 0; + int errno_store = 0; + + if ( *tftp_handle == NULL ) { + T_log( + T_VERBOSE, + "TFTP Client: tftp_open( \"%s\", \"%s\", %s, ... )", + hostname, + filename, + is_for_reading ? "read" : "write" + ); + res = tftp_open( + hostname, + filename, + is_for_reading, + config, + tftp_handle + ); + T_log( + T_VERBOSE, + "TFTP Client: [tftp_open( \"%s\", \"%s\", %s, ... )] = %d (handle:%p)", + hostname, + filename, + is_for_reading ? "read" : "write", + res, + *tftp_handle + ); + } else { + T_log( + T_VERBOSE, + "TFTP Client: \"%s\":\"%s\" already open for %s, handle: %p ", + hostname, + filename, + is_for_reading ? "read" : "write", + *tftp_handle + ); + } + + if ( res != 0 ) { + /* open() may intentionally fail (e.g. test for invalid server address) */ + T_log( + T_VERBOSE, + "TFTP client: cannot open \"%s\":\"%s\" for %s", + hostname, + filename, + is_for_reading ? "reading" : "writing" + ); + errno_store = res; + } else { + T_assert_not_null( *tftp_handle ); + } + + if ( *tftp_handle != NULL ) { + data_buffer = malloc( buffer_size ); + + if ( is_for_reading ) { + + /* Read file */ + while ( bytes > 0 ) { + errno = 0; + bytes = tftp_read( + *tftp_handle, + data_buffer, + buffer_size + ); + T_log( + T_VERBOSE, + "TFTP Client: [tftp_read( %p, size=%zu )] = %zd", + *tftp_handle, + buffer_size, + bytes + ); + + if ( bytes > 0 ) { + for ( i = 0; i < bytes; ++i ) { + if ( data_buffer[i] != get_file_content( bytes_total + i ) ) { + T_true( + false, + "FTP Client: wrong file content '%c' (expected '%c') at positon %zd", + (int) ( isprint( (int) data_buffer[i] ) ? data_buffer[i] : '?' ), + (int) get_file_content( bytes_total + i ), + bytes_total + i + ); + bytes = 0; + break; + } + } /* for */ + bytes_total += bytes; + } + if ( bytes < 0 ) { + /* read() may intentionally fail (e.g. test lost network connection) */ + T_log( + T_VERBOSE, + "TFTP Client: error reading from \"%s\":\"%s\" after %zd bytes", + hostname, + filename, + bytes_total + ); + errno_store = -bytes; + } + } /* while */ + } else { + + /* Write file */ + do { /* Try also to write files with 0 bytes size */ + bytes = ( file_size - bytes_total >= buffer_size ) ? + buffer_size : file_size - bytes_total; + for ( i = 0; i < bytes; ++i ) { + data_buffer[i] = get_file_content( bytes_total + i ); + } + errno = 0; + bytes = tftp_write( *tftp_handle, data_buffer, i ); + T_log( + T_VERBOSE, + "TFTP Client: [tftp_write( %p, size=%zd )] = %zd", + *tftp_handle, + i, + bytes + ); + if ( bytes > 0 ) { + bytes_total += bytes; + } else { + errno_store = -bytes; + } + if ( bytes != i ) { + /* write() may intentionally fail (e.g. test lost network connection) */ + T_log( + T_VERBOSE, + "TFTP Client: error writing to \"%s\":\"%s\" after %zd bytes", + hostname, + filename, + bytes_total + ); + break; + } + } while( bytes_total < file_size ); + } /* if ( is_for_reading ) */ + + free( data_buffer ); + } /* if ( *tftp_handle != NULL ) */ + + if ( *tftp_handle != NULL ) { + res = tftp_close( *tftp_handle ); + T_log( + T_VERBOSE, + "TFTP Client: [tftp_close( \"%s\":\"%s\" (handle:%p) )] = %d", + hostname, + filename, + *tftp_handle, + res + ); + *tftp_handle = NULL; /* Avoid that the fixture closes it again */ + T_eq_int( res, 0 ); + } /* if ( *tftp_handle != NULL ) */ + + errno = errno_store; + return (int) bytes_total; +} + +/* + * Unit test cases + */ + +/* + * This is a classical unit test for the function tftp_initialize_net_config(). + * Tests: + * * tftp_initialize_net_config() sets correct default values as defined + * in the documentation of the data structures tftp_net_config + * and tftp_options. + */ +T_TEST_CASE( tftp_initialize_net_config ) +{ + tftp_net_config config; + memset( &config, 0, sizeof( config ) ); + tftp_initialize_net_config( &config ); + T_eq_u16( config.retransmissions, 6 ); + T_eq_u16( config.server_port, 69 ); + T_eq_u32( config.timeout, 1000 ); + T_eq_u32( config.first_timeout, 400 ); + T_eq_u16( config.options.block_size, 1456 ); + T_eq_u16( config.options.window_size, 8 ); +} + +/* + * This is a classical unit test for the function tftp_initialize_net_config(). + * Tests: + * * tftp_initialize_net_config() does not crash when called with a + * NULL pointer. + */ +T_TEST_CASE( tftp_initialize_net_config_null ) +{ + tftp_initialize_net_config( NULL ); +} + +/* + * This is a classical unit test for the function tftp_open(). + * Tests: + * * tftp_open() returns an error when called with a NULL pointer + * for hostname. + */ +T_TEST_CASE_FIXTURE( tftp_open_null_hostname, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int res; + + res = tftp_open( + NULL, /* hostname */ + tftpfs_file, + true, /* is_for_reading */ + NULL, /* config */ + &ctx->tftp_handle + ); + T_eq_int( res, EINVAL ); + T_null( ctx->tftp_handle ); +} + +/* + * This is a classical unit test for the function tftp_open(). + * Tests: + * * tftp_open() returns an error when called with a NULL pointer + * for filename. + */ +T_TEST_CASE_FIXTURE( tftp_open_null_filename, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int res; + + res = tftp_open( + tftpfs_ipv4_loopback, + NULL, /* filename */ + true, /* is_for_reading */ + NULL, /* config */ + &ctx->tftp_handle + ); + T_eq_int( res, EINVAL ); + T_null( ctx->tftp_handle ); +} + +/* + * This is a classical unit test for the function tftp_open(). + * Tests: + * * tftp_open() returns an error when called with a NULL pointer + * for tftp_handle. + */ +T_TEST_CASE( tftp_open_null_tftp_handle ) +{ + int res; + + res = tftp_open( + tftpfs_ipv4_loopback, + tftpfs_file, + true, /* is_for_reading */ + NULL, /* config */ + NULL /* tftp_handle */ + ); + T_eq_int( res, EINVAL ); +} + +/* + * This is a classical unit test for the function tftp_open(). + * Tests: + * * tftp_open() returns an error when called with value 0 for + * option window_size. + */ +T_TEST_CASE_FIXTURE( tftp_open_illegal_window_size, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + tftp_net_config config; + int res; + + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + tftp_initialize_net_config( &config ); + config.options.window_size = 1 - 1; + + res = tftp_open( + tftpfs_ipv4_loopback, + tftpfs_file, + true, /* is_for_reading */ + &config, + &ctx->tftp_handle + ); + T_eq_int( res, EINVAL ); + T_null( ctx->tftp_handle ); + T_no_more_interactions(); +} + +/* + * This is a classical unit test for the function tftp_open(). + * Tests: + * * tftp_open() returns an error when called with a too small + * value for option block_size. + */ +T_TEST_CASE_FIXTURE( tftp_open_block_size_too_small, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + tftp_net_config config; + int res; + + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + tftp_initialize_net_config( &config ); + config.options.block_size = 8 - 1; + + res = tftp_open( + tftpfs_ipv4_loopback, + tftpfs_file, + true, /* is_for_reading */ + &config, + &ctx->tftp_handle + ); + T_eq_int( res, EINVAL ); + T_null( ctx->tftp_handle ); + T_no_more_interactions(); +} + +/* + * This is a classical unit test for the function tftp_open(). + * Tests: + * * tftp_open() returns an error when called with a too large + * value for option block_size. + */ +T_TEST_CASE_FIXTURE( tftp_open_block_size_too_large, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + tftp_net_config config; + int res; + + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + tftp_initialize_net_config( &config ); + config.options.block_size = 65464 + 1; + + res = tftp_open( + tftpfs_ipv4_loopback, + tftpfs_file, + false, /* is_for_reading */ + &config, + &ctx->tftp_handle + ); + T_eq_int( res, EINVAL ); + T_null( ctx->tftp_handle ); + T_no_more_interactions(); +} + +/* + * This is a classical unit test for the function tftp_read(). + * Tests: + * * tftp_read() returns an error when called with a NULL pointer + * for tftp_handle. + */ +T_TEST_CASE( tftp_read_null_tftp_handle ) +{ + char data_buffer[10]; + ssize_t res; + + res = tftp_read( + NULL, /* tftp_handle */ + data_buffer, + sizeof( data_buffer) + ); + T_eq_int( res, -EIO ); +} + +/* + * This is a classical unit test for the function tftp_read(). + * Tests: + * * tftp_read() returns an error when called with a NULL pointer + * for buffer. + */ +T_TEST_CASE( tftp_read_null_buffer ) +{ + int tftp_handle; + ssize_t res; + + res = tftp_read( + &tftp_handle, + NULL, /* buffer */ + 8 + ); + T_eq_int( res, -EIO ); +} + +/* + * This is a classical unit test for the function tftp_write(). + * Tests: + * * tftp_write() returns an error when called with a NULL pointer + * for tftp_handle. + */ +T_TEST_CASE( tftp_write_null_tftp_handle ) +{ + char data_buffer[10]; + ssize_t res; + + res = tftp_write( + NULL, /* tftp_handle */ + data_buffer, + sizeof( data_buffer) + ); + T_eq_int( res, -EIO ); +} + +/* + * This is a classical unit test for the function tftp_write(). + * Tests: + * * tftp_write() returns an error when called with a NULL pointer + * for buffer. + */ +T_TEST_CASE( tftp_write_null_buffer ) +{ + int tftp_handle; + ssize_t res; + + res = tftp_write( + &tftp_handle, + NULL, /* buffer */ + 8 + ); + T_eq_int( res, -EIO ); +} + +/* + * This is a classical unit test for the function tftp_close(). + * Tests: + * * tftp_close() returns 0 when called with a NULL pointer. + */ +T_TEST_CASE( tftp_close_null ) +{ + T_eq_int( tftp_close( NULL ), 0 ); +} + +/* + * This is a classical unit test for the function _Tftpfs_Parse_options(). + * Tests: + * * Parsing an empty string has no effects. + */ +T_TEST_CASE( _Tftpfs_Parse_options_empty ) +{ + size_t err_pos; + uint32_t flags = 0; + tftp_net_config config; + + tftp_initialize_net_config( &config ); + err_pos = _Tftpfs_Parse_options( "", &config, &flags ); + T_eq_sz( err_pos, 0 ); + T_eq_u16( config.options.block_size, TFTP_DEFAULT_BLOCK_SIZE ); + T_eq_u16( config.options.window_size, TFTP_DEFAULT_WINDOW_SIZE ); + T_eq_u32( flags, 0 ); +} + +/* + * This is a classical unit test for the function _Tftpfs_Parse_options(). + * Tests: + * * Providing an NULL pointer instead of a string has no effect. + */ +T_TEST_CASE( _Tftpfs_Parse_options_null ) +{ + size_t err_pos; + uint32_t flags = 0; + tftp_net_config config; + + tftp_initialize_net_config( &config ); + err_pos = _Tftpfs_Parse_options( NULL, &config, &flags ); + T_eq_sz( err_pos, 0 ); + T_eq_u16( config.options.block_size, TFTP_DEFAULT_BLOCK_SIZE ); + T_eq_u16( config.options.window_size, TFTP_DEFAULT_WINDOW_SIZE ); + T_eq_u32( flags, 0 ); +} + +/* + * This is a classical unit test for the function _Tftpfs_Parse_options(). + * Tests: + * * Option "verbose" has the desired effect. + */ +T_TEST_CASE( _Tftpfs_Parse_options_verbose ) +{ + size_t err_pos; + uint32_t flags = 0; + tftp_net_config config; + + tftp_initialize_net_config( &config ); + err_pos = _Tftpfs_Parse_options( "verbose", &config, &flags ); + T_eq_sz( err_pos, 0 ); + T_eq_u16( config.options.block_size, TFTP_DEFAULT_BLOCK_SIZE ); + T_eq_u16( config.options.window_size, TFTP_DEFAULT_WINDOW_SIZE ); + T_gt_u32( flags, 0 ); +} + +/* + * This is a classical unit test for the function _Tftpfs_Parse_options(). + * Tests: + * * Option "rfc1350" has the desired effect. + */ +T_TEST_CASE( _Tftpfs_Parse_options_rfc1350 ) +{ + size_t err_pos; + uint32_t flags = 0; + tftp_net_config config; + + tftp_initialize_net_config( &config ); + err_pos = _Tftpfs_Parse_options( "rfc1350", &config, &flags ); + T_eq_sz( err_pos, 0 ); + T_eq_u16( config.options.block_size, TFTP_RFC1350_BLOCK_SIZE ); + T_eq_u16( config.options.window_size, TFTP_RFC1350_WINDOW_SIZE ); + T_eq_u32( flags, 0 ); +} + +/* + * This is a classical unit test for the function _Tftpfs_Parse_options(). + * Tests: + * * Option "blocksize" has the desired effect. + */ +T_TEST_CASE( _Tftpfs_Parse_options_blocksize ) +{ + size_t err_pos; + uint32_t flags = 0; + tftp_net_config config; + + tftp_initialize_net_config( &config ); + err_pos = _Tftpfs_Parse_options( "blocksize=21", &config, &flags ); + T_eq_sz( err_pos, 0 ); + T_eq_u16( config.options.block_size, 21 ); + T_eq_u16( config.options.window_size, TFTP_DEFAULT_WINDOW_SIZE ); + T_eq_u32( flags, 0 ); +} + +/* + * This is a classical unit test for the function _Tftpfs_Parse_options(). + * Tests: + * * Option "windowsize" has the desired effect. + */ +T_TEST_CASE( _Tftpfs_Parse_options_windowsize ) +{ + size_t err_pos; + uint32_t flags = 0; + tftp_net_config config; + + tftp_initialize_net_config( &config ); + err_pos = _Tftpfs_Parse_options( "windowsize=13", &config, &flags ); + T_eq_sz( err_pos, 0 ); + T_eq_u16( config.options.block_size, TFTP_DEFAULT_BLOCK_SIZE ); + T_eq_u16( config.options.window_size, 13 ); + T_eq_u32( flags, 0 ); +} + +/* + * This is a classical unit test for the function _Tftpfs_Parse_options(). + * Tests: + * * Processing of all options in one string works as expected. + */ +T_TEST_CASE( _Tftpfs_Parse_options_all ) +{ + size_t err_pos; + uint32_t flags = 0; + tftp_net_config config; + + tftp_initialize_net_config( &config ); + err_pos = _Tftpfs_Parse_options( "rfc1350,blocksize=1234,windowsize=4567,verbose", &config, &flags ); + T_eq_sz( err_pos, 0 ); + T_eq_u16( config.options.block_size, 1234 ); + T_eq_u16( config.options.window_size, 4567 ); + T_gt_u32( flags, 0 ); +} + +/* + * This is a classical unit test for the function _Tftpfs_Parse_options(). + * Tests: + * * Parser ignores unnecessary commas. + */ +T_TEST_CASE( _Tftpfs_Parse_options_surplus_comma ) +{ + size_t err_pos; + uint32_t flags = 0; + tftp_net_config config; + + tftp_initialize_net_config( &config ); + err_pos = _Tftpfs_Parse_options( ",blocksize=1234,,,,windowsize=4567,,", &config, &flags ); + T_eq_sz( err_pos, 0 ); + T_eq_u16( config.options.block_size, 1234 ); + T_eq_u16( config.options.window_size, 4567 ); + T_eq_u32( flags, 0 ); +} + +/* + * This is a classical unit test for the function _Tftpfs_Parse_options(). + * Tests: + * * Parser detects a bad value. + */ +T_TEST_CASE( _Tftpfs_Parse_options_bad_value ) +{ + size_t err_pos; + uint32_t flags = 0; + tftp_net_config config; + + tftp_initialize_net_config( &config ); + err_pos = _Tftpfs_Parse_options( "blocksize=123.4,windowsize=4567", &config, &flags ); + T_eq_sz( err_pos, 14 ); +} + +/* + * This is a classical unit test for the function _Tftpfs_Parse_options(). + * Tests: + * * Parser detects an illegal option. + */ +T_TEST_CASE( _Tftpfs_Parse_options_illegal_option ) +{ + size_t err_pos; + uint32_t flags = 0; + tftp_net_config config; + + tftp_initialize_net_config( &config ); + err_pos = _Tftpfs_Parse_options( "blocksize=123,illegal", &config, &flags ); + T_eq_sz( err_pos, 15 ); +} + +/* + * This is a classical unit test for the function _Tftpfs_Parse_options(). + * Tests: + * * Parser detects a truncated option. + */ +T_TEST_CASE( _Tftpfs_Parse_options_truncated_option ) +{ + size_t err_pos; + uint32_t flags = 0; + tftp_net_config config; + + tftp_initialize_net_config( &config ); + err_pos = _Tftpfs_Parse_options( "blocksize", &config, &flags ); + T_eq_sz( err_pos, 1 ); +} + +/* + * This is a classical unit test for the function rtems_tftpfs_initialize(). + * Tests: + * * Correct error handling in case mount options cannot be parsed. + */ +T_TEST_CASE_FIXTURE( mount_with_bad_options, &fixture_mount_point ) +{ + int result; + + result = mount( + "", + tftpfs_mount_point, + RTEMS_FILESYSTEM_TYPE_TFTPFS, + RTEMS_FILESYSTEM_READ_WRITE, + "windowsize=4567,blocksize=123bad" + ); + T_assert_le_int( result, -1 ); + T_assert_eq_int( errno, EINVAL ); +} + +/* + * Test cases for the TFTP client interface + * + * Since the TFTP file system uses the TFTP client interface for all + * file transfers, the function of the TFTP client is almost + * completely tested by the tests for the file system interface. + * The test cases here - for the TFTP client interface - test only + * those aspects not (easily) testable through the file system interface. + */ + +/* + * Read a file from the server using the TFTP client interface. + * The file is one byte long. No timeouts, packet loss, ... + * Tests: + * * tftp_open() called with NULL for config uses + * default configuration values. + * * Read a file using only the TFTP client (i.e. not using the + * file system) + */ +T_TEST_CASE_FIXTURE( client_open_with_NULL_config, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + uint16_t block_num = 1; + size_t pos_in_file = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + TFTP_DEFAULT_BLOCK_SIZE, + TFTP_DEFAULT_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + 1, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += 1; + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = rdwt_tftp_client_file( + tftpfs_ipv4_loopback, + tftpfs_file, + true, /* is_for_reading */ + -1, /* file_size for writing files only */ + NULL, /* config */ + &ctx->tftp_handle + ); + T_eq_sz( bytes_read, pos_in_file ); + T_eq_int( errno, 0 ); + T_no_more_interactions(); +} + +/* + * Read a very short file from the server using the TFTP client interface. + * The file is one data packet long. Use none-default configuration values. + * The second and the third DATA packets are lost. This causes + * a termination of the connection because only two retransmissions are + * configured. + * Tests: + * * tftp_open() called with all configuration values having + * none default values. + * * The test writes a file using only the TFTP client (i.e. not using the + * file system API). + * * The client uses the none default configuration values: + * retransmissions, server_port, timeout, first_timeout, + * block_size, window_size. + * * The server sends the options in a different order than the client. + * * The option names in the OACK can be upper or lower case. + * * If windowsize > 1, the client sends ACK only each windowsize packet. + * * If windowsize > 1 and no packet is received in the timeout period, + * the client retransmits the last ACK. + * * The client makes a limited number of retransmissions attempts + * and then terminates the connections with an error. + */ +T_TEST_CASE_FIXTURE( client_open_with_none_default_config, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + tftp_net_config config; + int bytes_read; + uint16_t block_num = 0; + size_t pos_in_file = 0; + uint16_t retransmissions = 2; + uint16_t server_port = 3456; + uint32_t timeout = 300; + uint32_t first_timeout = 200; + uint16_t block_size = 8; + uint16_t window_size = 2; + const char options[] = + "WINDOWSIZE" "\0" "2\0" + TFTP_OPTION_BLKSIZE "\0" "8"; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + server_port, + tftpfs_ipv4_loopback, + block_size, + window_size, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + first_timeout, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + first_timeout, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + pos_in_file, + block_size, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE; + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + first_timeout, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + block_size, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE; + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, + first_timeout /* Timeout: No packet received within timeout period */ + ); + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num - 1, /* Block number OK: Last block successfully received */ + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, + timeout /* Timeout: No packet received within timeout period */ + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_NO_USER, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + tftp_initialize_net_config( &config ); + config.retransmissions = retransmissions; + config.server_port = server_port; + config.timeout = timeout; + config.first_timeout = first_timeout; + config.options.block_size = block_size; + config.options.window_size = window_size; + + bytes_read = rdwt_tftp_client_file( + tftpfs_ipv4_loopback, + tftpfs_file, + true, /* is_for_reading */ + -1, /* file_size for writing files only */ + &config, + &ctx->tftp_handle + ); + + /* + * Not a bug but not nice: The client has received data before the connection + * breaks down due to timeout and this data is not provided to the user. + */ + + T_eq_sz( bytes_read, 0 ); + T_eq_int( errno, EIO ); + T_no_more_interactions(); +} + +/* + * Attempt to write to a file open for reading using the TFTP client interface. + * Tests: + * * tftp_open() called with NULL for config uses + * default configuration values. + * * Read a file using only the TFTP client (i.e. not using the + * file system) + * * The attempt to write to a file open for reading is rejected + * with an error. + * * The server receives an error message to indicate that the client + * closes the connection without having transferred data. + */ +T_TEST_CASE_FIXTURE( client_write_to_file_opened_for_reading, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int res = 0; + const char options[] = + TFTP_OPTION_BLKSIZE "\0" + RTEMS_XSTRING( TFTP_DEFAULT_BLOCK_SIZE ) "\0" + TFTP_OPTION_WINDOWSIZE"\0" + RTEMS_XSTRING( TFTP_DEFAULT_WINDOW_SIZE ); + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + TFTP_DEFAULT_BLOCK_SIZE, + TFTP_DEFAULT_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + /* Sending an ACK at this place before the ERROR would be OK, too. */ + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_NO_USER, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + res = tftp_open( + tftpfs_ipv4_loopback, + tftpfs_file, + true, /* is_for_reading */ + NULL, /* Config */ + &ctx->tftp_handle + ); + T_eq_int( res, 0 ); + T_assert_not_null( ctx->tftp_handle ); + + res = (int) tftp_write( ctx->tftp_handle, &res, 1 ); + T_eq_int( res, -EIO ); + + res = tftp_close( ctx->tftp_handle ); + ctx->tftp_handle = NULL; /* Avoid that the fixture closes it again */ + T_eq_int( res, 0 ); + T_no_more_interactions(); +} + +/* + * Attempt to read from a file open for writing using the TFTP client + * interface. + * Tests: + * * tftp_open() called with NULL for config uses + * default configuration values. + * * Read a file using only the TFTP client (i.e. not using the + * file system) + * * Attempt to read from a file open for writing is rejected with an error. + */ +T_TEST_CASE_FIXTURE( client_read_to_file_opened_for_writing, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int res = 0; + uint16_t block_num = 1; + size_t pos_in_file = 0; + const char options[] = + TFTP_OPTION_BLKSIZE "\0" + RTEMS_XSTRING( TFTP_DEFAULT_BLOCK_SIZE ) "\0" + TFTP_OPTION_WINDOWSIZE"\0" + RTEMS_XSTRING( TFTP_DEFAULT_WINDOW_SIZE ); + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + TFTP_DEFAULT_BLOCK_SIZE, + TFTP_DEFAULT_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + 0, /* Data size */ + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + pos_in_file += 0; + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + res = tftp_open( + tftpfs_ipv4_loopback, + tftpfs_file, + false, /* is_for_reading */ + NULL, /* config */ + &ctx->tftp_handle + ); + T_eq_int( res, 0 ); + T_assert_not_null( ctx->tftp_handle ); + + res = (int) tftp_read( ctx->tftp_handle, &res, 1 ); + T_eq_int( res, -EIO ); + + res = tftp_close( ctx->tftp_handle ); + ctx->tftp_handle = NULL; /* Avoid that the fixture closes it again */ + T_eq_int( res, 0 ); + T_no_more_interactions(); +} + +/* + * Write a file to the server using the TFTP client interface. + * The test uses the default options. + * The file is 2 and a half data packet long. No timeouts, packet loss, ... + * Tests: + * * The default options (windowsize = 8 and blocksize = 1456) are used. + * * tftp_open() is called with default configuration values. + * * The test writes a file using only the TFTP client (i.e. not using the + * file system) + * * The code supports the use of a server name instead of an IP address. + * * The first window is also the last window. + * * The only ACK packet is the one at the end of window. + * * Between sending data packets, the client checks whether any packets + * are received. + * * The client handles files correctly which end in the middle of a window. + */ +T_TEST_CASE_FIXTURE( client_write_simple_file, &fixture_default_options ) +{ + tftp_test_context *ctx = T_fixture_context(); + tftp_net_config config; + int bytes_written; + uint16_t block_num = 1; + size_t pos_in_file = 0; + const char options[] = + TFTP_OPTION_BLKSIZE "\0" + RTEMS_XSTRING( TFTP_DEFAULT_BLOCK_SIZE ) "\0" + TFTP_OPTION_WINDOWSIZE "\0" + RTEMS_XSTRING( TFTP_DEFAULT_WINDOW_SIZE ); + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_server0_ipv4, + TFTP_DEFAULT_BLOCK_SIZE, + TFTP_DEFAULT_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_server0_ipv4, + options, + sizeof( options ), + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num++, + pos_in_file, + TFTP_DEFAULT_BLOCK_SIZE, + get_file_content, + SERV_PORT, + tftpfs_server0_ipv4, + true + ); + pos_in_file += TFTP_DEFAULT_BLOCK_SIZE; + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, + DO_NOT_WAIT_FOR_ANY_TIMEOUT + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num++, + pos_in_file, + TFTP_DEFAULT_BLOCK_SIZE, + get_file_content, + SERV_PORT, + tftpfs_server0_ipv4, + true + ); + pos_in_file += TFTP_DEFAULT_BLOCK_SIZE; + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, + DO_NOT_WAIT_FOR_ANY_TIMEOUT + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + TFTP_DEFAULT_BLOCK_SIZE / 2, /* Data bytes in this block */ + get_file_content, + SERV_PORT, + tftpfs_server0_ipv4, + true + ); + pos_in_file += TFTP_DEFAULT_BLOCK_SIZE / 2; + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_server0_ipv4, + block_num++, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + tftp_initialize_net_config( &config ); + bytes_written = rdwt_tftp_client_file( + tftpfs_server0_name, + tftpfs_file, + false, /* is_for_reading */ + pos_in_file, /* file_size for writing files only */ + &config, + &ctx->tftp_handle + ); + T_eq_sz( bytes_written, pos_in_file ); + T_eq_int( errno, 0 ); + T_no_more_interactions(); +} + +/* + * Test cases for the file system interface + */ + +/* + * Read a file from the server using only RFC1350. + * The file is two and a half data packet long. No timeouts, packet loss, ... + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The first and second data packet are full. + * * The third data packet signals the end of transfer. + * * Read the file from file system in one big chunk of exactly + * the size of the file. + */ +T_TEST_CASE_FIXTURE( read_simple_file, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + uint16_t block_num = 1; + size_t pos_in_file = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE; + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE; + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE / 2, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE / 2; + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + /* Bytes read per call to read() */ + 2 * TFTP_RFC1350_BLOCK_SIZE + TFTP_RFC1350_BLOCK_SIZE / 2, + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( bytes_read, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Read a file from the server using only RFC1350. + * The file is one byte long. No timeouts, packet loss, ... + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The first data packet is not full and signals the end of the transfer. + * * The test reads a file from the file system in one-byte chunks. + */ +T_TEST_CASE_FIXTURE( read_tiny_file, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + uint16_t block_num = 1; + size_t pos_in_file = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + 1, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += 1; + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + 1, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( bytes_read, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Read a file from the server using only RFC1350. + * The file is one data packet long. No timeouts, packet loss, ... + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The first data packet is full. + * * The second data packet is empty and signals the end of the transfer. + * * The client handles an empty data packet correctly as end + * of file indicator. + * * The test reads a file from the file system in chunks of three quarters + * of the block size. + */ +T_TEST_CASE_FIXTURE( read_one_block_file, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + uint16_t block_num = 1; + size_t pos_in_file = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE; + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + 0, /* Number of bytes transferred */ + get_file_content, + true + ); + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + /* Bytes read per call to read() */ + TFTP_RFC1350_BLOCK_SIZE / 4 * 3, + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( bytes_read, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Read a file from the server using only RFC1350. + * The file is one data packet long. + * The client receives stray packets: + * * A packet from an unknown server (wrong TID) + * * An ACK packet instead of a DATA packet + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The first data packet is full. + * * The next received packet originates from a wrong TID + * (i.e. wrong connection). + * * Upon reception of a packet with a wrong TID, the client sends + * an ERROR message with code 5 and does not terminate the current + * transfer. + * * The final received packet is an ACK packet instead or the expected + * DATA packet. + * * Upon the reception of an unexpected packet, the client terminates + * the connection by sending an error packet to the server. + */ +T_TEST_CASE_FIXTURE( read_file_stray_packets, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + uint16_t block_num = 1; + size_t pos_in_file = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE; + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT + 1, /* Stray packet with wrong server TID */ + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_UNKNOWN_ID, + SERV_PORT + 1, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_ack( /* Stray ACK packet */ + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num - 1, + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_ILLEGAL, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + /* Bytes read per call to read() */ + TFTP_RFC1350_BLOCK_SIZE / 4 * 3, + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( errno, EPROTO ); + /* + * The client received one packet with TFTP_RFC1350_BLOCK_SIZE + * before the error occurred. The test reads in chunks of + * TFTP_RFC1350_BLOCK_SIZE /4 * 3. Thus, after the first chunk + * the client signals the error to the test. + * + * It would be a little improvement if the client would return all + * bytes received before signaling the error. + */ + T_eq_int( bytes_read, TFTP_RFC1350_BLOCK_SIZE / 4 * 3 ); + T_no_more_interactions(); +} + +/* + * Read a file from the server using only RFC1350. + * The file is one data packet long. + * The server sends an error message after the first DATA packet. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The client uses a short time out for all packets. + * * The client handles error packets from the server and stops the + * connection by signaling an error to the user on the file system side. + * * The test reads a file from the file system in chunks of three quarters + * of the block size. + */ +T_TEST_CASE_FIXTURE( read_one_block_file_server_error, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + uint16_t block_num = 1; + size_t pos_in_file = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE; + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_error( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + TFTP_ERROR_CODE_NO_ACCESS, + "Cannot read more", + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + /* Bytes read per call to read() */ + TFTP_RFC1350_BLOCK_SIZE / 4 * 3, + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( bytes_read, TFTP_RFC1350_BLOCK_SIZE / 4 * 3 ); + T_eq_int( errno, EPERM ); + T_no_more_interactions(); +} + +/* + * Read a file from the server using only RFC1350. + * The file is one data packet long. + * The server sends a malformed error packet after the first DATA packet. + * The error message in the packet is not a 0 terminated string + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The client handles malformed errors from the server and does not crash. + * * The test reads a file from the file system in chunks of three quarters + * of the block size. + */ +T_TEST_CASE_FIXTURE( read_one_block_file_malformed_server_error, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + uint16_t block_num = 1; + size_t pos_in_file = 0; + static const uint8_t packet_malformed_error[] = { + 0x00, 0x05, /* Opcode = TFTP_OPCODE_ERROR */ + 0x00, 0x02, /* Error code = TFTP_ERROR_CODE_NO_ACCESS */ + 'n', 'o', ' ', 'a', 'c', 'c', 'e', 's', 's' /* missing '\0' at the end */ + }; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE; + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_raw( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + sizeof( packet_malformed_error ), /* Malformed ERROR packet */ + packet_malformed_error, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + /* Bytes read per call to read() */ + TFTP_RFC1350_BLOCK_SIZE / 4 * 3, + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( bytes_read, TFTP_RFC1350_BLOCK_SIZE / 4 * 3 ); + T_eq_int( errno, EPERM ); + T_no_more_interactions(); +} + +/* + * Read a file from the server using only RFC1350. + * The reader on the file system side stops after having read half a + * data packet and before having received the whole file and closes the file. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The first data packet is full. + * * The client handles the closing of the file by the user correctly. + * * The client sends an error to the server after the user stops reading + * the file. + * * The test reads a file from the file system in chunks of block size. + */ +T_TEST_CASE_FIXTURE( read_one_block_close_file, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + uint16_t block_num = 1; + size_t pos_in_file = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + /* Sending an ACK at this place before the ERROR would be OK, too. */ + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_NO_USER, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + TFTP_RFC1350_BLOCK_SIZE / 4, /* Bytes read per call to read() */ + TFTP_RFC1350_BLOCK_SIZE / 2, /* Max bytes read from this file */ + &ctx->fd0 + ); + T_eq_int( bytes_read, TFTP_RFC1350_BLOCK_SIZE / 2 ); + T_no_more_interactions(); +} + +/* + * Read a file from the server using only RFC1350. + * The reader on the file system side just open()s and then immediately closes + * the file without ever reading a single byte. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The first data packet is full. + * * The client handles the closing of the file by the user correctly. + * * The client sends an error to the server when the user closes the file. + */ +T_TEST_CASE_FIXTURE( read_close_file_immediately, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + uint16_t block_num = 1; + size_t pos_in_file = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + /* Sending an ACK at this place before the ERROR would be OK, too. */ + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_NO_USER, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + TFTP_RFC1350_BLOCK_SIZE / 4, /* Bytes read per call to read() */ + 0, /* Max bytes read from this file */ + &ctx->fd0 + ); + T_eq_int( bytes_read, 0 ); + T_no_more_interactions(); +} + +/* + * Read an empty file from the server using only RFC1350. + * No timeouts, packet loss, ... + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of a server name instead of an IP address. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The client uses a short time out for all packets. + * * The first data packet has length 0. + * * The client can read empty files from the server. + * * The test reads a file from the file system in one big chunk which + * is larger than the file. + */ +T_TEST_CASE_FIXTURE( read_empty_file, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + uint16_t block_num = 1; + size_t pos_in_file = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_server0_ipv4, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_server0_ipv4, + block_num, + pos_in_file, + 0, /* Number of bytes transferred */ + get_file_content, + true + ); + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_server0_ipv4, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_server0_name, tftpfs_file ), + /* Bytes read per call to read() */ + TFTP_RFC1350_BLOCK_SIZE, + SIZE_MAX, + &ctx->fd0 + ); + T_assert_eq_int( bytes_read, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Read an empty file from the server using only RFC1350. + * The first two RRQ packets are lost. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of a server name instead of an IP address. + * * The first packet is sent to standard port 69 of server. + * * The client uses a short time out for first packets. + * * The client uses a longer time out for repeated packets. + * * The client repeats lost RRQs packets. + * * The client does not repeat the ACK packet for the last DATA packet + * which signals the end of transfer. + * * The first data packet is empty and signals the end of the transfer. + * * It is possible to read a file with 0 bytes content from + * the file system. + */ +T_TEST_CASE_FIXTURE( read_empty_file_looing_rrq, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + uint16_t block_num = 1; + size_t pos_in_file = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_server0_ipv4, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, /* Timeout: No packet received within timeout period */ + FIRST_TIMEOUT_MILLISECONDS + ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_server0_ipv4, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, /* Timeout: No packet received within timeout period */ + TIMEOUT_MILLISECONDS + ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_server0_ipv4, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_server0_ipv4, + block_num, + pos_in_file, + 0, /* Number of bytes transferred */ + get_file_content, + true + ); + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_server0_ipv4, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_server0_name, tftpfs_file ), + /* Bytes read per call to read() */ + TFTP_RFC1350_BLOCK_SIZE, + SIZE_MAX, + &ctx->fd0 + ); + T_assert_eq_int( bytes_read, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Read a file from the server using only RFC1350. + * The file is one and a half data packet long. + * Two data packet are lost (timeout) and the client must repeat the ACK. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * The client uses a short time out for first packets. + * * The client uses a longer time out for repeated packets. + * * The client repeats the ACK packets which are supposed + * to be lost. + * * The client does not repeat the ACK packet for the last DATA packet + * which signals the end of transfer. + * * The test reads a file in chunks of 17 bytes from file system. + */ +T_TEST_CASE_FIXTURE( read_small_file_lost_packets, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + uint16_t block_num = 1; + size_t pos_in_file = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE; + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, /* Timeout: No packet received within timeout period */ + FIRST_TIMEOUT_MILLISECONDS + ); + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, /* Timeout: No packet received within timeout period */ + TIMEOUT_MILLISECONDS + ); + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE / 2, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE / 2; + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + 17, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( bytes_read, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Read a file from the server using only RFC1350. + * The file is exactly one data packet long. + * The client receives a malformed DATA packet (wrong op code). + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The client uses a short time out for all packets. + * * The first data packet is full. + * * The client terminates the connection with an error message upon + * reception of a malformed packet. + */ +T_TEST_CASE_FIXTURE( read_small_file_malformed_packet_1, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + uint16_t block_num = 1; + size_t pos_in_file = 0; + static const uint8_t packet_illegal_opcode_1[] = { 0x00, 0xFF, 0x00, 0x00 }; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE; + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_raw( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + sizeof( packet_illegal_opcode_1 ), /* Malformed DATA packet */ + packet_illegal_opcode_1, + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_ILLEGAL, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + TFTP_RFC1350_BLOCK_SIZE / 4, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( bytes_read, pos_in_file ); + T_eq_int( errno, EPROTO ); + T_no_more_interactions(); +} + +/* + * Read a file from the server using only RFC1350. + * The file is exactly one data packet long. + * The client receives a malformed DATA packet (wrong op code). + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The first data packet is full. + * * The second data packet is malformed. + * * The client terminates the connection with an error message upon + * reception of a malformed packet. + */ +T_TEST_CASE_FIXTURE( read_small_file_malformed_packet_2, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + uint16_t block_num = 1; + size_t pos_in_file = 0; + static const uint8_t packet_illegal_opcode_2[] = { 0x03, 0x00, 0x00, 0x01 }; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE; + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_raw( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + sizeof( packet_illegal_opcode_2 ), /* Malformed DATA packet */ + packet_illegal_opcode_2, + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_ILLEGAL, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + TFTP_RFC1350_BLOCK_SIZE / 4, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( bytes_read, pos_in_file ); + T_eq_int( errno, EPROTO ); + T_no_more_interactions(); +} + +/* + * Read a file from the server using only RFC1350. + * The file is exactly one data packet long. + * The client receives a malformed packet. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The first DATA packet received after the RRQ packet is malformed. + * It is too short with only one byte length. + * * The client sends an error and terminates the file transfer upon + * reception of a malformed packet. + */ +T_TEST_CASE_FIXTURE( read_file_malformed_ack_1, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + size_t pos_in_file = 0; + static const uint8_t packet_too_short_1[] = { 0x03 }; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_raw( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + sizeof( packet_too_short_1 ), /* Malformed DATA packet */ + packet_too_short_1, + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_ILLEGAL, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + TFTP_RFC1350_BLOCK_SIZE / 4, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( bytes_read, pos_in_file ); + T_eq_int( errno, EPROTO ); + T_no_more_interactions(); +} + +/* + * Read a file from the server using only RFC1350. + * The file is exactly one data packet long. + * The client receives a malformed packet. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The first DATA packet received after the RRQ packet is malformed. + * It is too short with only two bytes length. + * * The client sends an error and terminates the file transfer upon + * reception of a malformed packet. + */ +T_TEST_CASE_FIXTURE( read_file_malformed_ack_2, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + size_t pos_in_file = 0; + static const uint8_t packet_too_short_2[] = { 0x00, 0x03 }; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_raw( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + sizeof( packet_too_short_2 ), /* Malformed DATA packet */ + packet_too_short_2, + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_ILLEGAL, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + TFTP_RFC1350_BLOCK_SIZE / 4, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( bytes_read, pos_in_file ); + T_eq_int( errno, EPROTO ); + T_no_more_interactions(); +} + +/* + * Read a file from the server using only RFC1350. + * The file is exactly one data packet long. + * The client receives a malformed packet. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The first DATA packet received after the RRQ packet is malformed. + * It is too short with only three bytes length. + * * The client sends an error and terminates the file transfer upon + * reception of a malformed packet. + */ +T_TEST_CASE_FIXTURE( read_file_malformed_ack_3, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + size_t pos_in_file = 0; + static const uint8_t packet_too_short_3[] = { 0x00, 0x03, 0x00 }; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_raw( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + sizeof( packet_too_short_3 ), /* Malformed DATA packet */ + packet_too_short_3, + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_ILLEGAL, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + TFTP_RFC1350_BLOCK_SIZE / 4, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( bytes_read, pos_in_file ); + T_eq_int( errno, EPROTO ); + T_no_more_interactions(); +} + +/* + * Read a file from the server using only RFC1350. + * The file is exactly one data packet long. + * The client receives a data packet with block number 0. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The first DATA packet received after the RRQ packet is malformed. + * It has block number 0. + * * The client sends an error and terminates the file transfer upon + * reception of a malformed packet. + */ +T_TEST_CASE_FIXTURE( read_file_block_number_0, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + size_t pos_in_file = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + 0, /* Wrong block number 0 */ + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_ILLEGAL, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + TFTP_RFC1350_BLOCK_SIZE / 4, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( bytes_read, pos_in_file ); + T_eq_int( errno, EPROTO ); + T_no_more_interactions(); +} + +/* + * Read a file from the server using only RFC1350. + * The file is exactly one data packet long. + * The client receives a malformed packet. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The first DATA packet received after the RRQ packet is malformed. + * The packet contains an illegal op code. + * * The client sends an error and terminates the file transfer upon + * reception of a malformed packet. + */ +T_TEST_CASE_FIXTURE( read_file_illegal_opcode_1, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + size_t pos_in_file = 0; + static const uint8_t packet_illegal_opcode_1[] = { 0x00, 0xFF, 0x00, 0x00 }; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_raw( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + sizeof( packet_illegal_opcode_1 ), /* Malformed DATA packet */ + packet_illegal_opcode_1, + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_ILLEGAL, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + TFTP_RFC1350_BLOCK_SIZE / 4, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( bytes_read, pos_in_file ); + T_eq_int( errno, EPROTO ); + T_no_more_interactions(); +} + +/* + * Read a file from the server using only RFC1350. + * The file is exactly two data packet long. + * The client receives DATA packets with wrong block numbers. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * The second RRQ is sent to the TFTP server port 69 and not to the + * port from which the first packet with the wrong block number came from. + * * The client uses a short time out for all packets. + * * The client uses a longer time out for repeated packets. + * * The client handles DATA packets with the wrong block numbers + * appropriately. + * * The third data packet is empty and signals the end of the transfer. + * * Old data packets are ignored (i.e. do not cause a retransmission). + * * Duplicates of the last data packet cause a retransmission of the + * last ACK. + * * The first data packet with a block number larger than the expected one, + * cause a retransmission of ACK or RRQ. (They can appear together + * with the windowsize option.) + * * The test reads a file from the file system in one big chunk with is larger + * than the files size. + */ +T_TEST_CASE_FIXTURE( read_two_block_file_wrong_block_numbers, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + uint16_t block_num = 1; + size_t pos_in_file = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num + 1, /* Wrong block number */ + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num + 1, /* Wrong block number */ + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE; + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num - 1, /* Wrong block number / duplicates last packet */ + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num - 1, /* Client assumes last ACK got lost and retransmits it. */ + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num + 1, /* Wrong block number */ + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num - 1, /* Client assumes last ACK got lost and retransmits it. */ + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num + 1, /* Wrong block number */ + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE; + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num - 2, /* Wrong block number */ + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num + 1, /* Wrong block number */ + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num - 1, /* Client assumes last ACK got lost and retransmits it. */ + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + 0, /* Number of bytes transferred */ + get_file_content, + true + ); + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + /* Bytes read per call to read() */ + 3 * TFTP_RFC1350_BLOCK_SIZE, + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( bytes_read, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Attempt to read a file from the server using filename without ':'. + * Tests: + * * The TFTP FS rejects malformed file names (i.e. it does not crash). + */ +T_TEST_CASE_FIXTURE( read_malformed_filename, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + char buffer[100]; + int len; + + len = snprintf( + buffer, + sizeof( buffer ), + "%s/%s", + tftpfs_mount_point, + tftpfs_server0_name + ); + + T_quiet_gt_int( len, 0 ); + T_quiet_lt_int( len, (int) sizeof( buffer ) ); + + bytes_read = read_tftp_file( + buffer, + /* Bytes read per call to read() */ + TFTP_RFC1350_BLOCK_SIZE, + SIZE_MAX, + &ctx->fd0 + ); + T_assert_eq_int( bytes_read, 0 ); + T_assert_eq_int( errno, EINVAL ); +} + +/* + * Attempt to read a file from a none exiting server address. + * Tests: + * * TFTP FS returns an error if the server name cannot be resolved. + */ +T_TEST_CASE_FIXTURE( read_from_unknown_ip_address, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + + bytes_read = read_tftp_file( + create_tftpfs_path( "not-existing-server-address", tftpfs_file ), + /* Bytes read per call to read() */ + TFTP_RFC1350_BLOCK_SIZE, + SIZE_MAX, + &ctx->fd0 + ); + T_assert_eq_int( bytes_read, 0 ); + T_assert_eq_int( errno, ENOENT ); +} + +/* + * Attempt to read a file which the server does not know + * No timeouts, packet loss, ... + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of a server name instead of an IP address. + * * The client handles an ERROR packet received upon sending an RRQ + * correctly. + * * TFTP FS returns an error upon the reception of the ERROR packet. + */ +T_TEST_CASE_FIXTURE( read_not_existing_file, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_server0_ipv4, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_error( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_server0_ipv4, + TFTP_ERROR_CODE_NOT_FOUND, + "No such file", + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_server0_name, tftpfs_file ), + /* Bytes read per call to read() */ + TFTP_RFC1350_BLOCK_SIZE, + SIZE_MAX, + &ctx->fd0 + ); + T_assert_eq_int( bytes_read, 0 ); + T_assert_eq_int( errno, ENOENT ); + T_no_more_interactions(); +} + +/* + * Write an empty file to the server using only RFC1350. + * The first two WRQ as well as the first two DATA packets are lost. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of a server name instead of an IP address. + * * The all WRQ are sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The client uses a short time out for first packets. + * * The client uses a longer time out for repeated packets. + * * The client repeats the WRQs and DATA packets which are supposed + * to be lost. + * * When a timeout occurs, the client repeats the last and empty packet. + * * The first data packet is empty and signals the end of the transfer. + * * The test writes a file with 0 bytes content to the file system. + */ +T_TEST_CASE_FIXTURE( write_empty_file_packet_losts, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_written; + uint16_t block_num = 0; + size_t pos_in_file = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_server0_ipv4, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, /* Timeout: No packet received within timeout period */ + FIRST_TIMEOUT_MILLISECONDS + ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_server0_ipv4, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, /* Timeout: No packet received within timeout period */ + TIMEOUT_MILLISECONDS + ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_server0_ipv4, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_server0_ipv4, + block_num++, + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + 0, /* Number of bytes transferred */ + get_file_content, + SERV_PORT, + tftpfs_server0_ipv4, + true + ); + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, /* Timeout: No packet received within timeout period */ + FIRST_TIMEOUT_MILLISECONDS + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + 0, /* Number of bytes transferred */ + get_file_content, + SERV_PORT, + tftpfs_server0_ipv4, + true + ); + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, /* Timeout: No packet received within timeout period */ + TIMEOUT_MILLISECONDS + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + 0, /* Number of bytes transferred */ + get_file_content, + SERV_PORT, + tftpfs_server0_ipv4, + true + ); + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_server0_ipv4, + block_num++, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_written = write_tftp_file( + create_tftpfs_path( tftpfs_server0_name, tftpfs_file ), + 0, /* Size of file */ + 4, /* Bytes written per call to write() */ + &ctx->fd0 + ); + T_eq_int( bytes_written, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Write a very short file to the server using only RFC1350. + * The file is one and half data packets long. + * The first two DATA packets and one ACK packet are lost. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of a server name instead of an IP address. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The client uses a short time out for first packets. + * * The client uses a longer time out for repeated packets. + * * The client repeats sending DATA packets which are supposed + * to be lost. + * * The client also repeats the last DATA packet when it is supposed + * to be lost. + * * The client repeats sending DATA packets when an ACK packet is repeated + * (presumably the DATA packet has been lost). + * * The test writes a file to the file system in chunks of a quarter of the block size. + */ +T_TEST_CASE_FIXTURE( write_tiny_file_packet_losts, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_written; + uint16_t block_num = 0; + size_t pos_in_file = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_server0_ipv4, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_server0_ipv4, + block_num++, + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, + get_file_content, + SERV_PORT, + tftpfs_server0_ipv4, + true + ); + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, /* Timeout: No packet received within timeout period */ + FIRST_TIMEOUT_MILLISECONDS + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, + get_file_content, + SERV_PORT, + tftpfs_server0_ipv4, + true + ); + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, /* Timeout: No packet received within timeout period */ + TIMEOUT_MILLISECONDS + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, + get_file_content, + SERV_PORT, + tftpfs_server0_ipv4, + true + ); + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_server0_ipv4, + block_num++, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE; + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE / 2, + get_file_content, + SERV_PORT, + tftpfs_server0_ipv4, + true + ); + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_server0_ipv4, + block_num - 1, /* Repeated ACK packet received */ + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE / 2, + get_file_content, + SERV_PORT, + tftpfs_server0_ipv4, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE / 2; + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_server0_ipv4, + block_num++, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_written = write_tftp_file( + create_tftpfs_path( tftpfs_server0_name, tftpfs_file ), + TFTP_RFC1350_BLOCK_SIZE / 2 * 3, /* Size of file */ + TFTP_RFC1350_BLOCK_SIZE / 4, /* Bytes written per call to write() */ + &ctx->fd0 + ); + T_eq_int( bytes_written, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Write a file to the server using only RFC1350. + * The file is 2 data packet and 1 byte long. No timeouts, packet loss, ... + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * First and second data packet is full. + * * Third data packet signals the end of transfer. + * * The test writes a file to the file system in one big chunk + * of exactly the files size. + */ +T_TEST_CASE_FIXTURE( write_simple_file, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_written; + uint16_t block_num = 0; + size_t pos_in_file = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE; + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE; + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + 1, /* Data bytes in this block */ + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + pos_in_file += 1; + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_written = write_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + pos_in_file, /* Size of file */ + pos_in_file, /* Bytes written per call to write() */ + &ctx->fd0 + ); + T_eq_int( bytes_written, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Write a file to the server using only RFC1350. + * As response to the first DATA packet, the server sends an error packet. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The first data packet is full. + * * The second packet from the server is an error packet. + * * The TFTP client ends the connection after receiving an error packet. + * * The test writes a file to the file system with a call to write() + * for each byte. + */ +T_TEST_CASE_FIXTURE( write_simple_file_disk_full, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_written; + uint16_t block_num = 0; + size_t pos_in_file = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE; + _Tftp_Add_interaction_recv_error( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + TFTP_ERROR_CODE_DISK_FULL, + "disk full", + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_written = write_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + pos_in_file, /* Size of file */ + 1, /* Bytes written per call to write() */ + &ctx->fd0 + ); + T_eq_int( errno, ENOSPC ); + T_eq_int( bytes_written, pos_in_file - 1 ); + T_no_more_interactions(); +} + +/* + * Write a file to the server using only RFC1350. + * The file is one and a half data packet long. + * The server sends a malformed packet. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The first ACK to the WRQ packet is malformed. + * It is only one byte long. + * * The client sends an error upon the reception of a malformed packet + * and terminates the file transfer. + */ +T_TEST_CASE_FIXTURE( write_file_malformed_ack_1, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_written; + size_t pos_in_file = 0; + static const uint8_t packet_too_short_1[] = { 0x04 }; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_raw( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + sizeof( packet_too_short_1 ), /* Malformed ACK packet */ + packet_too_short_1, + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_ILLEGAL, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_written = write_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + pos_in_file, /* Size of file */ + 17, /* Bytes written per call to write() */ + &ctx->fd0 + ); + T_eq_int( bytes_written, -1 ); + T_eq_int( errno, EPROTO ); + T_no_more_interactions(); +} + +/* + * Write a file to the server using only RFC1350. + * The file is one and a half data packet long. + * The server sends a malformed packet. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The first ACK to the WRQ packet is malformed. + * It is only two bytes long. + * * The client sends an error upon the reception of a malformed packet + * and terminates the file transfer. + */ +T_TEST_CASE_FIXTURE( write_file_malformed_ack_2, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_written; + size_t pos_in_file = 0; + static const uint8_t packet_too_short_2[] = { 0x00, 0x04 }; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_raw( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + sizeof( packet_too_short_2 ), /* Malformed ACK packet */ + packet_too_short_2, + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_ILLEGAL, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_written = write_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + pos_in_file, /* Size of file */ + 17, /* Bytes written per call to write() */ + &ctx->fd0 + ); + T_eq_int( bytes_written, -1 ); + T_eq_int( errno, EPROTO ); + T_no_more_interactions(); +} + +/* + * Write a file to the server using only RFC1350. + * The file is one and a half data packet long. + * The server sends a malformed packet. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The first ACK to the WRQ packet is malformed. + * It is only three bytes long. + * * The client sends an error upon the reception of a malformed packet + * and terminates the file transfer. + */ +T_TEST_CASE_FIXTURE( write_file_malformed_ack_3, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_written; + size_t pos_in_file = 0; + static const uint8_t packet_too_short_3[] = { 0x00, 0x04, 0x00 }; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_raw( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + sizeof( packet_too_short_3 ), /* Malformed ACK packet */ + packet_too_short_3, + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_ILLEGAL, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_written = write_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + pos_in_file, /* Size of file */ + 17, /* Bytes written per call to write() */ + &ctx->fd0 + ); + T_eq_int( bytes_written, -1 ); + T_eq_int( errno, EPROTO ); + T_no_more_interactions(); +} + +/* + * Write a file to the server using only RFC1350. + * The file is one and a half data packet long. + * The server sends a malformed packet. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The first ACK to the WRQ packet is malformed. + * The packet contains an illegal op code. + * * The client sends an error upon the reception of a malformed packet + * and terminates the file transfer. + */ +T_TEST_CASE_FIXTURE( write_file_illegal_opcode_1, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_written; + size_t pos_in_file = 0; + static const uint8_t packet_illegal_opcode_1[] = { 0x00, 0xFF, 0x00, 0x00 }; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_raw( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + sizeof( packet_illegal_opcode_1 ), /* Malformed ACK packet */ + packet_illegal_opcode_1, + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_ILLEGAL, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_written = write_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + pos_in_file, /* Size of file */ + 17, /* Bytes written per call to write() */ + &ctx->fd0 + ); + T_eq_int( bytes_written, -1 ); + T_eq_int( errno, EPROTO ); + T_no_more_interactions(); +} + +/* + * Write a file to the server using only RFC1350. + * The server sends a malformed packet. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * A malformed ACK packet is received by the TFTP client. + * The packet is only three bytes long. + * * The client sends an error upon the reception of a malformed packet + * and terminates the file transfer. + */ +T_TEST_CASE_FIXTURE( write_short_file_malformed_ACK_1, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_written; + uint16_t block_num = 0; + size_t pos_in_file = 0; + static const uint8_t packet_too_short_3[] = { 0x00, 0x04, 0x00 }; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_raw( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + sizeof( packet_too_short_3 ), /* Malformed ACK packet */ + packet_too_short_3, + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_ILLEGAL, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_written = write_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + 2 * TFTP_RFC1350_BLOCK_SIZE, /* Size of file */ + 17, /* Bytes written per call to write() */ + &ctx->fd0 + ); + T_eq_int( errno, EPROTO ); + T_eq_int( bytes_written, 510 ); + T_no_more_interactions(); +} + +/* + * Write a file to the server using only RFC1350. + * The server sends a malformed packet. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * A malformed ACK packet is received by the TFTP client after the first + * DATA packet has been exchanged successfully. + * The packet is only one byte long. + * * The client sends an error upon the reception of a malformed packet + * and terminates the file transfer. + */ +T_TEST_CASE_FIXTURE( write_short_file_malformed_ACK_2, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_written; + uint16_t block_num = 0; + size_t pos_in_file = 0; + static const uint8_t packet_too_short_1[] = { 0x04 }; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE; + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE / 4, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE / 4; + _Tftp_Add_interaction_recv_raw( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + sizeof( packet_too_short_1 ), /* Malformed ACK packet */ + packet_too_short_1, + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_ILLEGAL, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_written = write_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + pos_in_file, /* Size of file */ + 17, /* Bytes written per call to write() */ + &ctx->fd0 + ); + T_eq_int( errno, EPROTO ); + T_eq_int( bytes_written, -1 ); + T_no_more_interactions(); +} + +/* + * Write a file to the server using only RFC1350. + * The server sends a malformed packet. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * A malformed ACK packet is received by the TFTP client after the first + * DATA packet has been exchanged successfully. + * The packet contains an illegal op code. + * * The client sends an error upon the reception of a malformed packet + * and terminates the file transfer. + */ +T_TEST_CASE_FIXTURE( write_short_file_malformed_opcode, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_written; + uint16_t block_num = 0; + size_t pos_in_file = 0; + static const uint8_t packet_illegal_opcode_2[] = { 0x04, 0x00, 0x00, 0x01 }; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_raw( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + sizeof( packet_illegal_opcode_2 ), /* Malformed ACK packet */ + packet_illegal_opcode_2, + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_ILLEGAL, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_written = write_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + 2 * TFTP_RFC1350_BLOCK_SIZE, /* Size of file */ + 17, /* Bytes written per call to write() */ + &ctx->fd0 + ); + T_eq_int( errno, EPROTO ); + T_eq_int( bytes_written, 510 ); + T_no_more_interactions(); +} + +/* + * Write a file to the server using only RFC1350. + * The file is two and a half data packet long. + * The server sends packets with wrong block numbers. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The first ACK packet contains a wrong block number. + * * The client repeats the WRQ upon reception of an ACK with + * an too high block number. + * * The client uses a short time out for waiting on the answer of the + * first WRQ or DATA packets. + * * The client uses a long time out for waiting on the answer of + * repeated WRQ or DATA packets. + * * The first DATA packet is full. + * * The second DATA packet signals the end of transfer. + * * The client handles DATA packets with wrong block numbers + * appropriately. + * * The test writes a file to the file system with calls to write() of + * exactly block size. + */ +T_TEST_CASE_FIXTURE( write_short_file_bad_block_numbers, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_written; + uint16_t block_num = 0; + size_t pos_in_file = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num + 1, /* Wrong block number */ + true + ); + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num - 1, /* Wrong block number */ + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE; + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num - 2, /* Wrong block number */ + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE; + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE / 4, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num - 2, /* Wrong block number */ + true + ); + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num + 1, /* Wrong block number */ + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE / 4; + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_written = write_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + pos_in_file, /* Size of file */ + TFTP_RFC1350_BLOCK_SIZE, /* Bytes written per call to write() */ + &ctx->fd0 + ); + T_eq_int( bytes_written, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Write a file to the server using only RFC1350. + * The file is one data packet long. + * The client receives a stray packet from an unknown server (wrong TID). + * Directly afterwards the expected ACK packet is lost and + * the client must retransmit the original DATA packet. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The code supports the use of an IPv4 address instead of a server name. + * * The first packet is sent to standard port 69 of server. + * * All other packets are sent to the port used for this connection. + * * The client uses a short time out for waiting on the answer of the + * first DATA packet. + * * The client uses a long time out for waiting on the answer of + * the repeated DATA. + * * Upon reception of a packet with a wrong TID, the client sends + * an ERROR message with code 5 but does not terminate the current + * transfer. + * * When re-transmitting the an packet, the data is intact (i.e. + * not corrupted by the reception of any packet in-between). + */ +T_TEST_CASE_FIXTURE( write_one_block_file_stray_packets, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_written; + uint16_t block_num = 0; + size_t pos_in_file = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT + 1, /* Stray packet with wrong server TID */ + tftpfs_ipv4_loopback, + block_num, + 0, + TFTP_RFC1350_BLOCK_SIZE / 2, /* Number of bytes transferred */ + get_bad_file_content, + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_UNKNOWN_ID, + SERV_PORT + 1, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, /* Timeout: No packet received within timeout period */ + FIRST_TIMEOUT_MILLISECONDS + ); + _Tftp_Add_interaction_send_data( /* Retransmission of the DATA packet */ + TFTP_FIRST_FD, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE; + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + 0, /* Number of bytes */ + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + pos_in_file += 0; + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_written = write_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + pos_in_file, /* Size of file */ + TFTP_RFC1350_BLOCK_SIZE, /* Bytes written per call to write() */ + &ctx->fd0 + ); + T_eq_int( bytes_written, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Read a file from the server using option to increase the block size. + * The file is one data packet long. No timeouts, packet loss, ... + * Tests: + * * Only the blksize option appears in RRQ and OACK. + * * The client uses a block size which is larger than the default size. + * * The first data packet is full. + * * The second data packet is empty and signals the end of the transfer. + * * Client handles the empty data packet correctly as + * end of file indicator. + * * The test reads a file from the file system in chunks of block size. + */ +T_TEST_CASE_FIXTURE( read_file_one_large_block, &fixture_large_blocksize ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + uint16_t block_num = 0; + size_t pos_in_file = 0; + const char options[] = + TFTP_OPTION_BLKSIZE "\0" + RTEMS_XSTRING( LARGE_BLOCK_SIZE ); + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + LARGE_BLOCK_SIZE, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + LARGE_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += LARGE_BLOCK_SIZE; + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + 0, /* Number of bytes transferred */ + get_file_content, + true + ); + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + LARGE_BLOCK_SIZE, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( bytes_read, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Try to read a file from the server using a file name too large for a RRQ. + * Tests: + * * The client rejects an attempt to open a file with a too long + * file name is with an error. + */ +T_TEST_CASE_FIXTURE( read_too_long_file_name, &fixture_default_options ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + char buffer[TFTP_RFC1350_BLOCK_SIZE - + strlen( TFTP_MODE_OCTET ) - 1 - 5]; + int len; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + len = sizeof( buffer ) - strlen( tftpfs_mount_point ) - + strlen( tftpfs_ipv4_loopback ) - 2 - 4; + len = snprintf( + buffer, + sizeof( buffer ), + "%s/%s:%0*d", + tftpfs_mount_point, + tftpfs_ipv4_loopback, + len, + 123 + ); + T_quiet_gt_int( len, 0 ); + T_quiet_lt_int( len, (int) sizeof( buffer ) ); + + bytes_read = read_tftp_file( + buffer, + TFTP_DEFAULT_BLOCK_SIZE, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( bytes_read, 0 ); + T_eq_int( errno, ENAMETOOLONG ); + T_no_more_interactions(); +} + +/* + * Read a file using options but the server sends a DATA packet. + * The file is one byte long. No timeouts, packet loss, ... + * Tests: + * * The client uses windowsize and blksize option in the RRQ. + * * For the data transfer the client uses the RFC1350 option values + * because the server responded with a DATA packet. + * * The whole package sequence behaves as if only RFC1350 was used. + * * The first data packet contains a single byte and signals the end of the transfer. + * * The test reads a file from the file system in chunks of half block size. + */ +T_TEST_CASE_FIXTURE( read_file_DATA_instead_of_OACK, &fixture_default_options ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + uint16_t block_num = 1; + size_t pos_in_file = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + TFTP_DEFAULT_BLOCK_SIZE, + TFTP_DEFAULT_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + 1, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += 1; + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + TFTP_DEFAULT_BLOCK_SIZE / 2, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( bytes_read, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Read a file using RFC1350 but the server sends an OACK packet. + * Tests: + * * The code supports requests without options (RFC1350 only). + * * The server wrongly responds with an OACK which contains no options. + * * The client sends an error upon reception of an unexpected packet. + */ +T_TEST_CASE_FIXTURE( read_tiny_file_OACK_instead_of_DATA, &fixture_rfc1350 ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + const char options[] = {}; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_ILLEGAL, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + 1, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( bytes_read, 0 ); + T_eq_int( errno, EPROTO ); + T_no_more_interactions(); +} + +/* + * Read a file from the server using the default options. + * The file is 18 and a half data packet long. No timeouts, packet loss, ... + * Tests: + * * The client uses the default options + * (windowsize = 8 and blocksize = 1456). + * * The server send the options in the same order as the client did + * send them. + * * The ninetenth data packet signals the end of transfer. + * * The test reads a file from the file system in chunks of 2000 bytes. + */ +T_TEST_CASE_FIXTURE( read_file_with_default_options, &fixture_default_options ) +{ + tftp_test_context *ctx = T_fixture_context(); + int i; + int bytes_read; + uint16_t block_num = 0; + size_t pos_in_file = 0; + const char options[] = + TFTP_OPTION_BLKSIZE "\0" + RTEMS_XSTRING( TFTP_DEFAULT_BLOCK_SIZE ) "\0" + TFTP_OPTION_WINDOWSIZE"\0" + RTEMS_XSTRING( TFTP_DEFAULT_WINDOW_SIZE ); + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + TFTP_DEFAULT_BLOCK_SIZE, + TFTP_DEFAULT_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + while ( block_num < 16 ) { + for ( i = 0; i < TFTP_DEFAULT_WINDOW_SIZE; ++i ) { + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + ++block_num, + pos_in_file, + TFTP_DEFAULT_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += TFTP_DEFAULT_BLOCK_SIZE; + } + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + } + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + ++block_num, + pos_in_file, + TFTP_DEFAULT_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += TFTP_DEFAULT_BLOCK_SIZE; + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + ++block_num, + pos_in_file, + TFTP_DEFAULT_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += TFTP_DEFAULT_BLOCK_SIZE; + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + ++block_num, + pos_in_file, + TFTP_DEFAULT_BLOCK_SIZE / 2, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += TFTP_DEFAULT_BLOCK_SIZE / 2; + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + 2000, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( bytes_read, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Read a file following exactly the scenario in RFC 7440. + * This test uses window size and block size options. There are lost packets. + * Tests: + * * The client uses of non-default options + * (windowsize = 4 and blocksize = 12). + * * Test the scenario included in RFC 7440. + * * When a packet from the server is lost (client receives DATA packet with + * a too high block number), the client sends an ACK for the last package + * received in the correct sequence. + * * The client ignores duplicated packets (with block numbers it has + * already processed). + * * The data of the file ends exactly at a window size border (i.e. + * after the window a single empty DATA packet is sent by the server). + * * The test reads a file from the file system in chunks of 10 bytes. + */ +T_TEST_CASE_FIXTURE( read_file_rfc7440_scenario, &fixture_small_opt_size ) +{ + tftp_test_context *ctx = T_fixture_context(); + int i; + int bytes_read; + uint16_t block_num = 0; + size_t pos_in_file = 0; + const char options[] = + TFTP_OPTION_WINDOWSIZE"\0" + RTEMS_XSTRING( SMALL_WINDOW_SIZE ) "\0" + TFTP_OPTION_BLKSIZE "\0" + RTEMS_XSTRING( SMALL_BLOCK_SIZE ); + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + SMALL_BLOCK_SIZE, + SMALL_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + for ( i = 0; i < SMALL_WINDOW_SIZE; ++i ) { + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + ++block_num, + pos_in_file, + SMALL_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += SMALL_BLOCK_SIZE; + } + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + ++block_num, + pos_in_file, + SMALL_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += SMALL_BLOCK_SIZE; + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num + 2, /* Error: One packet from the server has been lost */ + pos_in_file, + SMALL_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + for ( i = 0; i < SMALL_WINDOW_SIZE; ++i ) { + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + ++block_num, + pos_in_file, + SMALL_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += SMALL_BLOCK_SIZE; + } + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + ++block_num, + pos_in_file, + SMALL_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += SMALL_BLOCK_SIZE; + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num + 2, /* Error: One packet from the server has been lost */ + pos_in_file, + SMALL_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num, /* The packet is assumed to be lost/does not reach the server */ + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num + 3, /* Error: One packet from the server has been lost */ + pos_in_file, + SMALL_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + block_num = 9; /* The ACK for DATA packet 10 did not reach the server */ + pos_in_file = block_num * SMALL_BLOCK_SIZE; + for ( i = 0; i < SMALL_WINDOW_SIZE; ++i ) { + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + ++block_num, + pos_in_file, + SMALL_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += SMALL_BLOCK_SIZE; + } + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + ++block_num, + pos_in_file, + 0, /* Number of bytes transferred */ + get_file_content, + true + ); + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + 10, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( bytes_read, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Read a file using windowsize = 4. All kinds of packet loss, out of order + * packets and duplicated packets occur. + * This test uses window size and block size options. It is a stress test + * for all kind of network problems which can appear during a transfer + * of a file with window size larger than 1. + * Tests: + * * The client uses non-default options (windowsize = 4 and blocksize = 12). + * * The first DATA packet of a window is lost. + * * The last DATA packet of a window is lost. + * * The middle DATA packets of a window is lost. + * * The first two DATA packets of a window are received in reverse order. + * * The middle two DATA packets of a window are received in reverse order. + * * The last two DATA packets of a window are received in reverse order. + * * The a DATA packet is received duplicated. + * * The an old DATA packet is received duplicated. + * * The a very old DATA packet is received duplicated. + * * The normal ACK of a window is not received by the server. + * * The server repeats a whole window (ensure the client sends an ACK + * packet despite of the repetition). + * * An ACK for an out-of-order packet is not received by the server. + * * Windows with errors appear consecutively without + * error free windows in between. + * * After the reception of the first two DATA packets of a window, + * a timeout occurs and the client must send an ACK. + * * File transfer ends exactly with the last packet of a window + * (the second last packet is full and the last one is empty). + * * The test reads a file from the file system in chunks 100 bytes. + */ +T_TEST_CASE_FIXTURE( read_file_windowsize_trouble, &fixture_small_opt_size ) +{ + tftp_test_context *ctx = T_fixture_context(); + int i; + int bytes_read; + uint16_t block_num = 0; + size_t pos_in_file = 0; + int timeout = FIRST_TIMEOUT_MILLISECONDS; + const char options[] = + TFTP_OPTION_WINDOWSIZE"\0" + RTEMS_XSTRING( SMALL_WINDOW_SIZE ) "\0" + TFTP_OPTION_BLKSIZE "\0" + RTEMS_XSTRING( SMALL_BLOCK_SIZE ); + /* + * A positive number is the number of a DATA packet received. + * A negative number is the number of an ACK packet send. + * A zero indicates a timeout when waiting for a DATA packet. + * Each line corresponds to a window. + */ + int16_t pkg_sequence[] = { + 1, 1, 2, 3, 2, 1, 3, 4, -4, /* Duplicated DATA packets */ + 6, -4, 7, 8, /* DATA packet 5 lost */ + 6, 5, 7, -5, 4, 8, /* DATA packet 5 and 6 received in revers order; + reception of an very old packet: 4 */ + 7, 6, 8, -6, /* DATA packet 6 and 7 received in revers order; + DATA packet 9 not send or lost */ + 6, 7, 8, 9, -9, /* ACK packet 6 was not received by server */ + 10, 11, 12, 0, -12, /* DATA packet 13 lost */ + 13, 16, -13, /* DATA packets 14, 15 lost */ + 12, 13, 14, 15, -15, 16, 17, /* Reception of duplicated old packets 12 and + 13 */ + 16, 17, 18, 19, -19, /* Normal sequence; ACK 19 not receive by server */ + 16, 17, 18, 19, -19, /* Normal sequence repeated; + ACK 19 not receive by server */ + 16, 19, -19, 18, 17, /* Sequence repeated but DATA packet 17 and 18 + received after 19 and in revers order */ + 20, -20, 21, 22, 23, /* ACK 20 because the client assumes the server + did not get ACK 19 and restarted with 17 */ + 21, 22, 23, 24, -24, /* Normal sequence */ + 25, 27, -25, 26, 28, -26, /* The middle data packets 26, 27 are exchanged; + the client assumes DATA 26 being the start + of the next window and sends an ACK 26 + upon reception of out-of-sequence DATA 28; + assume ACK 26 not received by server */ + 26, 27, 29, -27, 28, /* The last data packets are exchanged; + ACK 27 not received by server */ + 26, 27, 28, 29, -29, /* Normal sequence repeated from ACK 25 */ + 30, 31, 0, -31, /* The last two data packets are lost (timeout) */ + 32, 33, 34, /* Normal sequence; the last DATA packet (here missing) will + contain no data and ends the transfer at a window + boundary; a final ACK (here missing too) follows */ + }; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + SMALL_BLOCK_SIZE, + SMALL_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + for ( i = 0; i < RTEMS_ARRAY_SIZE( pkg_sequence ); ++i ) { + if ( pkg_sequence[i] == 0 ) { + block_num = pkg_sequence[i]; + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, /* Timeout: No packet received within timeout period */ + timeout + ); + timeout = TIMEOUT_MILLISECONDS; + } else if ( pkg_sequence[i] > 0 ) { + block_num = pkg_sequence[i]; + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + timeout, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + ( block_num - 1 ) * SMALL_BLOCK_SIZE, + SMALL_BLOCK_SIZE, /* Number of bytes transferred */ + get_file_content, + pkg_sequence[i] > 0 /* pkg_sequence[i] == 0 means timeout */ + ); + timeout = FIRST_TIMEOUT_MILLISECONDS; + pos_in_file = block_num * SMALL_BLOCK_SIZE; + } else { + block_num = -pkg_sequence[i]; + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + } + } + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + timeout, + SERV_PORT, + tftpfs_ipv4_loopback, + ++block_num, + pos_in_file, + 0, /* Number of bytes transferred */ + get_file_content, + true + ); + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + 100, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( bytes_read, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Write a file to the server using an option to increase block size. + * The file is 2 DATA packet and 1 byte long. No timeouts, packet loss, ... + * Tests: + * * The client uses only the blksize option in WRQ and OACK. + * * The client uses a block size which is larger than the default size. + * * The server can change the block size value in the OACK. + * * The option name in the OACK can be upper or lower case. + * * First and second DATA packets are full. + * * The second DATA packet is not full and signals the end of the transfer. + * * The client handles an empty DATA packet correctly as + * end of file indicator. + * * The test writes the file to the file system in chunks of 333 bytes. + */ +T_TEST_CASE_FIXTURE( write_simple_file_large_blocks, &fixture_large_blocksize ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_written; + size_t pos_in_file = 0; + uint16_t block_num = 1; + uint16_t block_size = 211; + const char options[] = "BLKsiZe" "\0" "211"; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + LARGE_BLOCK_SIZE, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + block_size, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + pos_in_file += block_size; + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + block_size, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + pos_in_file += block_size; + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + 1, /* Data bytes in this block */ + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + pos_in_file += 1; + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_written = write_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + pos_in_file, /* Size of file */ + 333, /* Bytes written per call to write() */ + &ctx->fd0 + ); + T_eq_int( bytes_written, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Write a file to the server using default options. + * The file is 23 data packet long. No timeouts, packet loss, ... + * Tests: + * * The client uses the default options + * (windowsize = 8 and blocksize = 1456). + * * The server sends the options in the same order the client + * did send them. + * * The 24th data packet signals the end of file transfer. + * * Client sends an empty data packet as end of file indicator. + * * The client handles files correctly which end exactly at + * a window border. + */ +T_TEST_CASE_FIXTURE( + write_simple_file_default_options, + &fixture_default_options +) +{ + tftp_test_context *ctx = T_fixture_context(); + int i; + int bytes_written; + size_t pos_in_file = 0; + uint16_t block_num = 1; + const char options[] = + TFTP_OPTION_BLKSIZE "\0" + RTEMS_XSTRING( TFTP_DEFAULT_BLOCK_SIZE ) "\0" + TFTP_OPTION_WINDOWSIZE "\0" + RTEMS_XSTRING( TFTP_DEFAULT_WINDOW_SIZE ); + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + TFTP_DEFAULT_BLOCK_SIZE, + TFTP_DEFAULT_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + for ( i = 0; i < 23; ++i ) { + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num++, + pos_in_file, + TFTP_DEFAULT_BLOCK_SIZE, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + pos_in_file += TFTP_DEFAULT_BLOCK_SIZE; + if ( i % 8 == 7 ) { + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num - 1, + true + ); + } else { + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, + DO_NOT_WAIT_FOR_ANY_TIMEOUT + ); + } + } + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + 0, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_written = write_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + pos_in_file, /* Size of file */ + 333, /* Bytes written per call to write() */ + &ctx->fd0 + ); + T_eq_int( bytes_written, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Write a file following exactly the scenario from RFC 7440. + * This test uses window size and block size options. There are lost packets. + * Tests: + * * The client uses non-default options + * (windowsize = 4 and blocksize = 12). + * * Test the scenario included in RFC 7440. + * * The server sends the options in the inverse order the client + * did send them. + * * The data of the file ends exactly at a window size border (i.e. + * after the last window the server sends a single empty DATA packet). + * * The test writes a file to the file system in chunks of 10 bytes. + */ +T_TEST_CASE_FIXTURE( write_file_rfc7440_scenario, &fixture_small_opt_size ) +{ + tftp_test_context *ctx = T_fixture_context(); + int i; + int bytes_written; + size_t pos_in_file = 0; + uint16_t block_num = 1; + const char options[] = + TFTP_OPTION_WINDOWSIZE"\0" + RTEMS_XSTRING( SMALL_WINDOW_SIZE ) "\0" + TFTP_OPTION_BLKSIZE "\0" + RTEMS_XSTRING( SMALL_BLOCK_SIZE ); + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + SMALL_BLOCK_SIZE, + SMALL_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + for ( i = 0; i < 6; ++i ) { + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num++, + pos_in_file, + SMALL_BLOCK_SIZE, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + pos_in_file += SMALL_BLOCK_SIZE; + if ( i % 4 == 3 ) { + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num - 1, + true + ); + } else { + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, + DO_NOT_WAIT_FOR_ANY_TIMEOUT + ); + } + } + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + SMALL_BLOCK_SIZE, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + DO_NOT_WAIT_FOR_ANY_TIMEOUT, + SERV_PORT, + tftpfs_ipv4_loopback, + 5, + true + ); + block_num = 6; + pos_in_file = (block_num - 1) * SMALL_BLOCK_SIZE; + for ( i = 0; i < 7; ++i ) { + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num++, + pos_in_file, + SMALL_BLOCK_SIZE, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + pos_in_file += SMALL_BLOCK_SIZE; + if ( i % 4 == 3 ) { + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num - 1, + true + ); + } else { + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, + DO_NOT_WAIT_FOR_ANY_TIMEOUT + ); + } + } + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + SMALL_BLOCK_SIZE, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS + ); + block_num = 10; + pos_in_file = (block_num - 1) * SMALL_BLOCK_SIZE; + for ( i = 0; i < 4; ++i ) { + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num++, + pos_in_file, + SMALL_BLOCK_SIZE, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + pos_in_file += SMALL_BLOCK_SIZE; + if ( i % 4 == 3 ) { + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num - 1, + true + ); + } else { + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, + DO_NOT_WAIT_FOR_ANY_TIMEOUT + ); + } + } + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + 0, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_written = write_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + pos_in_file, /* Size of file */ + 10, /* Bytes written per call to write() */ + &ctx->fd0 + ); + T_eq_int( bytes_written, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Write a file using windowsize = 4. All kinds of packet loss, out of + * order packets and duplicated ACK packets appear. + * This test uses window size and block size options. It is a stress test + * for all kind of network problems which can appear during a transfer + * of a file with window size larger than 1. + * Tests: + * * The server repeats a whole window (timeout). + * * The client receives a duplicated ACK packet (directly in sequence). + * * The client receives a duplicated ACK (after sending a window). + * * The client receives an ACK with a block number which is not + * the end of the current window. + * * The client receives an very old ACK in the middle of a window. + * * The client receives an very old ACK at the end of a window. + * * The client receives an ACK for a not yet send DATA packet in the + * middle of a window (should be ignored or cause a error). + * * The client receives an ACK for a not yet send DATA packet at the + * end of a window (should be ignored or cause a error). + * * The client receives an ACK after sending a full window. + * * The client receives an ACK before all DATA packets of a + * window have been sent. + * * The client must repeat the first window completely (timeout). + * * The client must repeat the first window partially. + * * The client must repeat the last window completely (timeout). + * * The client must repeat the last window partially. + * * Windows with errors in between appear consecutively, without + * error free windows in between. + * * The test writes a file to the file system in chunks of block size. + */ +T_TEST_CASE_FIXTURE( write_file_windowsize_trouble, &fixture_small_opt_size ) +{ + tftp_test_context *ctx = T_fixture_context(); + int i; + int bytes_written; + size_t pos_in_file = 0; + uint16_t block_num = 1; + int timeout = FIRST_TIMEOUT_MILLISECONDS; + const char options[] = + TFTP_OPTION_BLKSIZE "\0" + RTEMS_XSTRING( SMALL_BLOCK_SIZE ) "\0" + TFTP_OPTION_WINDOWSIZE"\0" + RTEMS_XSTRING( SMALL_WINDOW_SIZE ); + /* + * A positive number is the number of an ACK packet received + * at the end of window. + * A 9999 indicates a timeout when waiting for an ACK packet. + * A zero indicates no ACK packet is received when checking for it. + * A positive number >= 10000 is the number+10000 of an ACK packet received + * while only checking for a packet. + * A negative number is the number of a DATA packet send + * at the end of a window. + * A negative number <= -10000 is the number-10000 of an *empty* DATA + * packet send. + * Each line corresponds to a window. + */ + int16_t pkg_sequence[] = { + -1, 0, -2, 0, -3, 0, -4, 9999, /* First window, trigger full repeat */ + -1, 0, -2, 0, -3, 0, -4, 2, /* ACK at end of window; + first window must be partially repeated */ + -3, 0, -4, 0, -5, 0, -6, 6, /* Normal sequence */ + -7, 0, -8, 0, -9, 0, -10, 6, /* Duplicate ACK */ + -7, 10006, /* Duplicate ACK; ACK before sending all packets of a window */ + -7, 0, -8, 10008, /* ACK before sending all packets of a window; + ACK is not at the window end */ + -9, 10007, 0, -10, 10013, 0, -11, 0, -12, 12, /* Reception of very old ACK; + Reception of future ACK (The + wrong "future" ACK must be + beyond the block size) + in the middle of a window */ + -13, 0, -14, 0, -15, 0, -16, 11, 17, 16, /* Reception of very old ACK; + Reception of future ACK at the + end of a window */ + -17, 0, -18, 0, -10019, 9999, /* Last window timeout, trigger full repeat */ + -17, 0, -18, 0, -10019, 16, /* Last window, duplicated ACK */ + -17, 0, -18, 10017, /* Last window, ACK before sending all packets */ + -18, 0, -10019, 19 /* Last window partially repeated */ + }; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + SMALL_BLOCK_SIZE, + SMALL_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + for ( i = 0; i < RTEMS_ARRAY_SIZE( pkg_sequence ); ++i ) { + if ( pkg_sequence[i] == 0 ) { + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, + DO_NOT_WAIT_FOR_ANY_TIMEOUT + ); + timeout = FIRST_TIMEOUT_MILLISECONDS; + } else if ( pkg_sequence[i] == 9999 ) { + _Tftp_Add_interaction_recv_nothing( + TFTP_FIRST_FD, + timeout + ); + timeout = FIRST_TIMEOUT_MILLISECONDS; + } else if ( pkg_sequence[i] >= 10000 ) { + block_num = pkg_sequence[i] - 10000; + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + DO_NOT_WAIT_FOR_ANY_TIMEOUT, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + timeout = FIRST_TIMEOUT_MILLISECONDS; + } else if ( pkg_sequence[i] > 0 ) { + block_num = pkg_sequence[i]; + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + timeout, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + timeout = FIRST_TIMEOUT_MILLISECONDS; + } else if ( pkg_sequence[i] <= -10000 ) { + block_num = -pkg_sequence[i] - 10000; + pos_in_file = (block_num - 1) * SMALL_BLOCK_SIZE; + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num++, + pos_in_file, + 0, /* Number of bytes transferred */ + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + timeout = FIRST_TIMEOUT_MILLISECONDS; + } else { + block_num = -pkg_sequence[i]; + pos_in_file = (block_num - 1) * SMALL_BLOCK_SIZE; + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num++, + pos_in_file, + SMALL_BLOCK_SIZE, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + timeout = FIRST_TIMEOUT_MILLISECONDS; + pos_in_file += SMALL_BLOCK_SIZE; + } + } + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_written = write_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + pos_in_file, /* Size of file */ + SMALL_BLOCK_SIZE, /* Bytes written per call to write() */ + &ctx->fd0 + ); + T_eq_int( bytes_written, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Write a file to the server where the server sends an OACK without options. + * The file is half a data packet long. No timeouts, packet loss, ... + * Tests: + * * The client processes an OACK without any options in it correctly. + * * The client uses the RFC1350 block size because the server does + * not agree to the options send. + * * The first data packet is half full and signals the end of the transfer. + * * The test reads a file from the file system in chunks of double block size. + */ +T_TEST_CASE_FIXTURE( write_tiny_file_OACK_no_options, &fixture_large_blocksize ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_written; + size_t pos_in_file = 0; + uint16_t block_num = 1; + const char options[] = {}; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + LARGE_BLOCK_SIZE, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE / 2, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE / 2; + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_written = write_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + pos_in_file, /* Size of file */ + 2 * TFTP_RFC1350_BLOCK_SIZE, /* Bytes written per call to write() */ + &ctx->fd0 + ); + T_eq_int( bytes_written, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Read a file and when the server responses with an ERROR to options + * fallback to no options. + * The file is one byte long. No timeouts, packet loss, ... + * Tests: + * * The client uses windowsize and blksize option in the first RRQ. + * * Upon reception of an ERROR packet from the server, the client + * re-tries opening the session using an RRQ without options. + * * The server accepts the RRQ without options by sending a DATA packet. + * * The second RRQ is sent to the TFTP server port 69 and not to the + * port from which the first ERROR packet came from. + * * The first data packet contains a single byte and signals the + * end of the transfer. + */ +T_TEST_CASE_FIXTURE( read_file_fallback_to_no_options, + &fixture_default_options ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + uint16_t block_num = 1; + size_t pos_in_file = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + TFTP_DEFAULT_BLOCK_SIZE, + TFTP_DEFAULT_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_error( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + TFTP_ERROR_CODE_OPTION_NEGO, + "Don't like options", + true + ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_data( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num, + pos_in_file, + 1, /* Number of bytes transferred */ + get_file_content, + true + ); + pos_in_file += 1; + _Tftp_Add_interaction_send_ack( + TFTP_FIRST_FD, + block_num++, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + TFTP_DEFAULT_BLOCK_SIZE / 2, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( bytes_read, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Read a file from the server but the server responds with + * an ERROR to all RRQ with and without options. + * The file is one byte long. No timeouts, packet loss, ... + * Tests: + * * The client uses windowsize and blksize option in the first RRQ. + * * Upon reception of an ERROR packet from the server, the client + * re-tries opening a session using an RRQ without options. + * * The second RRQ is sent to the TFTP server port 69 and not to the + * port from which the first ERROR packet came from. + * * The server does not accept the RRQ without options + * and responds again with an ERROR packet. + * * The client ends all attempts to create a connection after + * receiving an ERROR packet to an RRQ without options. + * * The client signals the error to the user. + */ +T_TEST_CASE_FIXTURE( read_file_useless_fallback_to_no_options, + &fixture_default_options ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + TFTP_DEFAULT_BLOCK_SIZE, + TFTP_DEFAULT_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_error( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + TFTP_ERROR_CODE_ILLEGAL, + "Don't like options", + true + ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_error( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + TFTP_ERROR_CODE_ILLEGAL, + "Go away", + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + TFTP_DEFAULT_BLOCK_SIZE / 2, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( errno, EINVAL ); + T_eq_int( bytes_read, 0 ); + T_no_more_interactions(); +} + +/* + * Write a file to the server and the server responses with an ACK packet. + * The file is half a data packet long. No timeouts, packet loss, ... + * Tests: + * * The client uses windowsize and blksize option in the WRQ. + * * The client uses the RFC1350 option values for the data transfer + * because the server responded with an ACK packet. + * * The whole package sequence behaves as if only RFC1350 was used. + * * The first data packet is half filled and signals the end of the + * transfer. + */ +T_TEST_CASE_FIXTURE( write_file_ACK_instead_of_OACK, &fixture_default_options ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_written; + size_t pos_in_file = 0; + uint16_t block_num = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + TFTP_DEFAULT_BLOCK_SIZE, + TFTP_DEFAULT_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE / 2, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE / 2; + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_written = write_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + pos_in_file, /* Size of file */ + 2 * TFTP_RFC1350_BLOCK_SIZE, /* Bytes written per call to write() */ + &ctx->fd0 + ); + T_eq_int( bytes_written, pos_in_file ); + T_no_more_interactions(); +} + +/* + * Write a file and when the server responses with an ERROR to options + * fallback to no options. + * The file is half a data packet long. No timeouts, packet loss, ... + * Tests: + * * The client uses windowsize and blksize options in the first WRQ. + * * Upon reception of an ERROR from the server, the client re-tries + * opening a session using a WRQ without options. + * * The second WRQ is sent to the TFTP server port 69 and not to the + * port from which the first ERROR packet came from. + * * The server accepts the WRQ without options by sending an ACK packet. + * * The first data packet is half filled and signals the end of the transfer. + */ +T_TEST_CASE_FIXTURE( write_file_fallback_to_no_options, + &fixture_default_options ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_written; + size_t pos_in_file = 0; + uint16_t block_num = 0; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + TFTP_DEFAULT_BLOCK_SIZE, + TFTP_DEFAULT_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_error( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + TFTP_ERROR_CODE_ILLEGAL, + "Don't like options", + true + ); + _Tftp_Add_interaction_send_wrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + NO_BLOCK_SIZE_OPTION, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_send_data( + TFTP_FIRST_FD, + block_num, + pos_in_file, + TFTP_RFC1350_BLOCK_SIZE / 2, + get_file_content, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + pos_in_file += TFTP_RFC1350_BLOCK_SIZE / 2; + _Tftp_Add_interaction_recv_ack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + block_num++, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_written = write_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + pos_in_file, /* Size of file */ + 2 * TFTP_RFC1350_BLOCK_SIZE, /* Bytes written per call to write() */ + &ctx->fd0 + ); + T_eq_int( bytes_written, pos_in_file ); + T_no_more_interactions(); +} + +/* + * The server sends a malformed OACK: The final 0 byte is missing. + */ +T_TEST_CASE_FIXTURE( OACK_without_null, &fixture_default_options ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + const char options[] = { 'b', 'l', 'k', 's', 'i', 'z', 'e', '\0', '1', '2' }; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + TFTP_DEFAULT_BLOCK_SIZE, + TFTP_DEFAULT_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_OPTION_NEGO, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + LARGE_BLOCK_SIZE, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( errno, EPROTO ); + T_eq_int( bytes_read, 0 ); + T_no_more_interactions(); +} + +/* + * Server sends a malformed OACK packet: The value of the option is missing. + */ +T_TEST_CASE_FIXTURE( OACK_without_option_value, &fixture_default_options ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + const char options[] = TFTP_OPTION_BLKSIZE; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + TFTP_DEFAULT_BLOCK_SIZE, + TFTP_DEFAULT_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_OPTION_NEGO, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + LARGE_BLOCK_SIZE, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( errno, EPROTO ); + T_eq_int( bytes_read, 0 ); + T_no_more_interactions(); +} + +/* + * Server sends a malformed OACK packet: The option is unknown. + */ +T_TEST_CASE_FIXTURE( OACK_with_unknown_option, &fixture_default_options ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + const char options[] = + "shoesize" "\0" + RTEMS_XSTRING( TFTP_DEFAULT_BLOCK_SIZE ); + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + TFTP_DEFAULT_BLOCK_SIZE, + TFTP_DEFAULT_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_OPTION_NEGO, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + LARGE_BLOCK_SIZE, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( errno, EPROTO ); + T_eq_int( bytes_read, 0 ); + T_no_more_interactions(); +} + +/* + * Server sends a malformed OACK packet: The value of the option is + * not a number. + */ +T_TEST_CASE_FIXTURE( OACK_malformed_option_value, &fixture_default_options ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + const char options[] = TFTP_OPTION_BLKSIZE "\0" "abc"; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + TFTP_DEFAULT_BLOCK_SIZE, + TFTP_DEFAULT_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_OPTION_NEGO, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + LARGE_BLOCK_SIZE, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( errno, EPROTO ); + T_eq_int( bytes_read, 0 ); + T_no_more_interactions(); +} + +/* + * Server sends a malformed OACK packet: The value of the option is empty. + */ +T_TEST_CASE_FIXTURE( OACK_with_empty_option_value, &fixture_default_options ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + const char options[] = TFTP_OPTION_BLKSIZE "\0"; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + TFTP_DEFAULT_BLOCK_SIZE, + TFTP_DEFAULT_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_OPTION_NEGO, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + LARGE_BLOCK_SIZE, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( errno, EPROTO ); + T_eq_int( bytes_read, 0 ); + T_no_more_interactions(); +} + +/* + * Server sends a malformed OACK packet: The option name is empty. + */ +T_TEST_CASE_FIXTURE( OACK_with_empty_option_name, &fixture_default_options ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + const char options[] = "\0" RTEMS_XSTRING( TFTP_DEFAULT_BLOCK_SIZE ); + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + TFTP_DEFAULT_BLOCK_SIZE, + TFTP_DEFAULT_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_OPTION_NEGO, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + LARGE_BLOCK_SIZE, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( errno, EPROTO ); + T_eq_int( bytes_read, 0 ); + T_no_more_interactions(); +} + +/* + * Server sends a malformed OACK packet: The block size option value + * is too small. + */ +T_TEST_CASE_FIXTURE( OACK_blocksize_too_small, &fixture_default_options ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + const char options[] = TFTP_OPTION_BLKSIZE "\0" "7"; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + TFTP_DEFAULT_BLOCK_SIZE, + TFTP_DEFAULT_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_OPTION_NEGO, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + LARGE_BLOCK_SIZE, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( errno, EPROTO ); + T_eq_int( bytes_read, 0 ); + T_no_more_interactions(); +} + +/* + * Server sends a malformed OACK packet: The block size option value + * is too large. + */ +T_TEST_CASE_FIXTURE( OACK_blocksize_too_large, &fixture_default_options ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + const char options[] = TFTP_OPTION_BLKSIZE "\0" "1457"; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + TFTP_DEFAULT_BLOCK_SIZE, + TFTP_DEFAULT_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_OPTION_NEGO, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + LARGE_BLOCK_SIZE, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( errno, EPROTO ); + T_eq_int( bytes_read, 0 ); + T_no_more_interactions(); +} + +/* + * Server sends a malformed OACK packet: The windowsize option value + * is too small. + */ +T_TEST_CASE_FIXTURE( OACK_windowsize_too_small, &fixture_default_options ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + const char options[] = TFTP_OPTION_WINDOWSIZE "\0" "0"; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + TFTP_DEFAULT_BLOCK_SIZE, + TFTP_DEFAULT_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_OPTION_NEGO, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + LARGE_BLOCK_SIZE, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( errno, EPROTO ); + T_eq_int( bytes_read, 0 ); + T_no_more_interactions(); +} + +/* + * Server sends a malformed OACK packet: The windowsize option is too large. + */ +T_TEST_CASE_FIXTURE( OACK_windowsize_too_large, &fixture_default_options ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + const char options[] = TFTP_OPTION_WINDOWSIZE "\0" "9"; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + TFTP_DEFAULT_BLOCK_SIZE, + TFTP_DEFAULT_WINDOW_SIZE, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_OPTION_NEGO, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + LARGE_BLOCK_SIZE, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( errno, EPROTO ); + T_eq_int( bytes_read, 0 ); + T_no_more_interactions(); +} + +/* + * Server sends a malformed OACK packet: There is a known but surplus option. + */ +T_TEST_CASE_FIXTURE( OACK_with_surplus_option, &fixture_large_blocksize ) +{ + tftp_test_context *ctx = T_fixture_context(); + int bytes_read; + const char options[] = + TFTP_OPTION_BLKSIZE "\0" + RTEMS_XSTRING( LARGE_BLOCK_SIZE ) "\0" + TFTP_OPTION_WINDOWSIZE "\0" + "1"; + + /* T_set_verbosity( T_VERBOSE ); */ + _Tftp_Add_interaction_socket( AF_INET, SOCK_DGRAM, 0, TFTP_FIRST_FD ); + _Tftp_Add_interaction_send_rrq( + TFTP_FIRST_FD, + tftpfs_file, + TFTP_STD_PORT, + tftpfs_ipv4_loopback, + LARGE_BLOCK_SIZE, + NO_WINDOW_SIZE_OPTION, + true + ); + _Tftp_Add_interaction_recv_oack( + TFTP_FIRST_FD, + FIRST_TIMEOUT_MILLISECONDS, + SERV_PORT, + tftpfs_ipv4_loopback, + options, + sizeof( options ), + true + ); + _Tftp_Add_interaction_send_error( + TFTP_FIRST_FD, + TFTP_ERROR_CODE_OPTION_NEGO, + SERV_PORT, + tftpfs_ipv4_loopback, + true + ); + _Tftp_Add_interaction_close( TFTP_FIRST_FD, 0 ); + + bytes_read = read_tftp_file( + create_tftpfs_path( tftpfs_ipv4_loopback, tftpfs_file ), + LARGE_BLOCK_SIZE, /* Bytes read per call to read() */ + SIZE_MAX, + &ctx->fd0 + ); + T_eq_int( errno, EPROTO ); + T_eq_int( bytes_read, 0 ); + T_no_more_interactions(); +} + +/* + * Test suite and configuration + */ + +const char rtems_test_name[] = "TFTPFS"; + +static char buffer[ 512 ]; + +static const T_action actions[] = { + T_report_hash_sha256, + T_check_task_context, + T_check_file_descriptors, + T_check_rtems_barriers, + T_check_rtems_extensions, + T_check_rtems_message_queues, + T_check_rtems_partitions, + T_check_rtems_periods, + T_check_rtems_regions, + T_check_rtems_semaphores, + T_check_rtems_tasks, + T_check_rtems_timers, + T_check_posix_keys +}; + +static const T_config config = { + .name = rtems_test_name, + .buf = buffer, + .buf_size = sizeof( buffer ), + .putchar = T_putchar_default, + .verbosity = RTEMS_TEST_VERBOSITY, + .now = T_now_clock, + .allocate = T_memory_allocate, + .deallocate = T_memory_deallocate, + .action_count = T_ARRAY_SIZE( actions ), + .actions = actions +}; + +static void Init( rtems_task_argument argument ) +{ + (void) argument; + int exit_code; + + /* + * It would be much easier to simply use + * rtems_test_run( argument, TEST_STATE ); + * instead of all the code below and the variables + * buffer, actions, config + * above. Yet, rtems_test_run() sets the verbosity always to + * T_VERBOSE and this would produce plenty of output. + */ + rtems_test_begin( rtems_test_name, TEST_STATE ); + T_register(); + exit_code = T_main( &config ); + + if ( exit_code == 0 ) { + rtems_test_end( rtems_test_name ); + } + + rtems_fatal( RTEMS_FATAL_SOURCE_EXIT, (uint32_t) exit_code ); +} + +/* + * RTEMS configuration for tftp + */ + +#define CONFIGURE_FILESYSTEM_TFTPFS +#define CONFIGURE_MAXIMUM_FILE_DESCRIPTORS 64 + +/* + * Simple RTEMS configuration + */ + +#define CONFIGURE_APPLICATION_NEEDS_CLOCK_DRIVER +#define CONFIGURE_APPLICATION_NEEDS_CONSOLE_DRIVER + +#define CONFIGURE_UNLIMITED_OBJECTS +#define CONFIGURE_UNIFIED_WORK_AREAS + +#define CONFIGURE_RTEMS_INIT_TASKS_TABLE + +#define CONFIGURE_INIT + +#include diff --git a/testsuites/fstests/tftpfs/tftpfs_interactions.c b/testsuites/fstests/tftpfs/tftpfs_interactions.c new file mode 100644 index 0000000000..ad05978b17 --- /dev/null +++ b/testsuites/fstests/tftpfs/tftpfs_interactions.c @@ -0,0 +1,984 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +/** + * @file + * + * @ingroup RTEMSTestSuiteTestsTFTPFS + * + * @brief This source file contains the implementation of network interaction + * functions related to the UDP network fake for tftpfs testing. + * + * The UDP Network Fake requires *interactions* between TFTP client and test + * (which emulates a TFTP server). The idea is that each test defines a + * sequence of interactions. In a successful test run all interactions must + * be carried out one-by-one till the *last* interaction is reached. + * + * Interactions appear when the TFTP client calls functions like + * sendto(), recvfrom(), or socket(). Here functions are defined + * which + * + * * handle such interactions and + * * permit the tests to easily defined the sequence of interactions. + */ + +/* + * Copyright (C) 2022 embedded brains GmbH (http://www.embedded-brains.de) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include /* sprintf() */ +#include /* printf() macros like PRId8 */ +#include /* ntohs() */ +#include + + +#include "tftpfs_interactions.h" +#include "tftpfs_udp_network_fake.h" + +/* + * Interaction: socket() + */ + +typedef struct interaction_data_socket { + int domain; + int type; + int protocol; + int result; +} interaction_data_socket; + +static bool interact_socket( Tftp_Action *act, void *data ) +{ + interaction_data_socket *d = data; + T_eq_int( act->data.socket.domain, d->domain ); + T_eq_int( act->data.socket.type, d->type ); + T_eq_int( act->data.socket.protocol, d->protocol ); + if ( + act->data.socket.domain != d->domain || + act->data.socket.type != d->type || + act->data.socket.protocol != d->protocol + ) { + return false; + } + + act->data.socket.result = d->result; + return true; +} + +void _Tftp_Add_interaction_socket( + int domain, + int type, + int protocol, + int result +) +{ + interaction_data_socket *d; + + d = _Tftp_Append_interaction( + TFTP_IA_KIND_SOCKET, + interact_socket, + sizeof( interaction_data_socket ) + ); + + d->domain = domain; + d->type = type; + d->protocol = protocol; + d->result = result; +} + +/* + * Interaction: close() + */ + +typedef struct interaction_data_close { + int fd; + int result; +} interaction_data_close; + +static bool interact_close( Tftp_Action *act, void *data ) +{ + interaction_data_close *d = data; + T_eq_int( act->data.close.fd, d->fd ); + if ( act->data.close.fd != d->fd ) { + return false; + } + + act->data.close.result = d->result; + return true; +} + +void _Tftp_Add_interaction_close( int fd, int result ) +{ + interaction_data_close *d; + + d = _Tftp_Append_interaction( + TFTP_IA_KIND_CLOSE, + interact_close, + sizeof( interaction_data_close ) + ); + + d->fd = fd; + d->result = result; +} + +/* + * Interaction: bind() + */ + +typedef struct interaction_data_bind { + int fd; + int family; + int result; +} interaction_data_bind; + +static bool interact_bind( Tftp_Action *act, void *data ) +{ + interaction_data_bind *d = data; + T_eq_int( act->data.bind.fd, d->fd ); + T_eq_int( act->data.bind.family, d->family ); + if ( + act->data.bind.fd != d->fd || + act->data.bind.family != d->family + ) { + return false; + } + + act->data.bind.result = d->result; + return true; +} + +void _Tftp_Add_interaction_bind( int fd, int family, int result ) +{ + interaction_data_bind *d; + + d = _Tftp_Append_interaction( + TFTP_IA_KIND_BIND, + interact_bind, + sizeof( interaction_data_bind ) + ); + + d->fd = fd; + d->family = family; + d->result = result; +} + +/* + * Interaction: sendto() + */ + +#define TFTP_MAX_FILENAME_STRLEN 12 + +typedef struct interaction_data_sendto { + int fd; + uint16_t dest_port; + char dest_addr_str[TFTP_MAX_IP_ADDR_STRLEN]; + bool result; + union { + struct { + uint16_t opcode; + char filename[TFTP_MAX_FILENAME_STRLEN]; + uint16_t block_size; + uint16_t window_size; + } rwrq; + struct { + uint16_t block_num; + } ack; + struct { + uint16_t block_num; + size_t start; + size_t len; + uint8_t (*get_data)( size_t pos ); + } data; + struct { + uint16_t error_code; + } error; + } content; +} interaction_data_sendto; + +static bool check_filename_and_mode( + const char **buf, + size_t *max_len, + const char *filename +) +{ + const char *str; + size_t len; + + /* Make sure there is a 0 byte in the end before comparing strings */ + if ( *max_len <= 0 ) { return false; }; + T_quiet_eq_u8( *( *buf + *max_len - 1 ), '\0' ); + + str = *buf; + len = strlen( *buf ) + 1; + *buf += len; + *max_len -= len; + if ( strcmp( str, filename ) != 0 ) { + T_true( false, "Filename '%s' does not match '%s'", str, filename ); + return false; + } + if ( *max_len <= 0 ) { + T_true( false, "Mode string missing." ); + return false; + } + + str = *buf; + len = strlen( *buf ) + 1; + *buf += len; + *max_len -= len; + if ( strcasecmp( str, TFTP_MODE_OCTET ) != 0 ) { + T_true( false, "Mode '%s' does not match '%s'", str, TFTP_MODE_OCTET ); + return false; + } + + return true; +} + +static bool check_for_option( + const char **buf, + size_t *max_len, + const char *option_name, + const char *option_value +) +{ + const char *str; + size_t len; + + /* Make sure there is a 0 byte in the end before comparing strings */ + if ( *max_len <= 0 ) { return false; }; + T_quiet_eq_u8( *( *buf + *max_len - 1 ), '\0' ); + + str = *buf; + len = strlen( *buf ) + 1; + if ( strcasecmp( str, option_name ) != 0 ) { + return false; + } + *buf += len; + *max_len -= len; + + if ( *max_len <= 0 ) { + T_true( false, "Option value for '%s' missing.", option_name ); + return false; + } + + str = *buf; + len = strlen( *buf ) + 1; + *buf += len; + *max_len -= len; + if ( strcmp( str, option_value ) != 0 ) { + T_true( + false, + "Option '%s': Actual value '%s' does not match '%s'", + option_name, + str, + option_value + ); + return false; + } + + return true; +} + +static bool interact_sendto_common( Tftp_Action *act, void *data ) +{ + interaction_data_sendto *d = data; + const Tftp_Packet *pkt = act->data.sendto.buf; + + T_eq_int( act->data.sendto.fd, d->fd ); + T_eq_int( act->data.sendto.flags, 0 ); + T_eq_u16( act->data.sendto.dest_port, d->dest_port ); + T_eq_str( act->data.sendto.dest_addr_str, d->dest_addr_str ); + T_gt_sz( act->data.sendto.len, sizeof( pkt->opcode ) ); + if ( + act->data.sendto.fd != d->fd || + act->data.sendto.flags != 0 || + act->data.sendto.dest_port != d->dest_port || + strcmp( act->data.sendto.dest_addr_str, d->dest_addr_str ) != 0 || + act->data.sendto.len <= sizeof( pkt->opcode ) + ) { + return false; + } + + act->data.sendto.result = d->result ? act->data.sendto.len : -1; + return true; +} + +static bool interact_sendto_rwrq( Tftp_Action *act, void *data ) +{ + interaction_data_sendto *d = data; + const Tftp_Packet *pkt = act->data.sendto.buf; + size_t len = act->data.sendto.len - sizeof( pkt->opcode ); + const char *buf = pkt->content.rrq.opts; + const char *tmp; + char block_size_buffer[6]; + char window_size_buffer[6]; + + if ( !interact_sendto_common( act, data ) ) { + return false; + } + + T_eq_u16( ntohs( pkt->opcode ), d->content.rwrq.opcode ); + if ( ntohs( pkt->opcode ) != d->content.rwrq.opcode ) { return false; } + if ( !check_filename_and_mode( &buf, &len, d->content.rwrq.filename ) ) { + return false; + } + + snprintf( + block_size_buffer, + sizeof( block_size_buffer ), + "%"PRIu16, + d->content.rwrq.block_size + ); + snprintf( + window_size_buffer, + sizeof( window_size_buffer ), + "%"PRIu16, + d->content.rwrq.window_size + ); + + for ( tmp = buf; len > 0; ) { + if ( d->content.rwrq.block_size != NO_BLOCK_SIZE_OPTION && + check_for_option( + &buf, + &len, + TFTP_OPTION_BLKSIZE, + block_size_buffer )) { + d->content.rwrq.block_size = NO_BLOCK_SIZE_OPTION; + } else if ( d->content.rwrq.window_size != NO_WINDOW_SIZE_OPTION && + check_for_option( + &buf, + &len, + TFTP_OPTION_WINDOWSIZE, + window_size_buffer )) { + d->content.rwrq.window_size = NO_WINDOW_SIZE_OPTION; + } else { + T_true( false, "Error at option '%s'", tmp ); + return false; + } + } + + T_eq_sz( len, 0 ); /* Check that all data till the end has been read */ + + return true; +} + +static bool interact_sendto_ack( Tftp_Action *act, void *data ) +{ + interaction_data_sendto *d = data; + const Tftp_Packet *pkt = act->data.sendto.buf; + size_t pkt_size = sizeof( pkt->opcode ) + sizeof( pkt->content.ack ); + + if ( !interact_sendto_common( act, data ) ) { + return false; + } + + T_eq_u16( ntohs( pkt->opcode ), TFTP_OPCODE_ACK ); + T_eq_sz( act->data.sendto.len, pkt_size ); + if ( ntohs( pkt->opcode ) != TFTP_OPCODE_ACK || + act->data.sendto.len != pkt_size + ) { + return false; + } + + T_eq_u16( ntohs( pkt->content.ack.block_num ), d->content.ack.block_num ); + if ( ntohs( pkt->content.ack.block_num ) != d->content.ack.block_num ) { + return false; + } + + return true; +} + +static bool interact_sendto_data( Tftp_Action *act, void *data ) +{ + interaction_data_sendto *d = data; + const Tftp_Packet *pkt = act->data.sendto.buf; + size_t pkt_size = sizeof( pkt->opcode ) + + sizeof( pkt->content.data.block_num ) + d->content.data.len; + size_t i; + + if ( !interact_sendto_common( act, data ) ) { + return false; + } + + T_eq_u16( ntohs( pkt->opcode ), TFTP_OPCODE_DATA ); + T_eq_sz( act->data.sendto.len, pkt_size ); + if ( ntohs( pkt->opcode ) != TFTP_OPCODE_DATA || + act->data.sendto.len != pkt_size + ) { + return false; + } + + T_eq_u16( ntohs( pkt->content.ack.block_num ), d->content.ack.block_num ); + if ( ntohs( pkt->content.ack.block_num ) != d->content.ack.block_num ) { + return false; + } + + for ( i = 0; i < d->content.data.len; ++i ) { + if ( pkt->content.data.bytes[i] != + d->content.data.get_data( d->content.data.start + i ) ) { + T_true( + false, + "Expected byte %02"PRIx8" but TFTP client sent %02"PRIx8 + " at position %zu", + d->content.data.get_data( d->content.data.start + i ), + pkt->content.data.bytes[i], + d->content.data.start + i + ); + return false; + } + } + + return true; +} + +static bool interact_sendto_error( Tftp_Action *act, void *data ) +{ + interaction_data_sendto *d = data; + const Tftp_Packet *pkt = act->data.sendto.buf; + size_t pkt_size = sizeof( pkt->opcode ) + sizeof( pkt->content.error ); + + if ( !interact_sendto_common( act, data ) ) { + return false; + } + + T_eq_u16( ntohs( pkt->opcode ), TFTP_OPCODE_ERROR ); + T_gt_sz( act->data.sendto.len, pkt_size ); + if ( ntohs( pkt->opcode ) != TFTP_OPCODE_ERROR || + act->data.sendto.len <= pkt_size + ) { + return false; + } + + T_eq_u16( + ntohs( pkt->content.error.error_code ), + d->content.error.error_code + ); + if ( ntohs( pkt->content.error.error_code ) != d->content.error.error_code ) { + return false; + } + + return true; +} + +static void add_interaction_send_rwrq( + int fd, + const char *filename, + uint16_t dest_port, + const char *dest_addr_str, + uint16_t block_size, + uint16_t window_size, + bool result, + uint16_t opcode +) +{ + interaction_data_sendto *d; + + d = _Tftp_Append_interaction( + TFTP_IA_KIND_SENDTO, + interact_sendto_rwrq, + sizeof( interaction_data_sendto ) + ); + + T_assert_lt_sz( strlen( filename ), TFTP_MAX_FILENAME_STRLEN ); + T_assert_lt_sz( strlen( dest_addr_str ), TFTP_MAX_IP_ADDR_STRLEN ); + strcpy( d->content.rwrq.filename, filename ); + strcpy( d->dest_addr_str, dest_addr_str ); + d->fd = fd; + d->dest_port = dest_port; + d->content.rwrq.opcode = opcode; + d->content.rwrq.block_size = block_size; + d->content.rwrq.window_size = window_size; + d->result = result; +} + +void _Tftp_Add_interaction_send_rrq( + int fd, + const char *filename, + uint16_t dest_port, + const char *dest_addr_str, + uint16_t block_size, + uint16_t window_size, + bool result +) +{ + add_interaction_send_rwrq( + fd, + filename, + dest_port, + dest_addr_str, + block_size, + window_size, + result, + TFTP_OPCODE_RRQ + ); +} + +void _Tftp_Add_interaction_send_wrq( + int fd, + const char *filename, + uint16_t dest_port, + const char *dest_addr_str, + uint16_t block_size, + uint16_t window_size, + bool result +) +{ + add_interaction_send_rwrq( + fd, + filename, + dest_port, + dest_addr_str, + block_size, + window_size, + result, + TFTP_OPCODE_WRQ + ); +} + +void _Tftp_Add_interaction_send_ack( + int fd, + uint16_t block_num, + uint16_t dest_port, + const char *dest_addr_str, + bool result +) +{ + interaction_data_sendto *d; + + d = _Tftp_Append_interaction( + TFTP_IA_KIND_SENDTO, + interact_sendto_ack, + sizeof( interaction_data_sendto ) + ); + + T_assert_lt_sz( strlen( dest_addr_str ), TFTP_MAX_IP_ADDR_STRLEN ); + strcpy( d->dest_addr_str, dest_addr_str ); + d->fd = fd; + d->dest_port = dest_port; + d->result = result; + d->content.ack.block_num = block_num; +} + +void _Tftp_Add_interaction_send_data( + int fd, + uint16_t block_num, + size_t start, + size_t len, + uint8_t (*get_data)( size_t pos ), + uint16_t dest_port, + const char *dest_addr_str, + bool result +) +{ + interaction_data_sendto *d; + + d = _Tftp_Append_interaction( + TFTP_IA_KIND_SENDTO, + interact_sendto_data, + sizeof( interaction_data_sendto ) + ); + + T_assert_lt_sz( strlen( dest_addr_str ), TFTP_MAX_IP_ADDR_STRLEN ); + strcpy( d->dest_addr_str, dest_addr_str ); + d->fd = fd; + d->dest_port = dest_port; + d->result = result; + d->content.data.block_num = block_num; + d->content.data.start = start; + d->content.data.len = len; + d->content.data.get_data = get_data; +} + +void _Tftp_Add_interaction_send_error( + int fd, + uint16_t error_code, + uint16_t dest_port, + const char *dest_addr_str, + bool result +) +{ + interaction_data_sendto *d; + + d = _Tftp_Append_interaction( + TFTP_IA_KIND_SENDTO, + interact_sendto_error, + sizeof( interaction_data_sendto ) + ); + + T_assert_lt_sz( strlen( dest_addr_str ), TFTP_MAX_IP_ADDR_STRLEN ); + strcpy( d->dest_addr_str, dest_addr_str ); + d->fd = fd; + d->dest_port = dest_port; + d->result = result; + d->content.error.error_code = error_code; +} + +/* + * Interaction: recvfrom() + */ + +typedef struct interaction_data_recvfrom { + int fd; + uint32_t timeout_ms; + uint16_t src_port; + char src_addr_str[TFTP_MAX_IP_ADDR_STRLEN]; + bool result; + union { + struct { + uint16_t block_num; + } ack; + struct { + size_t options_size; + char options[TFTP_MAX_OPTIONS_SIZE]; + } oack; + struct { + uint16_t block_num; + size_t start; + size_t len; + uint8_t (*get_data)( size_t pos ); + } data; + struct { + uint16_t error_code; + char err_msg[TFTP_MAX_ERROR_STRLEN]; + } error; + struct { + size_t len; + uint8_t bytes[0]; + } raw; + } content; +} interaction_data_recvfrom; + +static bool interact_recvfrom_common( + Tftp_Action *act, + void *data, + size_t actual_size +) +{ + interaction_data_recvfrom *d = data; + + T_eq_int( act->data.recvfrom.fd, d->fd ); + T_quiet_ge_sz( act->data.recvfrom.len, actual_size ); + if ( + act->data.recvfrom.fd != d->fd || + act->data.recvfrom.len < actual_size + ) { + return false; + } + if ( d->timeout_ms == DO_NOT_WAIT_FOR_ANY_TIMEOUT ) { + T_ne_int( act->data.recvfrom.flags, 0 ); + if ( act->data.recvfrom.flags == 0 ) { + return false; + } + } else { + T_eq_int( act->data.recvfrom.flags, 0 ); + T_eq_u32( act->data.recvfrom.timeout_ms, d->timeout_ms ); + if ( + act->data.recvfrom.flags != 0 || + act->data.recvfrom.timeout_ms != d->timeout_ms + ) { + return false; + } + } + + strncpy( + act->data.recvfrom.src_addr_str, + d->src_addr_str, + sizeof( act->data.recvfrom.src_addr_str ) + ); + act->data.recvfrom.src_addr_str[ + sizeof( act->data.recvfrom.src_addr_str ) - 1] = '\0'; + act->data.recvfrom.src_port = d->src_port; + act->data.recvfrom.result = d->result ? actual_size : -1; + return true; +} + +static bool interact_recvfrom_data( Tftp_Action *act, void *data ) +{ + interaction_data_recvfrom *d = data; + Tftp_Packet *pkt = act->data.recvfrom.buf; + size_t actual_size; + size_t i; + + actual_size = sizeof( pkt->opcode ) + + sizeof( pkt->content.data.block_num ) + d->content.data.len; + if ( !interact_recvfrom_common( act, data, actual_size ) ) { + return false; + } + + pkt->opcode = htons( TFTP_OPCODE_DATA ); + pkt->content.data.block_num = htons( d->content.data.block_num ); + for ( i = 0; i < d->content.data.len; ++i ) { + pkt->content.data.bytes[i] = + d->content.data.get_data( d->content.data.start + i ); + } + + return true; +} + +static bool interact_recvfrom_ack( Tftp_Action *act, void *data ) +{ + interaction_data_recvfrom *d = data; + Tftp_Packet *pkt = act->data.recvfrom.buf; + size_t actual_size; + + actual_size = sizeof( pkt->opcode ) + sizeof( pkt->content.ack.block_num ); + if ( !interact_recvfrom_common( act, data, actual_size ) ) { + return false; + } + + pkt->opcode = htons( TFTP_OPCODE_ACK ); + pkt->content.ack.block_num = htons( d->content.ack.block_num ); + + return true; +} + +static bool interact_recvfrom_oack( Tftp_Action *act, void *data ) +{ + interaction_data_recvfrom *d = data; + Tftp_Packet *pkt = act->data.recvfrom.buf; + size_t actual_size; + + actual_size = sizeof( pkt->opcode ) + d->content.oack.options_size; + if ( !interact_recvfrom_common( act, data, actual_size ) ) { + return false; + } + + pkt->opcode = htons( TFTP_OPCODE_OACK ); + memcpy( + pkt->content.oack.opts, + d->content.oack.options, + d->content.oack.options_size + ); + + return true; +} + +static bool interact_recvfrom_error( Tftp_Action *act, void *data ) +{ + interaction_data_recvfrom *d = data; + Tftp_Packet *pkt = act->data.recvfrom.buf; + size_t actual_size; + + actual_size = sizeof( pkt->opcode ) + + sizeof( pkt->content.error.error_code ) + + strlen( d->content.error.err_msg ) + 1; + if ( !interact_recvfrom_common( act, data, actual_size ) ) { + return false; + } + + pkt->opcode = htons( TFTP_OPCODE_ERROR ); + pkt->content.error.error_code = htons( d->content.error.error_code ); + strncpy( + pkt->content.error.err_msg, + d->content.error.err_msg, + act->data.recvfrom.len - sizeof( pkt->opcode ) - + sizeof( pkt->content.error.error_code ) - 1 + ); + + return true; +} + +static bool interact_recvfrom_raw( Tftp_Action *act, void *data ) +{ + interaction_data_recvfrom *d = data; + uint8_t *pkt = act->data.recvfrom.buf; + size_t actual_size = d->content.raw.len; + + if ( !interact_recvfrom_common( act, data, actual_size ) ) { + return false; + } + + memcpy( pkt, d->content.raw.bytes, actual_size ); + + return true; +} + +void _Tftp_Add_interaction_recv_data( + int fd, + uint32_t timeout_ms, + uint16_t src_port, + const char *src_addr_str, + uint16_t block_num, + size_t start, + size_t len, + uint8_t (*get_data)( size_t pos ), + bool result +) +{ + interaction_data_recvfrom *d; + + d = _Tftp_Append_interaction( + TFTP_IA_KIND_RECVFROM, + interact_recvfrom_data, + sizeof( interaction_data_recvfrom ) + ); + + T_assert_lt_sz( strlen( src_addr_str ), sizeof( d->src_addr_str ) - 1 ); + strcpy( d->src_addr_str, src_addr_str ); + d->fd = fd; + d->timeout_ms = timeout_ms; + d->src_port = src_port; + d->content.data.block_num = block_num; + d->content.data.start = start; + d->content.data.len = len; + d->content.data.get_data = get_data; + d->result = result; +} + +void _Tftp_Add_interaction_recv_ack( + int fd, + uint32_t timeout_ms, + uint16_t src_port, + const char *src_addr_str, + uint16_t block_num, + bool result +) +{ + interaction_data_recvfrom *d; + + d = _Tftp_Append_interaction( + TFTP_IA_KIND_RECVFROM, + interact_recvfrom_ack, + sizeof( interaction_data_recvfrom ) + ); + + T_assert_lt_sz( strlen( src_addr_str ), sizeof( d->src_addr_str ) - 1 ); + strcpy( d->src_addr_str, src_addr_str ); + d->fd = fd; + d->timeout_ms = timeout_ms; + d->src_port = src_port; + d->content.ack.block_num = block_num; + d->result = result; +} + +void _Tftp_Add_interaction_recv_oack( + int fd, + uint32_t timeout_ms, + uint16_t src_port, + const char *src_addr_str, + const char *options, + size_t options_size, + bool result +) +{ + interaction_data_recvfrom *d; + + d = _Tftp_Append_interaction( + TFTP_IA_KIND_RECVFROM, + interact_recvfrom_oack, + sizeof( interaction_data_recvfrom ) + ); + + T_assert_lt_sz( strlen( src_addr_str ), sizeof( d->src_addr_str ) - 1 ); + strcpy( d->src_addr_str, src_addr_str ); + T_assert_lt_sz( options_size, sizeof( d->content.oack.options ) ); + memcpy( d->content.oack.options, options, options_size ); + d->fd = fd; + d->timeout_ms = timeout_ms; + d->src_port = src_port; + d->content.oack.options_size = options_size; + d->result = result; +} + +void _Tftp_Add_interaction_recv_error( + int fd, + uint32_t timeout_ms, + uint16_t src_port, + const char *src_addr_str, + uint16_t error_code, + const char *err_msg, + bool result +) +{ + interaction_data_recvfrom *d; + + d = _Tftp_Append_interaction( + TFTP_IA_KIND_RECVFROM, + interact_recvfrom_error, + sizeof( interaction_data_recvfrom ) + ); + + T_assert_lt_sz( strlen( src_addr_str ), sizeof( d->src_addr_str ) - 1 ); + strcpy( d->src_addr_str, src_addr_str ); + T_assert_lt_sz( strlen( src_addr_str ), sizeof( d->src_addr_str ) - 1 ); + strcpy( d->content.error.err_msg, err_msg ); + d->fd = fd; + d->timeout_ms = timeout_ms; + d->src_port = src_port; + d->content.error.error_code = error_code; + d->result = result; +} + +void _Tftp_Add_interaction_recv_raw( + int fd, + uint32_t timeout_ms, + uint16_t src_port, + const char *src_addr_str, + size_t len, + const uint8_t *bytes, + bool result +) +{ + interaction_data_recvfrom *d; + + d = _Tftp_Append_interaction( + TFTP_IA_KIND_RECVFROM, + interact_recvfrom_raw, + sizeof( interaction_data_recvfrom ) + len + ); + + T_assert_lt_sz( strlen( src_addr_str ), sizeof( d->src_addr_str ) - 1 ); + strcpy( d->src_addr_str, src_addr_str ); + memcpy( d->content.raw.bytes, bytes, len ); + d->fd = fd; + d->timeout_ms = timeout_ms; + d->src_port = src_port; + d->content.raw.len = len; + d->result = result; +} + +void _Tftp_Add_interaction_recv_nothing( + int fd, + uint32_t timeout_ms +) +{ + static const char *dummy_ip = "0.0.0.0"; + interaction_data_recvfrom *d; + + d = _Tftp_Append_interaction( + TFTP_IA_KIND_RECVFROM, + interact_recvfrom_ack, + sizeof( interaction_data_recvfrom ) + ); + + + T_assert_lt_sz( strlen( dummy_ip ), sizeof( d->src_addr_str ) - 1 ); + strcpy( d->src_addr_str, dummy_ip ); + d->fd = fd; + d->timeout_ms = timeout_ms; + d->src_port = 0; + d->content.ack.block_num = 0; + d->result = false; +} diff --git a/testsuites/fstests/tftpfs/tftpfs_interactions.h b/testsuites/fstests/tftpfs/tftpfs_interactions.h new file mode 100644 index 0000000000..f320f685d3 --- /dev/null +++ b/testsuites/fstests/tftpfs/tftpfs_interactions.h @@ -0,0 +1,213 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +/** + * @file + * + * @ingroup RTEMSTestSuiteTestsTFTPFS + * + * @brief This header file provides functions used to + * implement network interactions of the UDP network fake for tftpfs tests. + * + * Definitions and declarations of data structures and functions. + */ + +/* + * Copyright (C) 2022 embedded brains GmbH (http://www.embedded-brains.de) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _TFTPFS_INTERACTIONS_H +#define _TFTPFS_INTERACTIONS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define NO_BLOCK_SIZE_OPTION 0 +#define NO_WINDOW_SIZE_OPTION 0 +#define DO_NOT_WAIT_FOR_ANY_TIMEOUT UINT32_MAX + +/** + * @addtogroup RTEMSTestSuiteTestsTFTPFS + * + * @{ + */ + +/* + * These functions append an interaction to the list of expected interactions. + * For example, _Tftp_Add_interaction_socket() "means": + * + * * As next interaction, expect that the TFTP client calls function + * socket(). + * * Expect (i.e. check) that this socket() call will get parameter values + * as provided by its parameters `domain`, `type` and `protocol`. + * * This call to socket() shall return value of parameter `result` + * to the TFTP client. + * + * The interactions with functions sendto() and recvfrom() are a bit more + * complicated because specific packets are expected to be received or sent. + * For example, _Tftp_Add_interaction_send_rrq() appends an interaction + * where the TFTP client is expected to call sendto() with an RRQ (Read + * Request) packet. _Tftp_Add_interaction_recv_data() appends an interaction + * where the TFTP client is expected to call recvfrom() and as result it + * receives a data packet (which the interaction writes into the buffer + * which the TFTP client provides as parameter in its the recvfrom() call). + */ + +void _Tftp_Add_interaction_socket( + int domain, + int type, + int protocol, + int result +); + +void _Tftp_Add_interaction_close( int fd, int result ); + +void _Tftp_Add_interaction_bind( int fd, int family, int result ); + +void _Tftp_Add_interaction_send_rrq( + int fd, + const char *filename, + uint16_t dest_port, + const char *dest_addr_str, + uint16_t block_size, + uint16_t window_size, + bool result +); + +void _Tftp_Add_interaction_send_wrq( + int fd, + const char *filename, + uint16_t dest_port, + const char *dest_addr_str, + uint16_t block_size, + uint16_t window_size, + bool result +); + +void _Tftp_Add_interaction_send_ack( + int fd, + uint16_t block_num, + uint16_t dest_port, + const char *dest_addr_str, + bool result +); + +void _Tftp_Add_interaction_send_data( + int fd, + uint16_t block_num, + size_t start, + size_t len, + uint8_t (*get_data)( size_t pos ), + uint16_t dest_port, + const char *dest_addr_str, + bool result +); + +void _Tftp_Add_interaction_send_error( + int fd, + uint16_t error_code, + uint16_t dest_port, + const char *dest_addr_str, + bool result +); + +/* + * _Tftp_Add_interaction_recv_data() permits only a boolean result. + * The TFTP client code does not check `errno` and always behaves as if + * a return of -1 indicates a timeout. Hence + * _Tftp_Add_interaction_recv_data() permits only a boolean result + * and no special value to distinct timeouts from other errors. + */ +void _Tftp_Add_interaction_recv_data( + int fd, + uint32_t timeout_ms, + uint16_t src_port, + const char *src_addr_str, + uint16_t block_num, + size_t start, + size_t len, + uint8_t (*get_data)( size_t pos ), + bool result +); + +void _Tftp_Add_interaction_recv_ack( + int fd, + uint32_t timeout_ms, + uint16_t src_port, + const char *src_addr_str, + uint16_t block_num, + bool result +); + +void _Tftp_Add_interaction_recv_oack( + int fd, + uint32_t timeout_ms, + uint16_t src_port, + const char *src_addr_str, + const char *options, + size_t options_size, + bool result +); + +void _Tftp_Add_interaction_recv_error( + int fd, + uint32_t timeout_ms, + uint16_t src_port, + const char *src_addr_str, + uint16_t error_code, + const char *err_msg, + bool result +); + +/* + * The TFTP client receives a "raw" packet. + * + * Test tests use this function to trigger packet format errors such as: + * + * * Too short packets, + * * Strings without 0-byte at their end + * * Wrong op-codes + */ +void _Tftp_Add_interaction_recv_raw( + int fd, + uint32_t timeout_ms, + uint16_t src_port, + const char *src_addr_str, + size_t len, + const uint8_t *bytes, + bool result +); + +void _Tftp_Add_interaction_recv_nothing( + int fd, + uint32_t timeout_ms +); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* _TFTPFS_INTERACTIONS_H */ diff --git a/testsuites/fstests/tftpfs/tftpfs_udp_network_fake.c b/testsuites/fstests/tftpfs/tftpfs_udp_network_fake.c new file mode 100644 index 0000000000..201e87f0db --- /dev/null +++ b/testsuites/fstests/tftpfs/tftpfs_udp_network_fake.c @@ -0,0 +1,983 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +/** + * @file + * + * @ingroup RTEMSTestSuiteTestsTFTPFS + * + * @brief This source file contains the implementation of UDP network fake + * functions related to tftpfs testing. + * + * The libtftpfs source code is situated in the RTEMS repository. For + * testing it, either libbsd or RTEMS legacy networking would be required. + * This implies that the tests for libtftpfs would need to be placed in + * the libbsd repository - a different one than the libtftpfs source code. + * + * Yet, libtftpfs uses only a handful of networking functions. This + * file provides fake implementations of those functions. These fake + * functions permit to simulate the exchange of UDP packets + * with the libtftpfs code and thus permits testing libtftpfs without + * the need of a full network stack. + */ + +/* + * Copyright (C) 2022 embedded brains GmbH (http://www.embedded-brains.de) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include /* snprintf() */ +#include /* malloc(), free() */ +#include /* printf() macros like PRId8 */ +#include +#include /* isprint() */ +#include /* getnameinfo() */ +#include +#include +#include +#include /* struct sockaddr_in, struct sockaddr_in6 */ +#include + +#include "tftpfs_udp_network_fake.h" + +#define IPV4_ADDR_SIZE 4 +#define MAX_SOCKET_FD 4 + +int __wrap_close( int fd ); /* Prevent compiler warnings */ +int __real_close( int fd ); /* Prevent compiler warnings */ + +/* + * Control data + */ + +/* + * A singleton global data object is used to control the network interaction + * with the TFTP client. + * + * A test can exchange UDP packets when the TFTP client calls functions + * sendto() and recvfrom(). Simply put, when the client calls sendto() + * the test must check that the client sends the expected UDP packet + * and when the client calls recvfrom() the test must provide the UDP + * packet it wants to send to the client. + * + * Defining the sequence of sendto() and recvfrom() function calls + * including parameters and return values essentially represents a + * test for the TFTP networking. To be a bit more complete, a few more + * functions such as socket() and bind() are included in the sequence. + * + * Each of these function calls defines a single *interaction* between + * TFTP client and test (aka TFTP server). The idea is that each test + * defines a sequence of interactions. In a successful test run all + * interactions must be carried out one-by-one till the last (normally + * "close()") interaction is reached. + * + * The control_data essentially stores the sequence of interactions + * as well as the current state (e.g. which is the next interaction?). + */ +typedef struct control_data { + Tftp_Interaction *interaction_head; + Tftp_Interaction *interaction_tail; + Tftp_Interaction *interaction_cur; + int receive_timeout_socket_fd[MAX_SOCKET_FD]; + uint32_t receive_timeout_ms[MAX_SOCKET_FD]; +} control_data; + +static control_data *control = NULL; + +void _Tftp_Reset( void ) +{ + static control_data ctl; + int i; + Tftp_Interaction *iter; + Tftp_Interaction *current; + + if ( control == NULL ) { + control = &ctl; + } else { + for ( iter = control->interaction_head; iter != NULL; ) { + current = iter; + iter = iter->next; + free( current ); + } + } + + control->interaction_head = NULL; + control->interaction_tail = (Tftp_Interaction *) &control->interaction_head; + control->interaction_cur = (Tftp_Interaction *) &control->interaction_head; + for ( i = 0; i < MAX_SOCKET_FD; ++i ) { + control->receive_timeout_socket_fd[i] = 0; + control->receive_timeout_ms[i] = 0; + } +} + +void *_Tftp_Append_interaction( + Tftp_Action_kind kind, + Tftp_Interaction_fn fn, + size_t size +) +{ + Tftp_Interaction *ia; + T_quiet_not_null( control ); + ia = malloc( sizeof( Tftp_Interaction ) + size ); + T_quiet_not_null( ia ); + ia->next = NULL; + ia->kind = kind; + ia->fn = fn; + control->interaction_tail->next = ia; + control->interaction_tail = ia; + return ia->data; +} + +bool _Tftp_Has_no_more_interactions( void ) +{ + return control == NULL || control->interaction_cur != NULL; +} + +static Tftp_Interaction *get_next_interaction( control_data *control ) +{ + if ( control == NULL ) { + return NULL; + } + if ( control->interaction_cur != NULL ) { + control->interaction_cur = control->interaction_cur->next; + } + return control->interaction_cur; +} + +static const char *get_action_kind_as_string( Tftp_Action_kind kind ) +{ + switch ( kind ) { + case TFTP_IA_KIND_SOCKET: + return "socket"; + case TFTP_IA_KIND_CLOSE: + return "close"; + case TFTP_IA_KIND_BIND: + return "bind"; + case TFTP_IA_KIND_SENDTO: + return "sendto"; + case TFTP_IA_KIND_RECVFROM: + return "recvfrom"; + default: + break; + } + return "UNKNOWN"; +} + +/* + * Parse and log UDP TFTP packet functions + */ + +const char *_Tftp_Get_error_str( uint16_t error_code ) +{ + static const char *unknown_str = "Unknown error code"; + static const char *error_str[] = { + "Not defined, see error message (if any)", + "File not found", + "Access violation", + "Disk full or allocation exceeded", + "Illegal TFTP operation", + "Unknown transfer ID", + "File already exists", + "No such user", + "Option negociation failed", + }; + const char *result = unknown_str; + + if ( error_code < RTEMS_ARRAY_SIZE( error_str ) ) { + result = error_str[ error_code ]; + } + + return result; +} + +static void log_hex_dump( const void *buf, size_t len ) +{ + const size_t per_line = 16; + size_t pos = 0; + const uint8_t *pkt = buf; + char hex[2 * per_line + 4]; + char chars[per_line + 1]; + char *hexpos; + int i; + + chars[per_line] = '\0'; + do { + for ( i = 0, hexpos = hex; i < per_line; ++i ) { + if ( pos + i < len ) { + hexpos += snprintf( hexpos, 3, "%02"PRIX8, pkt[ pos + i ] ); + chars[i] = ( isprint( pkt[ pos + i ] ) ) ? pkt[ pos + i ] : '.'; + } else { + hexpos += snprintf( hexpos, 3, " " ); + chars[i] = '\0'; + } + if ( i < per_line - 1 && i % 4 == 3 ) { + *(hexpos++) = ' '; + } + } + + T_log( T_VERBOSE, " %04zX : %s |%s|", pos, hex, chars ); + pos += per_line; + } while( len > pos ); +} + +static const char *get_next_str( + const char **buf, + size_t *max_len, + bool *has_errors +) +{ + const char *result; + size_t len = strnlen( *buf, *max_len ); + if ( len < *max_len ) { + result = *buf; + *buf += len + 1; + *max_len -= len + 1; + } else { + result = "MAL_FORMED_STRING"; + *max_len = 0; + *has_errors = true; + } + return result; +} + +static void log_tftp_packet( const void *buf, size_t len ) +{ + int op_code; + const char *buffer = ( (char *) buf ) + sizeof( uint16_t ); + size_t remaining_len = len - sizeof( uint16_t ); + bool has_errors = false; + + if ( len >= sizeof( uint16_t ) ) { + op_code = ntohs( *((uint16_t *) buf ) ); + switch ( op_code ) { + case TFTP_OPCODE_RRQ: + case TFTP_OPCODE_WRQ: + T_log( + T_VERBOSE, + " %s File: \"%s\" Mode: \"%s\" %s", + ( op_code == TFTP_OPCODE_RRQ ) ? "RRQ" : "WRQ", + get_next_str( &buffer, &remaining_len, &has_errors ), + get_next_str( &buffer, &remaining_len, &has_errors ), + ( remaining_len > 0 ) ? "Options:" : "No options" + ); + while ( remaining_len > 0 ) { + T_log( + T_VERBOSE, + " %s = \"%s\"", + get_next_str( &buffer, &remaining_len, &has_errors ), + get_next_str( &buffer, &remaining_len, &has_errors ) + ); + } + break; + + case TFTP_OPCODE_DATA: + if ( len >= 2 * sizeof( uint16_t ) ) { + buffer += sizeof( uint16_t ); + remaining_len -= sizeof( uint16_t ); + T_log( + T_VERBOSE, + " DATA Block: %"PRIu16" Data (%zu bytes):", + ntohs( *( ( (uint16_t *) buf ) + 1 ) ), + remaining_len + ); + log_hex_dump( buffer, remaining_len ); + } else { + T_log( T_VERBOSE, " DATA packet ILLEGAL length" ); + has_errors = true; + } + break; + + case TFTP_OPCODE_ACK: + if ( len == 2 * sizeof( uint16_t ) ) { + T_log( + T_VERBOSE, + " ACK Block: %"PRIu16, + ntohs( *( ( (uint16_t *) buf ) + 1 ) ) + ); + } else { + T_log( T_VERBOSE, " ACK packet ILLEGAL length" ); + has_errors = true; + } + break; + + case TFTP_OPCODE_ERROR: + if ( len > 2 * sizeof( uint16_t ) ) { + uint16_t err_code = ntohs( *( ( (uint16_t *) buf ) + 1 ) ); + T_log( + T_VERBOSE, + " ERROR Code: %"PRIu16" (%s) ErrMsg:", + err_code, + _Tftp_Get_error_str( err_code ) + ); + buffer += sizeof( uint16_t ); + remaining_len -= sizeof( uint16_t ); + T_log( + T_VERBOSE, + " \"%s\"", + get_next_str( &buffer, &remaining_len, &has_errors ) + ); + } else { + T_log( T_VERBOSE, " ERROR packet ILLEGAL length" ); + has_errors = true; + } + break; + + case TFTP_OPCODE_OACK: + T_log( + T_VERBOSE, + " OACK %s", + ( remaining_len > 0 ) ? "Options:" : "No options" + ); + while ( remaining_len > 0 ) { + T_log( + T_VERBOSE, + " %s = \"%s\"", + get_next_str( &buffer, &remaining_len, &has_errors ), + get_next_str( &buffer, &remaining_len, &has_errors ) + ); + } + break; + + default: + T_log( T_VERBOSE, " TFTP ILLEGAL OpCode: %d", op_code ); + has_errors = true; + } + } else { + has_errors = true; + } + + if ( has_errors ) { + log_hex_dump( buf, len ); + } +} + +static void log_interaction( + Tftp_Action *act, + bool has_result, + bool was_success +) +{ + char *begin = has_result ? "[" : ""; + const char *action_name; + int result; + char result_buffer[20]; + result_buffer[0] = '\0'; + + if ( act == NULL ) { return; } + action_name = get_action_kind_as_string( act->kind ); + + if ( has_result && was_success ) { + switch ( act->kind ) { + case TFTP_IA_KIND_SOCKET: + result = act->data.socket.result; + break; + + case TFTP_IA_KIND_CLOSE: + result = act->data.close.result; + break; + + case TFTP_IA_KIND_BIND: + result = act->data.bind.result; + break; + + case TFTP_IA_KIND_SENDTO: + result = (int) act->data.sendto.result; + break; + + case TFTP_IA_KIND_RECVFROM: + result = (int) act->data.recvfrom.result; + break; + + default: + result = 0; + } + snprintf( result_buffer, sizeof( result_buffer ), "] = %d", result ); + } else if ( ! was_success ) { + snprintf( result_buffer, sizeof( result_buffer ), "] = FAILED!" ); + } + + switch ( act->kind ) { + case TFTP_IA_KIND_SOCKET: + T_log( + T_VERBOSE, + "%s%s(domain=%d, type=%d, protocol=%d)%s", + begin, + action_name, + act->data.socket.domain, + act->data.socket.type, + act->data.socket.protocol, + result_buffer + ); + break; + + case TFTP_IA_KIND_CLOSE: + T_log( T_VERBOSE, "%s%s(%d)%s", + begin, + action_name, + act->data.close.fd, + result_buffer + ); + break; + + case TFTP_IA_KIND_BIND: + T_log( + T_VERBOSE, + "%s%s(sockfd=%d, addr.family=%d, addr=%s:%"PRIu16")%s", + begin, + action_name, + act->data.bind.fd, + act->data.bind.family, + act->data.bind.addr_str, + act->data.bind.port, + result_buffer + ); + break; + + case TFTP_IA_KIND_SENDTO: + T_log( + T_VERBOSE, + "%s%s(sockfd=%d, buf=%p, len=%zu, flags=%X, " + "addr=%s:%"PRIu16", addrlen=%d)%s", + begin, + action_name, + act->data.sendto.fd, + act->data.sendto.buf, + act->data.sendto.len, + act->data.sendto.flags, + act->data.sendto.dest_addr_str, + act->data.sendto.dest_port, + act->data.sendto.addrlen, + result_buffer + ); + if ( !has_result ) { + log_tftp_packet( act->data.sendto.buf, act->data.sendto.len ); + } + break; + + case TFTP_IA_KIND_RECVFROM: + if ( !has_result ) { + T_log( + T_VERBOSE, + "%s%s(sockfd=%d, buf=%p, len=%zu, flags=%X, " + "timeout=%"PRIu32"ms, addr=?:?, addrlen=%d)%s", + begin, + action_name, + act->data.recvfrom.fd, + act->data.recvfrom.buf, + act->data.recvfrom.len, + act->data.recvfrom.flags, + act->data.recvfrom.timeout_ms, + act->data.recvfrom.addrlen, + result_buffer + ); + } else { + T_log( + T_VERBOSE, + "%s%s(sockfd=%d, buf=%p, len=%zu, flags=%X, " + "timeout=%"PRIu32"ms, addr=%s:%"PRIu16", addrlen=%d)%s", + begin, + action_name, + act->data.recvfrom.fd, + act->data.recvfrom.buf, + act->data.recvfrom.len, + act->data.recvfrom.flags, + act->data.recvfrom.timeout_ms, + act->data.recvfrom.src_addr_str, + act->data.recvfrom.src_port, + act->data.recvfrom.addrlen, + result_buffer + ); + if ( act->data.recvfrom.result > 0 && was_success ) { + log_tftp_packet( act->data.recvfrom.buf, act->data.recvfrom.result ); + } + } + break; + + default: + T_quiet_true( false, "Unknown interaction: %d", act->kind ); + } +} + +/* + * Note: This function *must* return. + * All the fake network functions (and any functions called from them) + * must return control to the TFTP client. Hence, do not use T_assert_*() + * or similar functions. Even if the test fails at some point, + * continue and return an error value to the client. + * Reason: T_assert_*() does stop the test and invokes teardown() + * without returning to the client. teardown() then "hangs" when + * un-mounting while executing client code. + */ +static bool process_interaction( Tftp_Action *act ) +{ + Tftp_Interaction *ia = get_next_interaction( control ); + bool result; + + T_quiet_not_null( act ); + if ( act == NULL ) { + return false; + } else { + /* Logging this early helps debugging defect tests */ + log_interaction( act, false, true ); + } + + T_quiet_not_null( ia ); + if( ia == NULL ) { + T_log( + T_NORMAL, + "ERROR: You have not registered any (more) 'interaction' but the TFTP " + "client invoked interaction '%s()'. See 'tftpfs_interactions.h' and use " + "'T_set_verbosity( T_VERBOSE )'.", + get_action_kind_as_string( act->kind ) + ); + return false; + } + + T_true( + act->kind == ia->kind, + "Expected %s() call but got %s()", + get_action_kind_as_string( ia->kind ), + get_action_kind_as_string( act->kind ) + ); + if ( act->kind != ia->kind ) { + return false; + } + result = ia->fn( act, ia->data ); + log_interaction( act, true, result ); + + return result; +} + +static uint16_t get_ip_addr_as_str( + const struct sockaddr *addr, + socklen_t addrlen, + char *buf, + size_t buf_size +) +{ + uint16_t port = 0xFFFF; + *buf = '\0'; + + switch ( addr->sa_family ) { + case AF_INET: + { + const struct sockaddr_in *ipv4 = (const struct sockaddr_in *) addr; + port = ntohs( ipv4->sin_port ); + const uint8_t *bytes = (const uint8_t *) &ipv4->sin_addr.s_addr; + snprintf( + buf, + buf_size, + "%"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8, + bytes[0], + bytes[1], + bytes[2], + bytes[3] + ); + break; + } + case AF_INET6: + { + const struct sockaddr_in6 *ipv6 = (const struct sockaddr_in6 *) addr; + port = ntohs( ipv6->sin6_port ); + const uint16_t *words = (const uint16_t *) ipv6->sin6_addr.s6_addr; + snprintf( + buf, + buf_size, + "%"PRIx16":%"PRIx16":%"PRIx16":%"PRIx16":%"PRIx16":%"PRIx16 + ":%"PRIx16":%"PRIx16, + ntohs( words[0] ), + ntohs( words[1] ), + ntohs( words[2] ), + ntohs( words[3] ), + ntohs( words[4] ), + ntohs( words[5] ), + ntohs( words[6] ), + ntohs( words[7] ) + ); + break; + } + } + + return port; +} + +static void set_ip_addr_from_str( + const char *addr_str, + uint16_t port, + struct sockaddr *addr, + socklen_t *addrlen + ) +{ + socklen_t addr_size; + int res; + int i; + + if ( addr == NULL || addrlen == NULL ) { + return; + } + addr_size = *addrlen; + + if ( strchr( addr_str, ':' ) == NULL ) { + /* IPv4 address */ + struct sockaddr_in *ipv4_addr = (struct sockaddr_in *) addr; + uint8_t *bytes = (uint8_t *) &ipv4_addr->sin_addr.s_addr; + + *addrlen = sizeof( struct sockaddr_in ); + T_ge_sz( addr_size, *addrlen ); + if ( addr_size < *addrlen ) { return; } + ipv4_addr->sin_family = AF_INET; + ipv4_addr->sin_port = htons( port ); + res = sscanf( + addr_str, + "%"SCNu8".%"SCNu8".%"SCNu8".%"SCNu8, + bytes, + bytes + 1, + bytes + 2, + bytes + 3 + ); + T_quiet_true( res == 4, "Connot parse IPv4 address: \"%s\"", addr_str ); + } else { + /* IPv6 address */ + struct sockaddr_in6 *ipv6_addr = (struct sockaddr_in6 *) addr; + uint16_t *words = (uint16_t *) &ipv6_addr->sin6_addr.s6_addr; + + *addrlen = sizeof( struct sockaddr_in6 ); + T_gt_sz( addr_size, *addrlen ); + if ( addr_size < *addrlen ) { return; } + ipv6_addr->sin6_family = AF_INET6; + ipv6_addr->sin6_port = htons( port ); + ipv6_addr->sin6_flowinfo = 0; + ipv6_addr->sin6_scope_id = 0; + res = sscanf( + addr_str, + "%"SCNx16":%"SCNx16":%"SCNx16":%"SCNx16":%"SCNx16":%"SCNx16 + ":%"SCNx16":%"SCNx16, + words, + words + 1, + words + 2, + words + 3, + words + 4, + words + 5, + words + 6, + words + 7 + ); + T_quiet_true( res == 8, "Connot parse IPv6 address: \"%s\"", addr_str ); + for ( i = 0; i < 8; ++i ) { + words[i] = htons( words[i] ); + } + } +} + +/* + * Fake networking functions + */ + +int inet_aton( const char *cp, struct in_addr *inp ) +{ + int result = 0; + uint8_t *ipv4_addr = (uint8_t *) inp; + static const char addr0[] = TFTP_KNOWN_IPV4_ADDR0_STR; + static const uint8_t addr0_data[] = { TFTP_KNOWN_IPV4_ADDR0_ARRAY }; + + if ( strcmp( addr0, cp ) == 0 ) { + memcpy( ipv4_addr, addr0_data, sizeof( addr0_data ) ); + result = 1; + } + + T_log( + T_VERBOSE, + "inet_aton(cp=%s, addr=%"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8") = %d", + cp, + ipv4_addr[0], + ipv4_addr[1], + ipv4_addr[2], + ipv4_addr[3], + result + ); + return result; +} + +struct hostent *gethostbyname( const char *name ) +{ + static char ip_addr_bytes[] = { + TFTP_KNOWN_SERVER0_ARRAY, /* IPv4: 10.7.0.2 "server.tftp" */ + TFTP_KNOWN_IPV4_ADDR0_ARRAY /* IPv4: 127.0.0.1 "127.0.0.1" */ + }; + static char *ip_addrs[] = { + ip_addr_bytes + 0 * IPV4_ADDR_SIZE, NULL, + ip_addr_bytes + 1 * IPV4_ADDR_SIZE, NULL + }; + static struct hostent hosts[] = { + { + .h_name = TFTP_KNOWN_SERVER0_NAME, + .h_aliases = NULL, + .h_addrtype = AF_INET, + .h_length = IPV4_ADDR_SIZE, + .h_addr_list = ip_addrs + 0, + }, + { + .h_name = TFTP_KNOWN_IPV4_ADDR0_STR, + .h_aliases = NULL, + .h_addrtype = AF_INET, + .h_length = IPV4_ADDR_SIZE, + .h_addr_list = ip_addrs + 2, + } + }; + struct hostent *result = NULL; + uint8_t *ipv4_addr; + int i; + + for ( i = 0; i < RTEMS_ARRAY_SIZE( hosts ); ++i ) { + if ( strcmp( hosts[i].h_name, name ) == 0 ) { + result = hosts + i; + } + } + /* Note: `h_errno` not set due to linker issues */ + + if ( result != NULL ) { + ipv4_addr = (uint8_t *) result->h_addr_list[0]; + T_log( + T_VERBOSE, + "gethostbyname(%s) = %s, %"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8, + name, + result->h_name, + ipv4_addr[0], + ipv4_addr[1], + ipv4_addr[2], + ipv4_addr[3] + ); + } else { + T_log( T_NORMAL, "gethostbyname(%s) = %p", name, result ); + } + return result; +} + +int socket( int domain, int type, int protocol ) +{ + Tftp_Action act = { + .kind = TFTP_IA_KIND_SOCKET, + .data.socket.domain = domain, + .data.socket.type = type, + .data.socket.protocol = protocol + }; + + if( !process_interaction( &act ) ) { + return -1; + }; + + /* Should never happen but check prevents programming mistakes */ + T_quiet_ge_int( act.data.socket.result, TFTP_FIRST_FD ); + if( act.data.socket.result < TFTP_FIRST_FD ) { + return -1; + }; + + return act.data.socket.result; +} + +int __wrap_close( int fd ) +{ + if ( fd < TFTP_FIRST_FD ) { + /* Normal file descriptors - i.e. not from fake socket() function above */ + return __real_close( fd ); + } + Tftp_Action act = { + .kind = TFTP_IA_KIND_CLOSE, + .data.close.fd = fd, + }; + + if( !process_interaction( &act ) ) { + return -1; + }; + + return act.data.close.result; +} + +int bind( int sockfd, const struct sockaddr *addr, socklen_t addrlen ) +{ + char addr_buf[INET6_ADDRSTRLEN]; + Tftp_Action act = { + .kind = TFTP_IA_KIND_BIND, + .data.bind.fd = sockfd, + .data.bind.family = addr->sa_family, + .data.bind.addr_str = addr_buf + }; + act.data.bind.port = get_ip_addr_as_str( + addr, + addrlen, + addr_buf, + sizeof( addr_buf ) + ); + + if( !process_interaction( &act ) ) { + return -1; + }; + + return act.data.bind.result; +} + +int setsockopt( + int sockfd, + int level, + int optname, + const void *optval, + socklen_t optlen +) +{ + int result = 0; + int i; + const struct timeval *tv = optval; + + T_log( + T_VERBOSE, + "setsockopt(sockfd=%d, level=%s, optname=%s, optval=%dms )", + sockfd, + ( level == SOL_SOCKET ) ? "SOL_SOCKET" : "UNKONWN", + ( optname == SO_RCVTIMEO ) ? "SO_RCVTIMEO" : "UNKONWN", + ( optname == SO_RCVTIMEO ) ? + (int) ( tv->tv_sec * 1000 + tv->tv_usec / 1000 ) : -1 + ); + + T_eq_int( level, SOL_SOCKET ); + T_eq_int( optname, SO_RCVTIMEO ); + T_eq_int( (int) optlen, sizeof( struct timeval ) ); + if ( + level != SOL_SOCKET || + optname != SO_RCVTIMEO || + optlen != sizeof( struct timeval ) + ) { + result = -1; + } + + for ( i = 0; result >= 0; ++i ) { + if ( i >= MAX_SOCKET_FD ) { + T_quiet_true( + false, + "Too few IP ports %d, increase MAX_SOCKET_FD", + MAX_SOCKET_FD + ); + result = -1; + break; + } + if ( control->receive_timeout_socket_fd[i] == sockfd || + control->receive_timeout_socket_fd[i] == 0 ) { + control->receive_timeout_socket_fd[i] = sockfd; + control->receive_timeout_ms[i] = tv->tv_sec * 1000 + tv->tv_usec / 1000; + break; + } + } + + T_log( + T_VERBOSE, + "[setsockopt(sockfd=%d, level=%s, optname=%s, optval=%"PRIu32"ms )] = %d", + sockfd, + ( level == SOL_SOCKET ) ? "SOL_SOCKET" : "UNKONWN", + ( optname == SO_RCVTIMEO ) ? "SO_RCVTIMEO" : "UNKONWN", + ( i < MAX_SOCKET_FD ) ? control->receive_timeout_ms[i] : -1, + result + ); + + return result; +} + +ssize_t sendto( + int sockfd, + const void *buf, + size_t len, + int flags, + const struct sockaddr *dest_addr, + socklen_t addrlen +) +{ + char addr_buf[INET6_ADDRSTRLEN]; + Tftp_Action act = { + .kind = TFTP_IA_KIND_SENDTO, + .data.sendto.fd = sockfd, + .data.sendto.buf = buf, + .data.sendto.len = len, + .data.sendto.flags = flags, + .data.sendto.dest_addr_str = addr_buf, + .data.sendto.addrlen = (int) addrlen, + }; + act.data.sendto.dest_port = get_ip_addr_as_str( + dest_addr, + addrlen, + addr_buf, + sizeof( addr_buf ) + ); + + if( !process_interaction( &act ) ) { + return -1; + }; + + return act.data.sendto.result; +} + +ssize_t recvfrom( + int sockfd, + void *buf, + size_t len, + int flags, + struct sockaddr *src_addr, + socklen_t *addrlen +) +{ + int i; + Tftp_Packet *pkt = buf; + Tftp_Action act = { + .kind = TFTP_IA_KIND_RECVFROM, + .data.recvfrom.fd = sockfd, + .data.recvfrom.buf = buf, + .data.recvfrom.len = len, + .data.recvfrom.flags = flags, + .data.recvfrom.timeout_ms = 0, + .data.recvfrom.addrlen = (int) *addrlen + }; + + for ( i = 0; i < MAX_SOCKET_FD; ++i ) { + if ( control->receive_timeout_socket_fd[i] == sockfd ) { + act.data.recvfrom.timeout_ms = control->receive_timeout_ms[i]; + break; + } + } + + /* Make log_tftp_packet() behave sane, if buf is not filled */ + if ( len >= sizeof( pkt->opcode ) ) { + pkt->opcode = ntohs( 0xFFFF ); + } + + if( !process_interaction( &act ) ) { + return -1; + }; + + set_ip_addr_from_str( + act.data.recvfrom.src_addr_str, + act.data.recvfrom.src_port, + src_addr, + addrlen + ); + + return act.data.recvfrom.result; +} diff --git a/testsuites/fstests/tftpfs/tftpfs_udp_network_fake.h b/testsuites/fstests/tftpfs/tftpfs_udp_network_fake.h new file mode 100644 index 0000000000..2c5860882f --- /dev/null +++ b/testsuites/fstests/tftpfs/tftpfs_udp_network_fake.h @@ -0,0 +1,315 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +/** + * @file + * + * @ingroup RTEMSTestSuiteTestsTFTPFS + * + * @brief This header file provides interfaces and functions used to + * implement the UDP network fake for tftpfs tests. + * + * Definitions and declarations of data structures and functions. + */ + +/* + * Copyright (C) 2022 embedded brains GmbH (http://www.embedded-brains.de) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include /* ssize_t */ + +#ifndef _TFTPFS_UDP_NETWORK_FAKE_H +#define _TFTPFS_UDP_NETWORK_FAKE_H + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @defgroup RTEMSTestSuiteTestsTFTPFS Test suite for libtftpsfs tests + * + * @ingroup RTEMSTestSuites + * + * @brief This test suite provides a tests for libtftpfs. + * + * There are some additional files relevant for the TFTP test suite: + * + * - `spec/build/testsuites/fstests/grp.yml`\n + * This file specifies how the RTEMS WAF build system has to compile, link + * and install all filesystem test suites. The TFTP test suite must + * be mentioned in this file to be build. + * + * - `spec/build/testsuites/fstests/tftpfs.yml`\n + * This file specifies how the RTEMS WAF build system has to compile, link + * and install the TFTP test suite. + * + * - `Doxygen`\n + * At variable `INPUT` the test suite is included to be processed by the + * Doxygen documentation generator. + * + * See also the _RTEMS Filesystem Design Guide_ Chapter _Trivial FTP Client + * Filesystem_. + * + * @{ + */ + +#define TFTP_STD_PORT 69 +#define TFTP_MAX_IP_ADDR_STRLEN (16 * 2 + 7 + 1) +#define TFTP_MAX_ERROR_STRLEN 20 +#define TFTP_MAX_OPTIONS_SIZE 40 + +/** + * @brief IP address strings and server names resolved by network fake + * functions like inet_aton() and gethostbyname(). + */ +#define TFTP_KNOWN_IPV4_ADDR0_STR "127.0.0.1" +#define TFTP_KNOWN_IPV4_ADDR0_ARRAY 127, 0, 0, 1 +#define TFTP_KNOWN_SERVER0_NAME "server.tftp" +#define TFTP_KNOWN_SERVER0_IPV4 "10.7.0.2" +#define TFTP_KNOWN_SERVER0_ARRAY 10, 7, 0, 2 + +/** + * @brief The faked socket() function (i.e. socket interaction) must return + * a file descriptor equal or larger than @c TFTP_FIRST_FD + * or -1. + */ +#define TFTP_FIRST_FD 33333 + +typedef enum Tftp_Action_kind { + TFTP_IA_KIND_SOCKET, + TFTP_IA_KIND_CLOSE, + TFTP_IA_KIND_BIND, + TFTP_IA_KIND_SENDTO, + TFTP_IA_KIND_RECVFROM +} Tftp_Action_kind; + +typedef struct Tftp_Action { + Tftp_Action_kind kind; + union { + struct { + int domain; + int type; + int protocol; + int result; + } socket; + struct { + int fd; + int result; + } close; + struct { + int fd; + int family; + uint16_t port; + const char *addr_str; + int result; + } bind; + struct { + int fd; + const void *buf; + size_t len; + int flags; + uint16_t dest_port; + const char *dest_addr_str; + int addrlen; + ssize_t result; + } sendto; + struct { + int fd; + void *buf; + size_t len; + int flags; + uint32_t timeout_ms; + uint16_t src_port; + char src_addr_str[TFTP_MAX_IP_ADDR_STRLEN]; + int addrlen; + ssize_t result; + } recvfrom; + } data; +} Tftp_Action; + +/** + * @brief Carry out interactions with TFTP client. + * + * @c Tftp_Interaction_fn() is called to + * + * * check that the fake network function has been called with the expected + * arguments (in @c act) + * * define values which shall be returned (to be stored in @c act) + * + * The function should not call @c T_assert_*() but use @c T_*(). + * Otherwise, it is unlikely that the test can terminate the client in + * @c teardown(). + * + * @param[in,out] act The actual arguments provided by the TFTP client + * to the network function. Moreover, storage to store the results + * to be returned to the TFTP client. + * @param data Arbitrary data area allocated when the interaction is created + * by @c _Tftp_Append_interaction() + * + * @retval true if the client behaved as expected. + * @retval false if the test shall fail. + */ +typedef bool (*Tftp_Interaction_fn)( Tftp_Action *act, void *data ); +typedef struct Tftp_Interaction Tftp_Interaction; +typedef struct Tftp_Interaction { + Tftp_Interaction *next; + Tftp_Action_kind kind; + Tftp_Interaction_fn fn; + void *data[0]; +} Tftp_Interaction; + +/** + * @brief Initialize and free the singleton control object. + * + * Invoke @c _Tftp_Reset() in @c setup() and @c teardown() of the test. + */ +void _Tftp_Reset( void ); + +/** + * @brief Create an interaction and append it to the sequence of expected + * interactions. + * + * This allocates memory for an interaction and additional specific data + * for the function @c fn() parameter @c data. The interaction is + * initialized and appended at the end of the sequence of expected interactions. + * If an error occurs a @c T_assert_*() macro is called. Hence, this function + * never returns @c NULL. + * + * @param kind Defines which interaction is expected. Note that it cannot + * happen that @c fn is called for a different network function. + * @param fn A function which is called to handle the interaction. + * See @c Tftp_Interaction_fn() + * @param size The size of a memory area which is given to @c fn() as + * @c data argument when it is invoked. This can be used to provide + * private data to the function. + * + * @return A pointer to a memory area of size @c size. The same pointer + * will be provided to @c fn as argument @c data when invoked. + */ +void *_Tftp_Append_interaction( + Tftp_Action_kind kind, + Tftp_Interaction_fn fn, + size_t size +); + + +/** + * @brief Have all queued interactions been processed? + * + * At the end of a test, it should be checked whether all queued interactions + * have been consumed by the TFTP client. + * + * @retval true All queued interactions have been processed. + * @retval false At least one queued interactions has not yet been processed. + */ +bool _Tftp_Has_no_more_interactions( void ); + +/* + * TFTP details from RFC1350, RFC2347, RFC2348 and RFC7440 + * + * Note: The RFCs require modes and options to be case in-sensitive. + */ + +#define TFTP_MODE_NETASCII "netascii" +#define TFTP_MODE_OCTET "octet" +#define TFTP_OPTION_BLKSIZE "blksize" +#define TFTP_OPTION_TIMEOUT "timeout" +#define TFTP_OPTION_TSIZE "tsize" +#define TFTP_OPTION_WINDOWSIZE "windowsize" + +#define TFTP_WINDOW_SIZE_MIN 1 +#define TFTP_BLOCK_SIZE_MIN 8 +#define TFTP_BLOCK_SIZE_MAX 65464 + +typedef enum Tftp_Opcode { + TFTP_OPCODE_RRQ = 1, + TFTP_OPCODE_WRQ = 2, + TFTP_OPCODE_DATA = 3, + TFTP_OPCODE_ACK = 4, + TFTP_OPCODE_ERROR = 5, + TFTP_OPCODE_OACK = 6, +} Tftp_Opcode; + +typedef enum Tftp_Error_code { + TFTP_ERROR_CODE_NOT_DEFINED = 0, + TFTP_ERROR_CODE_NOT_FOUND = 1, + TFTP_ERROR_CODE_NO_ACCESS = 2, + TFTP_ERROR_CODE_DISK_FULL = 3, + TFTP_ERROR_CODE_ILLEGAL = 4, + TFTP_ERROR_CODE_UNKNOWN_ID = 5, + TFTP_ERROR_CODE_FILE_EXISTS = 6, + TFTP_ERROR_CODE_NO_USER = 7, + TFTP_ERROR_CODE_OPTION_NEGO = 8, +} Tftp_Error_code; + +typedef struct Tftp_Packet { + uint16_t opcode; + union { + struct { + char opts[0]; + } rrq; + struct { + char opts[0]; + } wrq; + struct { + uint16_t block_num; + uint8_t bytes[0]; + } data; + struct { + uint16_t block_num; + } ack; + struct { + uint16_t error_code; + char err_msg[0]; + } error; + struct { + char opts[0]; + } oack; + } content; +} Tftp_Packet; + +/** + * @brief Provides a human readable description for an error code from an TFTP + * error packet. + * + * @param error_code The error code from the TFTP error packet in host byte + * order. + * + * @return A pointer to a string describing the error. If the error code is + * unknown, a pointer to "Unknown error code" is returned. Do not change the + * the string as a pointer to the very same string will be returned by future + * calls. + */ +const char *_Tftp_Get_error_str( uint16_t error_code ); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* _TFTPFS_UDP_NETWORK_FAKE_H */ -- cgit v1.2.3