summaryrefslogtreecommitdiffstats
path: root/testsuites/fstests/tftpfs
diff options
context:
space:
mode:
Diffstat (limited to 'testsuites/fstests/tftpfs')
-rw-r--r--testsuites/fstests/tftpfs/init.c6928
-rw-r--r--testsuites/fstests/tftpfs/tftpfs_interactions.c984
-rw-r--r--testsuites/fstests/tftpfs/tftpfs_interactions.h213
-rw-r--r--testsuites/fstests/tftpfs/tftpfs_udp_network_fake.c983
-rw-r--r--testsuites/fstests/tftpfs/tftpfs_udp_network_fake.h315
5 files changed, 9423 insertions, 0 deletions
diff --git a/testsuites/fstests/tftpfs/init.c b/testsuites/fstests/tftpfs/init.c
new file mode 100644
index 0000000000..38e429f11e
--- /dev/null
+++ b/testsuites/fstests/tftpfs/init.c
@@ -0,0 +1,6928 @@
+/* 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 & Co. KG
+ *
+ * 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 <stdio.h>
+#include <stdlib.h> /* malloc(), free() */
+#include <ctype.h> /* isprint() */
+#include <errno.h>
+#include <sys/stat.h> /* mkdir(), open() */
+#include <sys/types.h> /* mkdir(), open() */
+#include <sys/socket.h> /* AF_INET, SOCK_DGRAM */
+#include <fcntl.h> /* open() */
+#include <unistd.h> /* read(), close(), rmdir() */
+
+#include <rtems/tftp.h>
+#include <rtems/libio.h> /* mount(), RTEMS_FILESYSTEM_TYPE_TFTPFS */
+#include <rtems/test.h>
+#include <rtems/test-info.h>
+#include <rtems/testopts.h> /* RTEMS_TEST_VERBOSITY */
+#include <rtems.h>
+
+#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] = { 0 };
+ 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();
+}
+
+const char rtems_test_name[] = "TFTPFS";
+
+static void Init( rtems_task_argument argument )
+{
+ rtems_test_run( argument, TEST_STATE );
+}
+
+/*
+ * RTEMS configuration for tftp
+ */
+
+#define CONFIGURE_FILESYSTEM_TFTPFS
+#define CONFIGURE_MAXIMUM_FILE_DESCRIPTORS 4
+
+/*
+ * Simple RTEMS configuration
+ */
+
+#define CONFIGURE_APPLICATION_NEEDS_CLOCK_DRIVER
+#define CONFIGURE_APPLICATION_NEEDS_SIMPLE_CONSOLE_DRIVER
+
+#define CONFIGURE_MAXIMUM_TASKS 1
+
+#define CONFIGURE_RTEMS_INIT_TASKS_TABLE
+
+#define CONFIGURE_INIT_TASK_ATTRIBUTES RTEMS_FLOATING_POINT
+
+#define CONFIGURE_INIT
+
+#include <rtems/confdefs.h>
diff --git a/testsuites/fstests/tftpfs/tftpfs_interactions.c b/testsuites/fstests/tftpfs/tftpfs_interactions.c
new file mode 100644
index 0000000000..9ab026c5da
--- /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 & Co. KG
+ *
+ * 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 <stdio.h> /* sprintf() */
+#include <inttypes.h> /* printf() macros like PRId8 */
+#include <arpa/inet.h> /* ntohs() */
+#include <rtems/test.h>
+
+
+#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..732a542e99
--- /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 & Co. KG
+ *
+ * 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..995ae05fe0
--- /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 & Co. KG
+ *
+ * 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 <stdio.h> /* snprintf() */
+#include <stdlib.h> /* malloc(), free() */
+#include <inttypes.h> /* printf() macros like PRId8 */
+#include <string.h>
+#include <ctype.h> /* isprint() */
+#include <netdb.h> /* getnameinfo() */
+#include <arpa/inet.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h> /* struct sockaddr_in, struct sockaddr_in6 */
+#include <rtems/test.h>
+
+#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..0a852353ad
--- /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 & Co. KG
+ *
+ * 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 <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h> /* 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 RTEMSTestSuitesFilesystem
+ *
+ * @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 */