forked from Imagelibrary/rtems
committed by
Sebastian Huber
parent
679e7f109a
commit
3e2b4ec857
5
Doxyfile
5
Doxyfile
@@ -812,7 +812,10 @@ WARN_LOGFILE =
|
|||||||
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
||||||
# Note: If this tag is empty the current directory is searched.
|
# 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
|
# 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
|
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
|
||||||
|
|||||||
@@ -138,6 +138,8 @@ links:
|
|||||||
uid: mrfsfssymlink
|
uid: mrfsfssymlink
|
||||||
- role: build-dependency
|
- role: build-dependency
|
||||||
uid: mrfsfstime
|
uid: mrfsfstime
|
||||||
|
- role: build-dependency
|
||||||
|
uid: tftpfs
|
||||||
type: build
|
type: build
|
||||||
use-after: []
|
use-after: []
|
||||||
use-before:
|
use-before:
|
||||||
|
|||||||
25
spec/build/testsuites/fstests/tftpfs.yml
Normal file
25
spec/build/testsuites/fstests/tftpfs.yml
Normal 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: []
|
||||||
6980
testsuites/fstests/tftpfs/init.c
Normal file
6980
testsuites/fstests/tftpfs/init.c
Normal file
File diff suppressed because it is too large
Load Diff
984
testsuites/fstests/tftpfs/tftpfs_interactions.c
Normal file
984
testsuites/fstests/tftpfs/tftpfs_interactions.c
Normal 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;
|
||||||
|
}
|
||||||
213
testsuites/fstests/tftpfs/tftpfs_interactions.h
Normal file
213
testsuites/fstests/tftpfs/tftpfs_interactions.h
Normal 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 */
|
||||||
983
testsuites/fstests/tftpfs/tftpfs_udp_network_fake.c
Normal file
983
testsuites/fstests/tftpfs/tftpfs_udp_network_fake.c
Normal 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;
|
||||||
|
}
|
||||||
315
testsuites/fstests/tftpfs/tftpfs_udp_network_fake.h
Normal file
315
testsuites/fstests/tftpfs/tftpfs_udp_network_fake.h
Normal 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 */
|
||||||
Reference in New Issue
Block a user