TFTPFS: Add tests

Update #4666.
This commit is contained in:
Frank Kühndel
2022-06-01 19:14:02 +02:00
committed by Sebastian Huber
parent 679e7f109a
commit 3e2b4ec857
8 changed files with 9506 additions and 1 deletions

View File

@@ -812,7 +812,10 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
INPUT = bsps cpukit testsuites/validation
INPUT = bsps \
cpukit \
testsuites/fstests/tftpfs \
testsuites/validation
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses

View File

@@ -138,6 +138,8 @@ links:
uid: mrfsfssymlink
- role: build-dependency
uid: mrfsfstime
- role: build-dependency
uid: tftpfs
type: build
use-after: []
use-before:

View File

@@ -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: []

File diff suppressed because it is too large Load Diff

View File

@@ -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;
}

View File

@@ -0,0 +1,213 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/**
* @file
*
* @ingroup RTEMSTestSuiteTestsTFTPFS
*
* @brief This header file provides functions used to
* implement network interactions of the UDP network fake for tftpfs tests.
*
* Definitions and declarations of data structures and functions.
*/
/*
* Copyright (C) 2022 embedded brains GmbH (http://www.embedded-brains.de)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _TFTPFS_INTERACTIONS_H
#define _TFTPFS_INTERACTIONS_H
#ifdef __cplusplus
extern "C" {
#endif
#define NO_BLOCK_SIZE_OPTION 0
#define NO_WINDOW_SIZE_OPTION 0
#define DO_NOT_WAIT_FOR_ANY_TIMEOUT UINT32_MAX
/**
* @addtogroup RTEMSTestSuiteTestsTFTPFS
*
* @{
*/
/*
* These functions append an interaction to the list of expected interactions.
* For example, _Tftp_Add_interaction_socket() "means":
*
* * As next interaction, expect that the TFTP client calls function
* socket().
* * Expect (i.e. check) that this socket() call will get parameter values
* as provided by its parameters `domain`, `type` and `protocol`.
* * This call to socket() shall return value of parameter `result`
* to the TFTP client.
*
* The interactions with functions sendto() and recvfrom() are a bit more
* complicated because specific packets are expected to be received or sent.
* For example, _Tftp_Add_interaction_send_rrq() appends an interaction
* where the TFTP client is expected to call sendto() with an RRQ (Read
* Request) packet. _Tftp_Add_interaction_recv_data() appends an interaction
* where the TFTP client is expected to call recvfrom() and as result it
* receives a data packet (which the interaction writes into the buffer
* which the TFTP client provides as parameter in its the recvfrom() call).
*/
void _Tftp_Add_interaction_socket(
int domain,
int type,
int protocol,
int result
);
void _Tftp_Add_interaction_close( int fd, int result );
void _Tftp_Add_interaction_bind( int fd, int family, int result );
void _Tftp_Add_interaction_send_rrq(
int fd,
const char *filename,
uint16_t dest_port,
const char *dest_addr_str,
uint16_t block_size,
uint16_t window_size,
bool result
);
void _Tftp_Add_interaction_send_wrq(
int fd,
const char *filename,
uint16_t dest_port,
const char *dest_addr_str,
uint16_t block_size,
uint16_t window_size,
bool result
);
void _Tftp_Add_interaction_send_ack(
int fd,
uint16_t block_num,
uint16_t dest_port,
const char *dest_addr_str,
bool result
);
void _Tftp_Add_interaction_send_data(
int fd,
uint16_t block_num,
size_t start,
size_t len,
uint8_t (*get_data)( size_t pos ),
uint16_t dest_port,
const char *dest_addr_str,
bool result
);
void _Tftp_Add_interaction_send_error(
int fd,
uint16_t error_code,
uint16_t dest_port,
const char *dest_addr_str,
bool result
);
/*
* _Tftp_Add_interaction_recv_data() permits only a boolean result.
* The TFTP client code does not check `errno` and always behaves as if
* a return of -1 indicates a timeout. Hence
* _Tftp_Add_interaction_recv_data() permits only a boolean result
* and no special value to distinct timeouts from other errors.
*/
void _Tftp_Add_interaction_recv_data(
int fd,
uint32_t timeout_ms,
uint16_t src_port,
const char *src_addr_str,
uint16_t block_num,
size_t start,
size_t len,
uint8_t (*get_data)( size_t pos ),
bool result
);
void _Tftp_Add_interaction_recv_ack(
int fd,
uint32_t timeout_ms,
uint16_t src_port,
const char *src_addr_str,
uint16_t block_num,
bool result
);
void _Tftp_Add_interaction_recv_oack(
int fd,
uint32_t timeout_ms,
uint16_t src_port,
const char *src_addr_str,
const char *options,
size_t options_size,
bool result
);
void _Tftp_Add_interaction_recv_error(
int fd,
uint32_t timeout_ms,
uint16_t src_port,
const char *src_addr_str,
uint16_t error_code,
const char *err_msg,
bool result
);
/*
* The TFTP client receives a "raw" packet.
*
* Test tests use this function to trigger packet format errors such as:
*
* * Too short packets,
* * Strings without 0-byte at their end
* * Wrong op-codes
*/
void _Tftp_Add_interaction_recv_raw(
int fd,
uint32_t timeout_ms,
uint16_t src_port,
const char *src_addr_str,
size_t len,
const uint8_t *bytes,
bool result
);
void _Tftp_Add_interaction_recv_nothing(
int fd,
uint32_t timeout_ms
);
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* _TFTPFS_INTERACTIONS_H */

