[PATCH 5/7] TFTPFS: Add test suite framework

Frank Kuehndel frank.kuehndel at embedded-brains.de
Fri Jun 3 15:22:44 UTC 2022


From: Frank Kühndel <frank.kuehndel at embedded-brains.de>

---
 spec/build/testsuites/fstests/grp.yml         |    2 +
 spec/build/testsuites/fstests/tftpfs.yml      |   25 +
 testsuites/fstests/tftpfs/init.c              | 1054 +++++++++++++++++
 .../fstests/tftpfs/tftpfs_interactions.c      |  984 +++++++++++++++
 .../fstests/tftpfs/tftpfs_interactions.h      |  205 ++++
 .../fstests/tftpfs/tftpfs_udp_network_fake.c  |  983 +++++++++++++++
 .../fstests/tftpfs/tftpfs_udp_network_fake.h  |  283 +++++
 7 files changed, 3536 insertions(+)
 create mode 100644 spec/build/testsuites/fstests/tftpfs.yml
 create mode 100644 testsuites/fstests/tftpfs/init.c
 create mode 100644 testsuites/fstests/tftpfs/tftpfs_interactions.c
 create mode 100644 testsuites/fstests/tftpfs/tftpfs_interactions.h
 create mode 100644 testsuites/fstests/tftpfs/tftpfs_udp_network_fake.c
 create mode 100644 testsuites/fstests/tftpfs/tftpfs_udp_network_fake.h

diff --git a/spec/build/testsuites/fstests/grp.yml b/spec/build/testsuites/fstests/grp.yml
index 378157d3dc..ed8917504a 100644
--- a/spec/build/testsuites/fstests/grp.yml
+++ b/spec/build/testsuites/fstests/grp.yml
@@ -138,6 +138,8 @@ links:
   uid: mrfsfssymlink
 - role: build-dependency
   uid: mrfsfstime
+- role: build-dependency
+  uid: tftpfs
 type: build
 use-after: []
 use-before:
