summaryrefslogtreecommitdiffstats
path: root/testsuites/fstests/tftpfs/tftpfs_udp_network_fake.c
diff options
context:
space:
mode:
Diffstat (limited to 'testsuites/fstests/tftpfs/tftpfs_udp_network_fake.c')
-rw-r--r--testsuites/fstests/tftpfs/tftpfs_udp_network_fake.c983
1 files changed, 983 insertions, 0 deletions
diff --git a/testsuites/fstests/tftpfs/tftpfs_udp_network_fake.c b/testsuites/fstests/tftpfs/tftpfs_udp_network_fake.c
new file mode 100644
index 0000000000..995ae05fe0
--- /dev/null
+++ b/testsuites/fstests/tftpfs/tftpfs_udp_network_fake.c
@@ -0,0 +1,983 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+
+/**
+ * @file
+ *
+ * @ingroup RTEMSTestSuiteTestsTFTPFS
+ *
+ * @brief This source file contains the implementation of UDP network fake
+ * functions related to tftpfs testing.
+ *
+ * The libtftpfs source code is situated in the RTEMS repository. For
+ * testing it, either libbsd or RTEMS legacy networking would be required.
+ * This implies that the tests for libtftpfs would need to be placed in
+ * the libbsd repository - a different one than the libtftpfs source code.
+ *
+ * Yet, libtftpfs uses only a handful of networking functions. This
+ * file provides fake implementations of those functions. These fake
+ * functions permit to simulate the exchange of UDP packets
+ * with the libtftpfs code and thus permits testing libtftpfs without
+ * the need of a full network stack.
+ */
+
+/*
+ * Copyright (C) 2022 embedded brains GmbH & Co. KG
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h> /* snprintf() */
+#include <stdlib.h> /* malloc(), free() */
+#include <inttypes.h> /* printf() macros like PRId8 */
+#include <string.h>
+#include <ctype.h> /* isprint() */
+#include <netdb.h> /* getnameinfo() */
+#include <arpa/inet.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h> /* struct sockaddr_in, struct sockaddr_in6 */
+#include <rtems/test.h>
+
+#include "tftpfs_udp_network_fake.h"
+
+#define IPV4_ADDR_SIZE 4
+#define MAX_SOCKET_FD 4
+
+int __wrap_close( int fd ); /* Prevent compiler warnings */
+int __real_close( int fd ); /* Prevent compiler warnings */
+
+/*
+ * Control data
+ */
+
+/*
+ * A singleton global data object is used to control the network interaction
+ * with the TFTP client.
+ *
+ * A test can exchange UDP packets when the TFTP client calls functions
+ * sendto() and recvfrom(). Simply put, when the client calls sendto()
+ * the test must check that the client sends the expected UDP packet
+ * and when the client calls recvfrom() the test must provide the UDP
+ * packet it wants to send to the client.
+ *
+ * Defining the sequence of sendto() and recvfrom() function calls
+ * including parameters and return values essentially represents a
+ * test for the TFTP networking. To be a bit more complete, a few more
+ * functions such as socket() and bind() are included in the sequence.
+ *
+ * Each of these function calls defines a single *interaction* between
+ * TFTP client and test (aka TFTP server). The idea is that each test
+ * defines a sequence of interactions. In a successful test run all
+ * interactions must be carried out one-by-one till the last (normally
+ * "close()") interaction is reached.
+ *
+ * The control_data essentially stores the sequence of interactions
+ * as well as the current state (e.g. which is the next interaction?).
+ */
+typedef struct control_data {
+ Tftp_Interaction *interaction_head;
+ Tftp_Interaction *interaction_tail;
+ Tftp_Interaction *interaction_cur;
+ int receive_timeout_socket_fd[MAX_SOCKET_FD];
+ uint32_t receive_timeout_ms[MAX_SOCKET_FD];
+} control_data;
+
+static control_data *control = NULL;
+
+void _Tftp_Reset( void )
+{
+ static control_data ctl;
+ int i;
+ Tftp_Interaction *iter;
+ Tftp_Interaction *current;
+
+ if ( control == NULL ) {
+ control = &ctl;
+ } else {
+ for ( iter = control->interaction_head; iter != NULL; ) {
+ current = iter;
+ iter = iter->next;
+ free( current );
+ }
+ }
+
+ control->interaction_head = NULL;
+ control->interaction_tail = (Tftp_Interaction *) &control->interaction_head;
+ control->interaction_cur = (Tftp_Interaction *) &control->interaction_head;
+ for ( i = 0; i < MAX_SOCKET_FD; ++i ) {
+ control->receive_timeout_socket_fd[i] = 0;
+ control->receive_timeout_ms[i] = 0;
+ }
+}
+
+void *_Tftp_Append_interaction(
+ Tftp_Action_kind kind,
+ Tftp_Interaction_fn fn,
+ size_t size
+)
+{
+ Tftp_Interaction *ia;
+ T_quiet_not_null( control );
+ ia = malloc( sizeof( Tftp_Interaction ) + size );
+ T_quiet_not_null( ia );
+ ia->next = NULL;
+ ia->kind = kind;
+ ia->fn = fn;
+ control->interaction_tail->next = ia;
+ control->interaction_tail = ia;
+ return ia->data;
+}
+
+bool _Tftp_Has_no_more_interactions( void )
+{
+ return control == NULL || control->interaction_cur != NULL;
+}
+
+static Tftp_Interaction *get_next_interaction( control_data *control )
+{
+ if ( control == NULL ) {
+ return NULL;
+ }
+ if ( control->interaction_cur != NULL ) {
+ control->interaction_cur = control->interaction_cur->next;
+ }
+ return control->interaction_cur;
+}
+
+static const char *get_action_kind_as_string( Tftp_Action_kind kind )
+{
+ switch ( kind ) {
+ case TFTP_IA_KIND_SOCKET:
+ return "socket";
+ case TFTP_IA_KIND_CLOSE:
+ return "close";
+ case TFTP_IA_KIND_BIND:
+ return "bind";
+ case TFTP_IA_KIND_SENDTO:
+ return "sendto";
+ case TFTP_IA_KIND_RECVFROM:
+ return "recvfrom";
+ default:
+ break;
+ }
+ return "UNKNOWN";
+}
+
+/*
+ * Parse and log UDP TFTP packet functions
+ */
+
+const char *_Tftp_Get_error_str( uint16_t error_code )
+{
+ static const char *unknown_str = "Unknown error code";
+ static const char *error_str[] = {
+ "Not defined, see error message (if any)",
+ "File not found",
+ "Access violation",
+ "Disk full or allocation exceeded",
+ "Illegal TFTP operation",
+ "Unknown transfer ID",
+ "File already exists",
+ "No such user",
+ "Option negociation failed",
+ };
+ const char *result = unknown_str;
+
+ if ( error_code < RTEMS_ARRAY_SIZE( error_str ) ) {
+ result = error_str[ error_code ];
+ }
+
+ return result;
+}
+
+static void log_hex_dump( const void *buf, size_t len )
+{
+ const size_t per_line = 16;
+ size_t pos = 0;
+ const uint8_t *pkt = buf;
+ char hex[2 * per_line + 4];
+ char chars[per_line + 1];
+ char *hexpos;
+ int i;
+
+ chars[per_line] = '\0';
+ do {
+ for ( i = 0, hexpos = hex; i < per_line; ++i ) {
+ if ( pos + i < len ) {
+ hexpos += snprintf( hexpos, 3, "%02"PRIX8, pkt[ pos + i ] );
+ chars[i] = ( isprint( pkt[ pos + i ] ) ) ? pkt[ pos + i ] : '.';
+ } else {
+ hexpos += snprintf( hexpos, 3, " " );
+ chars[i] = '\0';
+ }
+ if ( i < per_line - 1 && i % 4 == 3 ) {
+ *(hexpos++) = ' ';
+ }
+ }
+
+ T_log( T_VERBOSE, " %04zX : %s |%s|", pos, hex, chars );
+ pos += per_line;
+ } while( len > pos );
+}
+
+static const char *get_next_str(
+ const char **buf,
+ size_t *max_len,
+ bool *has_errors
+)
+{
+ const char *result;
+ size_t len = strnlen( *buf, *max_len );
+ if ( len < *max_len ) {
+ result = *buf;
+ *buf += len + 1;
+ *max_len -= len + 1;
+ } else {
+ result = "MAL_FORMED_STRING";
+ *max_len = 0;
+ *has_errors = true;
+ }
+ return result;
+}
+
+static void log_tftp_packet( const void *buf, size_t len )
+{
+ int op_code;
+ const char *buffer = ( (char *) buf ) + sizeof( uint16_t );
+ size_t remaining_len = len - sizeof( uint16_t );
+ bool has_errors = false;
+
+ if ( len >= sizeof( uint16_t ) ) {
+ op_code = ntohs( *((uint16_t *) buf ) );
+ switch ( op_code ) {
+ case TFTP_OPCODE_RRQ:
+ case TFTP_OPCODE_WRQ:
+ T_log(
+ T_VERBOSE,
+ " %s File: \"%s\" Mode: \"%s\" %s",
+ ( op_code == TFTP_OPCODE_RRQ ) ? "RRQ" : "WRQ",
+ get_next_str( &buffer, &remaining_len, &has_errors ),
+ get_next_str( &buffer, &remaining_len, &has_errors ),
+ ( remaining_len > 0 ) ? "Options:" : "No options"
+ );
+ while ( remaining_len > 0 ) {
+ T_log(
+ T_VERBOSE,
+ " %s = \"%s\"",
+ get_next_str( &buffer, &remaining_len, &has_errors ),
+ get_next_str( &buffer, &remaining_len, &has_errors )
+ );
+ }
+ break;
+
+ case TFTP_OPCODE_DATA:
+ if ( len >= 2 * sizeof( uint16_t ) ) {
+ buffer += sizeof( uint16_t );
+ remaining_len -= sizeof( uint16_t );
+ T_log(
+ T_VERBOSE,
+ " DATA Block: %"PRIu16" Data (%zu bytes):",
+ ntohs( *( ( (uint16_t *) buf ) + 1 ) ),
+ remaining_len
+ );
+ log_hex_dump( buffer, remaining_len );
+ } else {
+ T_log( T_VERBOSE, " DATA packet ILLEGAL length" );
+ has_errors = true;
+ }
+ break;
+
+ case TFTP_OPCODE_ACK:
+ if ( len == 2 * sizeof( uint16_t ) ) {
+ T_log(
+ T_VERBOSE,
+ " ACK Block: %"PRIu16,
+ ntohs( *( ( (uint16_t *) buf ) + 1 ) )
+ );
+ } else {
+ T_log( T_VERBOSE, " ACK packet ILLEGAL length" );
+ has_errors = true;
+ }
+ break;
+
+ case TFTP_OPCODE_ERROR:
+ if ( len > 2 * sizeof( uint16_t ) ) {
+ uint16_t err_code = ntohs( *( ( (uint16_t *) buf ) + 1 ) );
+ T_log(
+ T_VERBOSE,
+ " ERROR Code: %"PRIu16" (%s) ErrMsg:",
+ err_code,
+ _Tftp_Get_error_str( err_code )
+ );
+ buffer += sizeof( uint16_t );
+ remaining_len -= sizeof( uint16_t );
+ T_log(
+ T_VERBOSE,
+ " \"%s\"",
+ get_next_str( &buffer, &remaining_len, &has_errors )
+ );
+ } else {
+ T_log( T_VERBOSE, " ERROR packet ILLEGAL length" );
+ has_errors = true;
+ }
+ break;
+
+ case TFTP_OPCODE_OACK:
+ T_log(
+ T_VERBOSE,
+ " OACK %s",
+ ( remaining_len > 0 ) ? "Options:" : "No options"
+ );
+ while ( remaining_len > 0 ) {
+ T_log(
+ T_VERBOSE,
+ " %s = \"%s\"",
+ get_next_str( &buffer, &remaining_len, &has_errors ),
+ get_next_str( &buffer, &remaining_len, &has_errors )
+ );
+ }
+ break;
+
+ default:
+ T_log( T_VERBOSE, " TFTP ILLEGAL OpCode: %d", op_code );
+ has_errors = true;
+ }
+ } else {
+ has_errors = true;
+ }
+
+ if ( has_errors ) {
+ log_hex_dump( buf, len );
+ }
+}
+
+static void log_interaction(
+ Tftp_Action *act,
+ bool has_result,
+ bool was_success
+)
+{
+ char *begin = has_result ? "[" : "";
+ const char *action_name;
+ int result;
+ char result_buffer[20];
+ result_buffer[0] = '\0';
+
+ if ( act == NULL ) { return; }
+ action_name = get_action_kind_as_string( act->kind );
+
+ if ( has_result && was_success ) {
+ switch ( act->kind ) {
+ case TFTP_IA_KIND_SOCKET:
+ result = act->data.socket.result;
+ break;
+
+ case TFTP_IA_KIND_CLOSE:
+ result = act->data.close.result;
+ break;
+
+ case TFTP_IA_KIND_BIND:
+ result = act->data.bind.result;
+ break;
+
+ case TFTP_IA_KIND_SENDTO:
+ result = (int) act->data.sendto.result;
+ break;
+
+ case TFTP_IA_KIND_RECVFROM:
+ result = (int) act->data.recvfrom.result;
+ break;
+
+ default:
+ result = 0;
+ }
+ snprintf( result_buffer, sizeof( result_buffer ), "] = %d", result );
+ } else if ( ! was_success ) {
+ snprintf( result_buffer, sizeof( result_buffer ), "] = FAILED!" );
+ }
+
+ switch ( act->kind ) {
+ case TFTP_IA_KIND_SOCKET:
+ T_log(
+ T_VERBOSE,
+ "%s%s(domain=%d, type=%d, protocol=%d)%s",
+ begin,
+ action_name,
+ act->data.socket.domain,
+ act->data.socket.type,
+ act->data.socket.protocol,
+ result_buffer
+ );
+ break;
+
+ case TFTP_IA_KIND_CLOSE:
+ T_log( T_VERBOSE, "%s%s(%d)%s",
+ begin,
+ action_name,
+ act->data.close.fd,
+ result_buffer
+ );
+ break;
+
+ case TFTP_IA_KIND_BIND:
+ T_log(
+ T_VERBOSE,
+ "%s%s(sockfd=%d, addr.family=%d, addr=%s:%"PRIu16")%s",
+ begin,
+ action_name,
+ act->data.bind.fd,
+ act->data.bind.family,
+ act->data.bind.addr_str,
+ act->data.bind.port,
+ result_buffer
+ );
+ break;
+
+ case TFTP_IA_KIND_SENDTO:
+ T_log(
+ T_VERBOSE,
+ "%s%s(sockfd=%d, buf=%p, len=%zu, flags=%X, "
+ "addr=%s:%"PRIu16", addrlen=%d)%s",
+ begin,
+ action_name,
+ act->data.sendto.fd,
+ act->data.sendto.buf,
+ act->data.sendto.len,
+ act->data.sendto.flags,
+ act->data.sendto.dest_addr_str,
+ act->data.sendto.dest_port,
+ act->data.sendto.addrlen,
+ result_buffer
+ );
+ if ( !has_result ) {
+ log_tftp_packet( act->data.sendto.buf, act->data.sendto.len );
+ }
+ break;
+
+ case TFTP_IA_KIND_RECVFROM:
+ if ( !has_result ) {
+ T_log(
+ T_VERBOSE,
+ "%s%s(sockfd=%d, buf=%p, len=%zu, flags=%X, "
+ "timeout=%"PRIu32"ms, addr=?:?, addrlen=%d)%s",
+ begin,
+ action_name,
+ act->data.recvfrom.fd,
+ act->data.recvfrom.buf,
+ act->data.recvfrom.len,
+ act->data.recvfrom.flags,
+ act->data.recvfrom.timeout_ms,
+ act->data.recvfrom.addrlen,
+ result_buffer
+ );
+ } else {
+ T_log(
+ T_VERBOSE,
+ "%s%s(sockfd=%d, buf=%p, len=%zu, flags=%X, "
+ "timeout=%"PRIu32"ms, addr=%s:%"PRIu16", addrlen=%d)%s",
+ begin,
+ action_name,
+ act->data.recvfrom.fd,
+ act->data.recvfrom.buf,
+ act->data.recvfrom.len,
+ act->data.recvfrom.flags,
+ act->data.recvfrom.timeout_ms,
+ act->data.recvfrom.src_addr_str,
+ act->data.recvfrom.src_port,
+ act->data.recvfrom.addrlen,
+ result_buffer
+ );
+ if ( act->data.recvfrom.result > 0 && was_success ) {
+ log_tftp_packet( act->data.recvfrom.buf, act->data.recvfrom.result );
+ }
+ }
+ break;
+
+ default:
+ T_quiet_true( false, "Unknown interaction: %d", act->kind );
+ }
+}
+
+/*
+ * Note: This function *must* return.
+ * All the fake network functions (and any functions called from them)
+ * must return control to the TFTP client. Hence, do not use T_assert_*()
+ * or similar functions. Even if the test fails at some point,
+ * continue and return an error value to the client.
+ * Reason: T_assert_*() does stop the test and invokes teardown()
+ * without returning to the client. teardown() then "hangs" when
+ * un-mounting while executing client code.
+ */
+static bool process_interaction( Tftp_Action *act )
+{
+ Tftp_Interaction *ia = get_next_interaction( control );
+ bool result;
+
+ T_quiet_not_null( act );
+ if ( act == NULL ) {
+ return false;
+ } else {
+ /* Logging this early helps debugging defect tests */
+ log_interaction( act, false, true );
+ }
+
+ T_quiet_not_null( ia );
+ if( ia == NULL ) {
+ T_log(
+ T_NORMAL,
+ "ERROR: You have not registered any (more) 'interaction' but the TFTP "
+ "client invoked interaction '%s()'. See 'tftpfs_interactions.h' and use "
+ "'T_set_verbosity( T_VERBOSE )'.",
+ get_action_kind_as_string( act->kind )
+ );
+ return false;
+ }
+
+ T_true(
+ act->kind == ia->kind,
+ "Expected %s() call but got %s()",
+ get_action_kind_as_string( ia->kind ),
+ get_action_kind_as_string( act->kind )
+ );
+ if ( act->kind != ia->kind ) {
+ return false;
+ }
+ result = ia->fn( act, ia->data );
+ log_interaction( act, true, result );
+
+ return result;
+}
+
+static uint16_t get_ip_addr_as_str(
+ const struct sockaddr *addr,
+ socklen_t addrlen,
+ char *buf,
+ size_t buf_size
+)
+{
+ uint16_t port = 0xFFFF;
+ *buf = '\0';
+
+ switch ( addr->sa_family ) {
+ case AF_INET:
+ {
+ const struct sockaddr_in *ipv4 = (const struct sockaddr_in *) addr;
+ port = ntohs( ipv4->sin_port );
+ const uint8_t *bytes = (const uint8_t *) &ipv4->sin_addr.s_addr;
+ snprintf(
+ buf,
+ buf_size,
+ "%"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8,
+ bytes[0],
+ bytes[1],
+ bytes[2],
+ bytes[3]
+ );
+ break;
+ }
+ case AF_INET6:
+ {
+ const struct sockaddr_in6 *ipv6 = (const struct sockaddr_in6 *) addr;
+ port = ntohs( ipv6->sin6_port );
+ const uint16_t *words = (const uint16_t *) ipv6->sin6_addr.s6_addr;
+ snprintf(
+ buf,
+ buf_size,
+ "%"PRIx16":%"PRIx16":%"PRIx16":%"PRIx16":%"PRIx16":%"PRIx16
+ ":%"PRIx16":%"PRIx16,
+ ntohs( words[0] ),
+ ntohs( words[1] ),
+ ntohs( words[2] ),
+ ntohs( words[3] ),
+ ntohs( words[4] ),
+ ntohs( words[5] ),
+ ntohs( words[6] ),
+ ntohs( words[7] )
+ );
+ break;
+ }
+ }
+
+ return port;
+}
+
+static void set_ip_addr_from_str(
+ const char *addr_str,
+ uint16_t port,
+ struct sockaddr *addr,
+ socklen_t *addrlen
+ )
+{
+ socklen_t addr_size;
+ int res;
+ int i;
+
+ if ( addr == NULL || addrlen == NULL ) {
+ return;
+ }
+ addr_size = *addrlen;
+
+ if ( strchr( addr_str, ':' ) == NULL ) {
+ /* IPv4 address */
+ struct sockaddr_in *ipv4_addr = (struct sockaddr_in *) addr;
+ uint8_t *bytes = (uint8_t *) &ipv4_addr->sin_addr.s_addr;
+
+ *addrlen = sizeof( struct sockaddr_in );
+ T_ge_sz( addr_size, *addrlen );
+ if ( addr_size < *addrlen ) { return; }
+ ipv4_addr->sin_family = AF_INET;
+ ipv4_addr->sin_port = htons( port );
+ res = sscanf(
+ addr_str,
+ "%"SCNu8".%"SCNu8".%"SCNu8".%"SCNu8,
+ bytes,
+ bytes + 1,
+ bytes + 2,
+ bytes + 3
+ );
+ T_quiet_true( res == 4, "Connot parse IPv4 address: \"%s\"", addr_str );
+ } else {
+ /* IPv6 address */
+ struct sockaddr_in6 *ipv6_addr = (struct sockaddr_in6 *) addr;
+ uint16_t *words = (uint16_t *) &ipv6_addr->sin6_addr.s6_addr;
+
+ *addrlen = sizeof( struct sockaddr_in6 );
+ T_gt_sz( addr_size, *addrlen );
+ if ( addr_size < *addrlen ) { return; }
+ ipv6_addr->sin6_family = AF_INET6;
+ ipv6_addr->sin6_port = htons( port );
+ ipv6_addr->sin6_flowinfo = 0;
+ ipv6_addr->sin6_scope_id = 0;
+ res = sscanf(
+ addr_str,
+ "%"SCNx16":%"SCNx16":%"SCNx16":%"SCNx16":%"SCNx16":%"SCNx16
+ ":%"SCNx16":%"SCNx16,
+ words,
+ words + 1,
+ words + 2,
+ words + 3,
+ words + 4,
+ words + 5,
+ words + 6,
+ words + 7
+ );
+ T_quiet_true( res == 8, "Connot parse IPv6 address: \"%s\"", addr_str );
+ for ( i = 0; i < 8; ++i ) {
+ words[i] = htons( words[i] );
+ }
+ }
+}
+
+/*
+ * Fake networking functions
+ */
+
+int inet_aton( const char *cp, struct in_addr *inp )
+{
+ int result = 0;
+ uint8_t *ipv4_addr = (uint8_t *) inp;
+ static const char addr0[] = TFTP_KNOWN_IPV4_ADDR0_STR;
+ static const uint8_t addr0_data[] = { TFTP_KNOWN_IPV4_ADDR0_ARRAY };
+
+ if ( strcmp( addr0, cp ) == 0 ) {
+ memcpy( ipv4_addr, addr0_data, sizeof( addr0_data ) );
+ result = 1;
+ }
+
+ T_log(
+ T_VERBOSE,
+ "inet_aton(cp=%s, addr=%"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8") = %d",
+ cp,
+ ipv4_addr[0],
+ ipv4_addr[1],
+ ipv4_addr[2],
+ ipv4_addr[3],
+ result
+ );
+ return result;
+}
+
+struct hostent *gethostbyname( const char *name )
+{
+ static char ip_addr_bytes[] = {
+ TFTP_KNOWN_SERVER0_ARRAY, /* IPv4: 10.7.0.2 "server.tftp" */
+ TFTP_KNOWN_IPV4_ADDR0_ARRAY /* IPv4: 127.0.0.1 "127.0.0.1" */
+ };
+ static char *ip_addrs[] = {
+ ip_addr_bytes + 0 * IPV4_ADDR_SIZE, NULL,
+ ip_addr_bytes + 1 * IPV4_ADDR_SIZE, NULL
+ };
+ static struct hostent hosts[] = {
+ {
+ .h_name = TFTP_KNOWN_SERVER0_NAME,
+ .h_aliases = NULL,
+ .h_addrtype = AF_INET,
+ .h_length = IPV4_ADDR_SIZE,
+ .h_addr_list = ip_addrs + 0,
+ },
+ {
+ .h_name = TFTP_KNOWN_IPV4_ADDR0_STR,
+ .h_aliases = NULL,
+ .h_addrtype = AF_INET,
+ .h_length = IPV4_ADDR_SIZE,
+ .h_addr_list = ip_addrs + 2,
+ }
+ };
+ struct hostent *result = NULL;
+ uint8_t *ipv4_addr;
+ int i;
+
+ for ( i = 0; i < RTEMS_ARRAY_SIZE( hosts ); ++i ) {
+ if ( strcmp( hosts[i].h_name, name ) == 0 ) {
+ result = hosts + i;
+ }
+ }
+ /* Note: `h_errno` not set due to linker issues */
+
+ if ( result != NULL ) {
+ ipv4_addr = (uint8_t *) result->h_addr_list[0];
+ T_log(
+ T_VERBOSE,
+ "gethostbyname(%s) = %s, %"PRIu8".%"PRIu8".%"PRIu8".%"PRIu8,
+ name,
+ result->h_name,
+ ipv4_addr[0],
+ ipv4_addr[1],
+ ipv4_addr[2],
+ ipv4_addr[3]
+ );
+ } else {
+ T_log( T_NORMAL, "gethostbyname(%s) = %p", name, result );
+ }
+ return result;
+}
+
+int socket( int domain, int type, int protocol )
+{
+ Tftp_Action act = {
+ .kind = TFTP_IA_KIND_SOCKET,
+ .data.socket.domain = domain,
+ .data.socket.type = type,
+ .data.socket.protocol = protocol
+ };
+
+ if( !process_interaction( &act ) ) {
+ return -1;
+ };
+
+ /* Should never happen but check prevents programming mistakes */
+ T_quiet_ge_int( act.data.socket.result, TFTP_FIRST_FD );
+ if( act.data.socket.result < TFTP_FIRST_FD ) {
+ return -1;
+ };
+
+ return act.data.socket.result;
+}
+
+int __wrap_close( int fd )
+{
+ if ( fd < TFTP_FIRST_FD ) {
+ /* Normal file descriptors - i.e. not from fake socket() function above */
+ return __real_close( fd );
+ }
+ Tftp_Action act = {
+ .kind = TFTP_IA_KIND_CLOSE,
+ .data.close.fd = fd,
+ };
+
+ if( !process_interaction( &act ) ) {
+ return -1;
+ };
+
+ return act.data.close.result;
+}
+
+int bind( int sockfd, const struct sockaddr *addr, socklen_t addrlen )
+{
+ char addr_buf[INET6_ADDRSTRLEN];
+ Tftp_Action act = {
+ .kind = TFTP_IA_KIND_BIND,
+ .data.bind.fd = sockfd,
+ .data.bind.family = addr->sa_family,
+ .data.bind.addr_str = addr_buf
+ };
+ act.data.bind.port = get_ip_addr_as_str(
+ addr,
+ addrlen,
+ addr_buf,
+ sizeof( addr_buf )
+ );
+
+ if( !process_interaction( &act ) ) {
+ return -1;
+ };
+
+ return act.data.bind.result;
+}
+
+int setsockopt(
+ int sockfd,
+ int level,
+ int optname,
+ const void *optval,
+ socklen_t optlen
+)
+{
+ int result = 0;
+ int i;
+ const struct timeval *tv = optval;
+
+ T_log(
+ T_VERBOSE,
+ "setsockopt(sockfd=%d, level=%s, optname=%s, optval=%dms )",
+ sockfd,
+ ( level == SOL_SOCKET ) ? "SOL_SOCKET" : "UNKONWN",
+ ( optname == SO_RCVTIMEO ) ? "SO_RCVTIMEO" : "UNKONWN",
+ ( optname == SO_RCVTIMEO ) ?
+ (int) ( tv->tv_sec * 1000 + tv->tv_usec / 1000 ) : -1
+ );
+
+ T_eq_int( level, SOL_SOCKET );
+ T_eq_int( optname, SO_RCVTIMEO );
+ T_eq_int( (int) optlen, sizeof( struct timeval ) );
+ if (
+ level != SOL_SOCKET ||
+ optname != SO_RCVTIMEO ||
+ optlen != sizeof( struct timeval )
+ ) {
+ result = -1;
+ }
+
+ for ( i = 0; result >= 0; ++i ) {
+ if ( i >= MAX_SOCKET_FD ) {
+ T_quiet_true(
+ false,
+ "Too few IP ports %d, increase MAX_SOCKET_FD",
+ MAX_SOCKET_FD
+ );
+ result = -1;
+ break;
+ }
+ if ( control->receive_timeout_socket_fd[i] == sockfd ||
+ control->receive_timeout_socket_fd[i] == 0 ) {
+ control->receive_timeout_socket_fd[i] = sockfd;
+ control->receive_timeout_ms[i] = tv->tv_sec * 1000 + tv->tv_usec / 1000;
+ break;
+ }
+ }
+
+ T_log(
+ T_VERBOSE,
+ "[setsockopt(sockfd=%d, level=%s, optname=%s, optval=%"PRIu32"ms )] = %d",
+ sockfd,
+ ( level == SOL_SOCKET ) ? "SOL_SOCKET" : "UNKONWN",
+ ( optname == SO_RCVTIMEO ) ? "SO_RCVTIMEO" : "UNKONWN",
+ ( i < MAX_SOCKET_FD ) ? control->receive_timeout_ms[i] : -1,
+ result
+ );
+
+ return result;
+}
+
+ssize_t sendto(
+ int sockfd,
+ const void *buf,
+ size_t len,
+ int flags,
+ const struct sockaddr *dest_addr,
+ socklen_t addrlen
+)
+{
+ char addr_buf[INET6_ADDRSTRLEN];
+ Tftp_Action act = {
+ .kind = TFTP_IA_KIND_SENDTO,
+ .data.sendto.fd = sockfd,
+ .data.sendto.buf = buf,
+ .data.sendto.len = len,
+ .data.sendto.flags = flags,
+ .data.sendto.dest_addr_str = addr_buf,
+ .data.sendto.addrlen = (int) addrlen,
+ };
+ act.data.sendto.dest_port = get_ip_addr_as_str(
+ dest_addr,
+ addrlen,
+ addr_buf,
+ sizeof( addr_buf )
+ );
+
+ if( !process_interaction( &act ) ) {
+ return -1;
+ };
+
+ return act.data.sendto.result;
+}
+
+ssize_t recvfrom(
+ int sockfd,
+ void *buf,
+ size_t len,
+ int flags,
+ struct sockaddr *src_addr,
+ socklen_t *addrlen
+)
+{
+ int i;
+ Tftp_Packet *pkt = buf;
+ Tftp_Action act = {
+ .kind = TFTP_IA_KIND_RECVFROM,
+ .data.recvfrom.fd = sockfd,
+ .data.recvfrom.buf = buf,
+ .data.recvfrom.len = len,
+ .data.recvfrom.flags = flags,
+ .data.recvfrom.timeout_ms = 0,
+ .data.recvfrom.addrlen = (int) *addrlen
+ };
+
+ for ( i = 0; i < MAX_SOCKET_FD; ++i ) {
+ if ( control->receive_timeout_socket_fd[i] == sockfd ) {
+ act.data.recvfrom.timeout_ms = control->receive_timeout_ms[i];
+ break;
+ }
+ }
+
+ /* Make log_tftp_packet() behave sane, if buf is not filled */
+ if ( len >= sizeof( pkt->opcode ) ) {
+ pkt->opcode = ntohs( 0xFFFF );
+ }
+
+ if( !process_interaction( &act ) ) {
+ return -1;
+ };
+
+ set_ip_addr_from_str(
+ act.data.recvfrom.src_addr_str,
+ act.data.recvfrom.src_port,
+ src_addr,
+ addrlen
+ );
+
+ return act.data.recvfrom.result;
+}