View File

@@ -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;
}

View File

@@ -0,0 +1,315 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/**
* @file
*
* @ingroup RTEMSTestSuiteTestsTFTPFS
*
* @brief This header file provides interfaces and functions used to
* implement the UDP network fake for tftpfs tests.
*
* Definitions and declarations of data structures and functions.
*/
/*
* Copyright (C) 2022 embedded brains GmbH (http://www.embedded-brains.de)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <sys/types.h> /* ssize_t */
#ifndef _TFTPFS_UDP_NETWORK_FAKE_H
#define _TFTPFS_UDP_NETWORK_FAKE_H
#ifdef __cplusplus
extern "C" {
#endif
/**
* @defgroup RTEMSTestSuiteTestsTFTPFS Test suite for libtftpsfs tests
*
* @ingroup RTEMSTestSuites
*
* @brief This test suite provides a tests for libtftpfs.
*
* There are some additional files relevant for the TFTP test suite:
*
* - `spec/build/testsuites/fstests/grp.yml`\n
* This file specifies how the RTEMS WAF build system has to compile, link
* and install all filesystem test suites. The TFTP test suite must
* be mentioned in this file to be build.
*
* - `spec/build/testsuites/fstests/tftpfs.yml`\n
* This file specifies how the RTEMS WAF build system has to compile, link
* and install the TFTP test suite.
*
* - `Doxygen`\n
* At variable `INPUT` the test suite is included to be processed by the
* Doxygen documentation generator.
*
* See also the _RTEMS Filesystem Design Guide_ Chapter _Trivial FTP Client
* Filesystem_.
*
* @{
*/
#define TFTP_STD_PORT 69
#define TFTP_MAX_IP_ADDR_STRLEN (16 * 2 + 7 + 1)
#define TFTP_MAX_ERROR_STRLEN 20
#define TFTP_MAX_OPTIONS_SIZE 40
/**
* @brief IP address strings and server names resolved by network fake
* functions like inet_aton() and gethostbyname().
*/
#define TFTP_KNOWN_IPV4_ADDR0_STR "127.0.0.1"
#define TFTP_KNOWN_IPV4_ADDR0_ARRAY 127, 0, 0, 1
#define TFTP_KNOWN_SERVER0_NAME "server.tftp"
#define TFTP_KNOWN_SERVER0_IPV4 "10.7.0.2"
#define TFTP_KNOWN_SERVER0_ARRAY 10, 7, 0, 2
/**
* @brief The faked socket() function (i.e. socket interaction) must return
* a file descriptor equal or larger than @c TFTP_FIRST_FD
* or -1.
*/
#define TFTP_FIRST_FD 33333
typedef enum Tftp_Action_kind {
TFTP_IA_KIND_SOCKET,
TFTP_IA_KIND_CLOSE,
TFTP_IA_KIND_BIND,
TFTP_IA_KIND_SENDTO,
TFTP_IA_KIND_RECVFROM
} Tftp_Action_kind;
typedef struct Tftp_Action {
Tftp_Action_kind kind;
union {
struct {
int domain;
int type;
int protocol;
int result;
} socket;
struct {
int fd;
int result;
} close;
struct {
int fd;
int family;
uint16_t port;
const char *addr_str;
int result;
} bind;
struct {
int fd;
const void *buf;
size_t len;
int flags;
uint16_t dest_port;
const char *dest_addr_str;
int addrlen;
ssize_t result;
} sendto;
struct {
int fd;
void *buf;
size_t len;
int flags;
uint32_t timeout_ms;
uint16_t src_port;
char src_addr_str[TFTP_MAX_IP_ADDR_STRLEN];
int addrlen;
ssize_t result;
} recvfrom;
} data;
} Tftp_Action;
/**
* @brief Carry out interactions with TFTP client.
*
* @c Tftp_Interaction_fn() is called to
*
* * check that the fake network function has been called with the expected
* arguments (in @c act)
* * define values which shall be returned (to be stored in @c act)
*
* The function should not call @c T_assert_*() but use @c T_*().
* Otherwise, it is unlikely that the test can terminate the client in
* @c teardown().
*
* @param[in,out] act The actual arguments provided by the TFTP client
* to the network function. Moreover, storage to store the results
* to be returned to the TFTP client.
* @param data Arbitrary data area allocated when the interaction is created
* by @c _Tftp_Append_interaction()
*
* @retval true if the client behaved as expected.
* @retval false if the test shall fail.
*/
typedef bool (*Tftp_Interaction_fn)( Tftp_Action *act, void *data );
typedef struct Tftp_Interaction Tftp_Interaction;
typedef struct Tftp_Interaction {
Tftp_Interaction *next;
Tftp_Action_kind kind;
Tftp_Interaction_fn fn;
void *data[0];
} Tftp_Interaction;
/**
* @brief Initialize and free the singleton control object.
*
* Invoke @c _Tftp_Reset() in @c setup() and @c teardown() of the test.
*/
void _Tftp_Reset( void );
/**
* @brief Create an interaction and append it to the sequence of expected
* interactions.
*
* This allocates memory for an interaction and additional specific data
* for the function @c fn() parameter @c data. The interaction is
* initialized and appended at the end of the sequence of expected interactions.
* If an error occurs a @c T_assert_*() macro is called. Hence, this function
* never returns @c NULL.
*
* @param kind Defines which interaction is expected. Note that it cannot
* happen that @c fn is called for a different network function.
* @param fn A function which is called to handle the interaction.
* See @c Tftp_Interaction_fn()
* @param size The size of a memory area which is given to @c fn() as
* @c data argument when it is invoked. This can be used to provide
* private data to the function.
*
* @return A pointer to a memory area of size @c size. The same pointer
* will be provided to @c fn as argument @c data when invoked.
*/
void *_Tftp_Append_interaction(
Tftp_Action_kind kind,
Tftp_Interaction_fn fn,
size_t size
);
/**
* @brief Have all queued interactions been processed?
*
* At the end of a test, it should be checked whether all queued interactions
* have been consumed by the TFTP client.
*
* @retval true All queued interactions have been processed.
* @retval false At least one queued interactions has not yet been processed.
*/
bool _Tftp_Has_no_more_interactions( void );
/*
* TFTP details from RFC1350, RFC2347, RFC2348 and RFC7440
*
* Note: The RFCs require modes and options to be case in-sensitive.
*/
#define TFTP_MODE_NETASCII "netascii"
#define TFTP_MODE_OCTET "octet"
#define TFTP_OPTION_BLKSIZE "blksize"
#define TFTP_OPTION_TIMEOUT "timeout"
#define TFTP_OPTION_TSIZE "tsize"
#define TFTP_OPTION_WINDOWSIZE "windowsize"
#define TFTP_WINDOW_SIZE_MIN 1
#define TFTP_BLOCK_SIZE_MIN 8
#define TFTP_BLOCK_SIZE_MAX 65464
typedef enum Tftp_Opcode {
TFTP_OPCODE_RRQ = 1,
TFTP_OPCODE_WRQ = 2,
TFTP_OPCODE_DATA = 3,
TFTP_OPCODE_ACK = 4,
TFTP_OPCODE_ERROR = 5,
TFTP_OPCODE_OACK = 6,
} Tftp_Opcode;
typedef enum Tftp_Error_code {
TFTP_ERROR_CODE_NOT_DEFINED = 0,
TFTP_ERROR_CODE_NOT_FOUND = 1,
TFTP_ERROR_CODE_NO_ACCESS = 2,
TFTP_ERROR_CODE_DISK_FULL = 3,
TFTP_ERROR_CODE_ILLEGAL = 4,
TFTP_ERROR_CODE_UNKNOWN_ID = 5,
TFTP_ERROR_CODE_FILE_EXISTS = 6,
TFTP_ERROR_CODE_NO_USER = 7,
TFTP_ERROR_CODE_OPTION_NEGO = 8,
} Tftp_Error_code;
typedef struct Tftp_Packet {
uint16_t opcode;
union {
struct {
char opts[0];
} rrq;
struct {
char opts[0];
} wrq;
struct {
uint16_t block_num;
uint8_t bytes[0];
} data;
struct {
uint16_t block_num;
} ack;
struct {
uint16_t error_code;
char err_msg[0];
} error;
struct {
char opts[0];
} oack;
} content;
} Tftp_Packet;
/**
* @brief Provides a human readable description for an error code from an TFTP
* error packet.
*
* @param error_code The error code from the TFTP error packet in host byte
* order.
*
* @return A pointer to a string describing the error. If the error code is
* unknown, a pointer to "Unknown error code" is returned. Do not change the
* the string as a pointer to the very same string will be returned by future
* calls.
*/
const char *_Tftp_Get_error_str( uint16_t error_code );
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* _TFTPFS_UDP_NETWORK_FAKE_H */