diff --git a/spec/build/testsuites/fstests/tftpfs.yml b/spec/build/testsuites/fstests/tftpfs.yml
new file mode 100644
index 0000000000..37d55d4132
--- /dev/null
+++ b/spec/build/testsuites/fstests/tftpfs.yml
@@ -0,0 +1,25 @@
+SPDX-License-Identifier: CC-BY-SA-4.0 OR BSD-2-Clause
+build-type: test-program
+cflags: []
+copyrights:
+- Copyright (C) 2020 embedded brains GmbH (http://www.embedded-brains.de)
+cppflags: []
+cxxflags: []
+enabled-by: true
+features: c cprogram
+includes:
+- cpukit/libfs/src/ftpfs
+ldflags:
+- -Wl,--wrap=close
+links: []
+source:
+- cpukit/libtest/testwrappers.c
+- testsuites/fstests/tftpfs/init.c
+- testsuites/fstests/tftpfs/tftpfs_udp_network_fake.c
+- testsuites/fstests/tftpfs/tftpfs_interactions.c
+stlib:
+- tftpfs
+target: testsuites/fstests/tftpfs.exe
+type: build
+use-after: []
+use-before: []
diff --git a/testsuites/fstests/tftpfs/init.c b/testsuites/fstests/tftpfs/init.c
new file mode 100644
index 0000000000..91308ec75b
--- /dev/null
+++ b/testsuites/fstests/tftpfs/init.c
@@ -0,0 +1,1054 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+
+/**
+ * @file
+ *
+ * @ingroup RTEMSTestSuiteTestsTftpfs
+ *
+ * @brief This source file contains the implementation of tests for libtftpfs.
+ *
+ * The tested source files are:
+ *   + @ref tftpfs.c "tftpfs.c: TFTP file system"
+ *   + @ref tftpDriver.c "tftpDriver.c: TFTP client library"
+ * These tests focus on testing the UDP network interaction of libtftpfs.
+ */
+
+/*
+ * Copyright (C) 2022 embedded brains GmbH (http://www.embedded-brains.de)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @defgroup RTEMSTestSuiteTestsTftpfs Test suite for libtftpsfs tests
+ *
+ * @ingroup RTEMSTestSuites
+ *
+ * @brief This test suite provides a tests for libtftpfs.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#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." \
+)
+#define ENABLE_ALL_TESTS 1
+
+/*
+ * 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
+};
+
+/*
+ * 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';
+  }
+}
+
+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;
+}
+
+/*
+ * 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.
+ */
+
+#if ENABLE_ALL_TESTS
+/*
+ * 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 );
+#ifdef RTEMS_NETWORKING
+  _Tftp_Add_interaction_bind( TFTP_FIRST_FD, AF_INET, 0 );
+#endif
+  _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();
+}
+#endif /* ENABLE_ALL_TESTS */
+
+/*
+ * Test cases for the file system interface
+ */
+
+#if ENABLE_ALL_TESTS
+/*
+ * 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 );
+#ifdef RTEMS_NETWORKING
+  _Tftp_Add_interaction_bind( TFTP_FIRST_FD, AF_INET, 0 );
+#endif
+  _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();
+}
+#endif /* ENABLE_ALL_TESTS */
+
+#if ENABLE_ALL_TESTS
+/*
+ * 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 );
+#ifdef RTEMS_NETWORKING
+  _Tftp_Add_interaction_bind( TFTP_FIRST_FD, AF_INET, 0 );
+#endif
+  _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();
+}
+#endif /* ENABLE_ALL_TESTS */
+
+/*
+ * Test suite and configuration
+ */
+
+const char rtems_test_name[] = "TFTPFS";
+
+static char buffer[ 512 ];
+
+static const T_action actions[] = {
+  T_report_hash_sha256,
+  T_check_task_context,
+  T_check_file_descriptors,
+  T_check_rtems_barriers,
+  T_check_rtems_extensions,
+  T_check_rtems_message_queues,
+  T_check_rtems_partitions,
+  T_check_rtems_periods,
+  T_check_rtems_regions,
+  T_check_rtems_semaphores,
+  T_check_rtems_tasks,
+  T_check_rtems_timers,
+  T_check_posix_keys
+};
+
+static const T_config config = {
+  .name = rtems_test_name,
+  .buf = buffer,
+  .buf_size = sizeof( buffer ),
+  .putchar = T_putchar_default,
+  .verbosity = RTEMS_TEST_VERBOSITY,
+  .now = T_now_clock,
+  .allocate = T_memory_allocate,
+  .deallocate = T_memory_deallocate,
+  .action_count = T_ARRAY_SIZE( actions ),
+  .actions = actions
+};
+
+static void Init( rtems_task_argument argument )
+{
+  (void) argument;
+  int exit_code;
+
+  /*
+   * It would be much easier to simply use
+   *    rtems_test_run( argument, TEST_STATE );
+   * instead of all the code below and the variables
+   *    buffer, actions, config
+   * above. Yet, rtems_test_run() sets the verbosity always to
+   * T_VERBOSE and this would produce plenty of output.
+   */
+  rtems_test_begin( rtems_test_name, TEST_STATE );
+  T_register();
+  exit_code = T_main( &config );
+
+  if ( exit_code == 0 ) {
+    rtems_test_end( rtems_test_name );
+  }
+
+  rtems_fatal( RTEMS_FATAL_SOURCE_EXIT, (uint32_t) exit_code );
+}
+
+/*
+ * RTEMS configuration for tftp
+ */
+
+#define CONFIGURE_FILESYSTEM_TFTPFS
+#define CONFIGURE_MAXIMUM_FILE_DESCRIPTORS 64
+
+/*
+ * Simple RTEMS configuration
+ */
+
+#define CONFIGURE_APPLICATION_NEEDS_CLOCK_DRIVER
+#define CONFIGURE_APPLICATION_NEEDS_CONSOLE_DRIVER
+
+#define CONFIGURE_UNLIMITED_OBJECTS
+#define CONFIGURE_UNIFIED_WORK_AREAS
+
+#define CONFIGURE_RTEMS_INIT_TASKS_TABLE
+
+#define CONFIGURE_INIT
+
+#include <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..873e7de95a
--- /dev/null
+++ b/testsuites/fstests/tftpfs/tftpfs_interactions.c
@@ -0,0 +1,984 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+
+/**
+ * @file
+ *
+ * @ingroup RTEMSTestSuiteTestsTftpfs
+ *
+ * @brief This source file contains the implementation of network interaction
+ *   functions related to the UDP network fake for tftpfs testing.
+ *
+ * The UDP Network Fake requires *interactions* between TFTP client and test
+ * (which emulates a TFTP server).  The idea is that each test defines a
+ * sequence of interactions.  In a successful test run all interactions must
+ * be carried out one-by-one till the *last* interaction is reached.
+ *
+ * Interactions appear when the TFTP client calls functions like
+ * sendto(), recvfrom(), or socket().  Here functions are defined
+ * which
+ *
+ *   * handle such interactions and
+ *   * permit the tests to easily defined the sequence of interactions.
+ */
+
+/*
+ * Copyright (C) 2022 embedded brains GmbH (http://www.embedded-brains.de)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <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..92c20c8cf4
--- /dev/null
+++ b/testsuites/fstests/tftpfs/tftpfs_interactions.h
@@ -0,0 +1,205 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+
+/**
+ * @file
+ *
+ * @ingroup RTEMSTestSuiteTestsTftpfs
+ *
+ * @brief This header file provides functions used to
+ *   implement network interactions of the UDP network fake for tftpfs tests.
+ *
+ * Definitions and declarations of data structures and functions.
+ */
+
+/*
+ * Copyright (C) 2022 embedded brains GmbH (http://www.embedded-brains.de)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _TFTPFS_INTERACTIONS_H
+#define _TFTPFS_INTERACTIONS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define NO_BLOCK_SIZE_OPTION 0
+#define NO_WINDOW_SIZE_OPTION 0
+#define DO_NOT_WAIT_FOR_ANY_TIMEOUT UINT32_MAX
+
+/*
+ * 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..4a79920c1c
--- /dev/null
+++ b/testsuites/fstests/tftpfs/tftpfs_udp_network_fake.c
@@ -0,0 +1,983 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+
+/**
+ * @file
+ *
+ * @ingroup RTEMSTestSuiteTestsTftpfs
+ *
+ * @brief This source file contains the implementation of UDP network fake
+ *   functions related to tftpfs testing.
+ *
+ * The libtftpfs source code is situated in the RTEMS repository.  For
+ * testing it, either libbsd or RTEMS legacy networking would be required.
+ * This implies that the tests for libtftpfs would need to be placed in
+ * the libbsd repository - a different one than the libtftpfs source code.
+ *
+ * Yet, libtftpfs uses only a handful of networking functions.  This
+ * file provides fake implementations of those functions.  These fake
+ * functions permit to simulate the exchange of UDP packets
+ * with the libtftpfs code and thus permits testing libtftpfs without
+ * the need of a full network stack.
+ */
+
+/*
+ * Copyright (C) 2022 embedded brains GmbH (http://www.embedded-brains.de)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <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..7398e99c68
--- /dev/null
+++ b/testsuites/fstests/tftpfs/tftpfs_udp_network_fake.h
@@ -0,0 +1,283 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+
+/**
+ * @file
+ *
+ * @ingroup RTEMSTestSuiteTestsTftpfs
+ *
+ * @brief This header file provides interfaces and functions used to
+ *   implement the UDP network fake for tftpfs tests.
+ *
+ * Definitions and declarations of data structures and functions.
+ */
+
+/*
+ * Copyright (C) 2022 embedded brains GmbH (http://www.embedded-brains.de)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <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
+
+#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 @c true if the client behaved as expected.
+ * @retval @c 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 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 */
-- 
2.35.3



More information about the devel mailing list