diff --git a/.github/workflows/make_test.yml b/.github/workflows/make_test.yml index f447aecf79..749c218756 100644 --- a/.github/workflows/make_test.yml +++ b/.github/workflows/make_test.yml @@ -69,3 +69,21 @@ jobs: - name: Run unit tests run: make -k -j2 --output-sync=target run-unit-test + + frank-single-transaction: + runs-on: [self-hosted, Linux, X64] + strategy: + matrix: + compiler: [gcc] + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: dtolnay/rust-toolchain@1.69.0 + + - name: Run + run: ./src/test/frank-single-transaction.sh diff --git a/src/ballet/base64/Local.mk b/src/ballet/base64/Local.mk new file mode 100644 index 0000000000..b01c8db269 --- /dev/null +++ b/src/ballet/base64/Local.mk @@ -0,0 +1,3 @@ +$(call add-hdrs,fd_base64.h) +$(call add-objs,fd_base64,fd_ballet) +$(call make-unit-test,test_base64,test_base64,fd_ballet fd_util) diff --git a/src/ballet/base64/fd_base64.c b/src/ballet/base64/fd_base64.c new file mode 100644 index 0000000000..ce68816ea6 --- /dev/null +++ b/src/ballet/base64/fd_base64.c @@ -0,0 +1,84 @@ +#include "fd_base64.h" + +/* Function to get the index of a character in the Base64 alphabet */ +static inline int +base64_decode_char( char c ) { + if( c >= 'A' && c <= 'Z' ) return c - 'A'; + if( c >= 'a' && c <= 'z' ) return c - 'a' + 26; + if( c >= '0' && c <= '9' ) return c - '0' + 52; + if( c == '+' ) return 62; + if( c == '/' ) return 63; + return -1; // Invalid character +} + +/* Function to decode a base64 encoded string into an unsigned char array + The function returns the length of the decoded array */ +int +fd_base64_decode( const char * encoded, + uchar * decoded ) { + int len = 0; + int bits_collected = 0; + uint accumulator = 0; + + while ( *encoded ) { + char c = *encoded++; + int value = base64_decode_char(c); + + if( value >= 0 ) { + accumulator = ( accumulator << 6 ) | ( uint ) value; + bits_collected += 6; + + if( bits_collected >= 8 ) { + bits_collected -= 8; + decoded[ len++ ] = ( uchar )( accumulator >> bits_collected ); + accumulator &= ( 1U << bits_collected ) - 1; + } + } else if( c == '=' ) { + /* Padding character, ignore and break the loop */ + break; + } else { + /* Fail with invalid characters (e.g., whitespace, padding) */ + return -1; + } + } + + return len; +} + +ulong +fd_base64_encode( const uchar * data, + int data_len, + char * encoded ) { + static const char base64_alphabet[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + uint encoded_len = 0; + uint accumulator = 0; + int bits_collected = 0; + + while( data_len-- ) { + accumulator = ( accumulator << 8 ) | *data++; + bits_collected += 8; + + while( bits_collected >= 6 ) { + encoded[ encoded_len++ ] = base64_alphabet[ ( accumulator >> ( bits_collected - 6) ) & 0x3F ]; + bits_collected -= 6; + } + } + + if( bits_collected > 0 ) { + // If there are remaining bits, pad the last Base64 character with zeroes + accumulator <<= 6 - bits_collected; + encoded[ encoded_len++ ] = base64_alphabet[accumulator & 0x3F ]; + } + + // Add padding characters if necessary + while( encoded_len % 4 != 0 ) { + encoded[ encoded_len++ ] = '='; + } + + // Null-terminate the encoded string + encoded[ encoded_len ] = '\0'; + + return encoded_len; +} diff --git a/src/ballet/base64/fd_base64.h b/src/ballet/base64/fd_base64.h new file mode 100644 index 0000000000..7b14809ee1 --- /dev/null +++ b/src/ballet/base64/fd_base64.h @@ -0,0 +1,15 @@ +#ifndef HEADER_fd_src_ballet_base64_fd_base64_h +#define HEADER_fd_src_ballet_base64_fd_base64_h + +/* fd_base64.h provides methods for converting between binary and base64. */ +#include "../fd_ballet_base.h" +int +fd_base64_decode( const char * encoded, + uchar * decoded ); + +ulong +fd_base64_encode( const uchar * data, + int data_len, + char * encoded ); + +#endif /* HEADER_fd_src_ballet_base64_fd_base64_h */ diff --git a/src/ballet/base64/test_base64.c b/src/ballet/base64/test_base64.c new file mode 100644 index 0000000000..abbbfedfe6 --- /dev/null +++ b/src/ballet/base64/test_base64.c @@ -0,0 +1,49 @@ +#include "fd_base64.h" +#include +#include + +void +decode_test( void ) { + const char * expected_string = "Hello World!"; + const char * encoded_string = "SGVsbG8gV29ybGQh"; + uchar decoded[ 100 ]; /* Assuming the decoded data won't exceed 100 bytes */ + + int decoded_length = fd_base64_decode( encoded_string, decoded ); + + FD_TEST( (uint)decoded_length == strlen( expected_string ) ); + FD_TEST( memcmp( decoded, expected_string, strlen(expected_string) ) == 0 ); +} + +void +decode_test_equals( void ) { + const char * encoded_string = "AZCML352XGjOwgIwMGsRf8oa2IoWzSvgWlJwcAEtLtwk3/h2VIe7n+YbPrAwpbIiK3KOM/G4XiNAKyhHbn2VBQ0BAAEGUn3G2+sjJ+xarkiI77ZYW6CEGHzEjzovKWoUG3/TSKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACdPJdmqIA5PfVdI4dCMAMKH7z7U0fpkodPhLfE54yrfhMa9sJylZdraDb38lv6aISwi7GkOXsRZ8PQnKkkbFarB08LBYexRVX/v3EVfNeQk9z+WMTKqR0tc/WtXBjQP+v5pDHP1tYUMPTA8WZARvn8XTCjSs+9iPlPPYBWQrEspMZwcluFwA/afVpAczCo7+IJMw5/a0W/kR2EsJRNuF3IBBQUCAwAEAYgEJgW58JTT/VUQEAERERERARABAREBEBERERERABEBEQAQABARAAAREQEREBAQEBERERABERABEBAREBEQEQARAAERABARAQEQABABABAQAQABABEBAREQAAARAAAREREREAERARAAAREQEBAAEAAAEAAQEBEAEREAAAEQEBERAQAAAQEAABABABAQAAEBAAAQEBEAERAQEAAQAREBEQAREREAARAAAREAEAAAAREBEQEQEAAAEQEBEREREBABAQEQAQEQAAEQAREAERAAEAEQARABEBAAAQAQEAABEQEBEAAAEAABEBAAEAEAAQAQEBEQAQABABAREREBAQEAABEBARAAEAABEBAQEREAEBAREQEAAAEBEAEREREQARERAQEQAAEBEQEQARABEBAQAAAAEBEBAQEREQEQEQEAEQAQEBERARAQAQEBABAAAAEAERABAAAAAAEBEAEAAREREBEQEQARAREBEBEREAABABEAEBEBERAQABAAEQEAEAAQABEBEAAAABABAAABEAAAEQAQAQEBAQARAAEREAAAAQAQAREAAQAQAAEQEAEQEAAAARABEAEREBEBAQAQAQEREQEAAAEBAAAQEQABEAABEAEBEBAQABEREAAAABEBAAAQEQEQAAARERAQABERERABEAARABABEBAQAQEAEAARARERABABAREQEBAAAA=="; + uchar decoded[ 1500 ]; /* Assuming the decoded data won't exceed 1500 bytes */ + + int decoded_length = fd_base64_decode( encoded_string, decoded ); + + FD_TEST( decoded_length != -1 ); +} + +void encode_test( void ) { + uchar binary_data[] = {72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33}; /* "Hello World!" */ + const char * expected_string = "SGVsbG8gV29ybGQh"; + + char encoded[100]; /* Assuming the encoded data won't exceed 100 characters */ + + ulong encoded_length = fd_base64_encode(binary_data, sizeof(binary_data), encoded); + printf("%lu %ld\n", sizeof(binary_data), strlen((char*)binary_data)); + + FD_TEST( (uint)encoded_length == strlen( expected_string ) ); + FD_TEST( memcmp( encoded, expected_string, strlen(encoded) ) == 0 ); +} + +int +main( int argc, + char ** argv ) { + fd_boot( &argc, &argv ); + decode_test(); + decode_test_equals(); + encode_test(); + fd_halt(); + return 0; +} diff --git a/src/tango/quic/tests/Local.mk b/src/tango/quic/tests/Local.mk index 5cf968ccfa..263f9cc7fa 100644 --- a/src/tango/quic/tests/Local.mk +++ b/src/tango/quic/tests/Local.mk @@ -7,6 +7,7 @@ $(call make-unit-test,test_quic_streams,test_quic_streams,fd_aio fd_ballet fd_ta $(call make-unit-test,test_quic_conn,test_quic_conn,fd_aio fd_quic fd_ballet fd_tango fd_util) $(call make-unit-test,test_quic_server,test_quic_server,fd_aio fd_ballet fd_quic fd_tango fd_util) $(call make-unit-test,test_quic_client_flood,test_quic_client_flood,fd_aio fd_quic fd_ballet fd_tango fd_util) +$(call make-unit-test,test_quic_txn,test_quic_txn,fd_aio fd_quic fd_ballet fd_tango fd_util) $(call make-unit-test,test_quic_bw,test_quic_bw,fd_aio fd_quic fd_ballet fd_tango fd_util) $(call make-unit-test,test_quic_handshake,test_handshake,fd_aio fd_ballet fd_quic fd_util) $(call make-unit-test,test_quic_crypto,test_crypto,fd_quic fd_ballet fd_util) diff --git a/src/tango/quic/tests/fd_quic_test_helpers.c b/src/tango/quic/tests/fd_quic_test_helpers.c index c6be7b4c2c..a213b3b599 100644 --- a/src/tango/quic/tests/fd_quic_test_helpers.c +++ b/src/tango/quic/tests/fd_quic_test_helpers.c @@ -226,6 +226,77 @@ fd_quic_test_keylog( fd_quic_virtual_pair_t const * pair, fd_pcapng_fwrite_tls_key_log( (uchar const *)line, (uint)strlen( line ), pair->quic_a2b.pcapng ); } +fd_quic_udpsock_t * +fd_quic_client_create_udpsock(fd_quic_udpsock_t * udpsock, + fd_wksp_t * wksp, + fd_aio_t const * rx_aio, + uint listen_ip) { + ulong mtu = 2048UL; + ulong rx_depth = 1024UL; + ulong tx_depth = 1024UL; + + int sock_fd = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); + if( FD_UNLIKELY( sock_fd<0 ) ) { + FD_LOG_WARNING(( "socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP) failed (%d-%s)", + errno, strerror( errno ) )); + return NULL; + } + + struct sockaddr_in listen_addr = { + .sin_family = AF_INET, + .sin_addr = { .s_addr = listen_ip }, + .sin_port = 0, + }; + if( FD_UNLIKELY( 0!=bind( sock_fd, (struct sockaddr const *)fd_type_pun_const( &listen_addr ), sizeof(struct sockaddr_in) ) ) ) { + FD_LOG_WARNING(( "bind(sock_fd) failed (%d-%s)", + errno, strerror( errno ) )); + close( sock_fd ); + return NULL; + } + + void * sock_mem = fd_wksp_alloc_laddr( wksp, fd_udpsock_align(), + fd_udpsock_footprint( mtu, rx_depth, tx_depth ), + 1UL ); + if( FD_UNLIKELY( !sock_mem ) ) { + FD_LOG_WARNING(( "fd_wksp_alloc_laddr() failed" )); + close( sock_fd ); + return NULL; + } + + fd_udpsock_t * sock = fd_udpsock_join( fd_udpsock_new( sock_mem, mtu, rx_depth, tx_depth ), sock_fd ); + if( FD_UNLIKELY( !sock ) ) { + FD_LOG_WARNING(( "fd_udpsock_join() failed" )); + close( sock_fd ); + fd_wksp_free_laddr( sock_mem ); + return NULL; + } + + udpsock->type = FD_QUIC_UDPSOCK_TYPE_UDPSOCK; + udpsock->wksp = wksp; + udpsock->udpsock.sock = sock; + udpsock->udpsock.sock_fd = sock_fd; + udpsock->aio = fd_udpsock_get_tx( sock ); + udpsock->listen_ip = fd_udpsock_get_ip4_address( sock ); + udpsock->listen_port = (ushort)fd_udpsock_get_listen_port( sock ); + fd_udpsock_set_rx( sock, rx_aio ); + + FD_LOG_NOTICE(( "UDP socket listening on " FD_IP4_ADDR_FMT ":%u", + FD_IP4_ADDR_FMT_ARGS( udpsock->listen_ip ), udpsock->listen_port )); + return udpsock; +} + +// TODO: LML complete this thought? +fd_quic_udpsock_t * +create_udp_socket(fd_quic_udpsock_t * udpsock) { + if( FD_UNLIKELY( !fd_cstr_to_ip4_addr("0.0.0.0", &udpsock->listen_ip ) ) ) { + goto error_1; + } + udpsock->listen_port = 0; // TODO: check this where is it set in flood? + error_1: + FD_LOG_NOTICE(( "invalid --listen-ip" )); + return NULL; +} + fd_quic_udpsock_t * fd_quic_udpsock_create( void * _sock, int * pargc, @@ -255,6 +326,7 @@ fd_quic_udpsock_create( void * _sock, quic_sock->listen_port = listen_port; int is_xsk = (!!xdp_app_name); + FD_LOG_NOTICE(( "is_xsk %d", is_xsk )); if( is_xsk ) { FD_TEST( _src_mac ); if( FD_UNLIKELY( !fd_cstr_to_mac_addr( _src_mac, quic_sock->self_mac ) ) ) FD_LOG_ERR(( "invalid --src-mac" )); diff --git a/src/tango/quic/tests/fd_quic_test_helpers.h b/src/tango/quic/tests/fd_quic_test_helpers.h index 8915cd6615..6c642f1ff2 100644 --- a/src/tango/quic/tests/fd_quic_test_helpers.h +++ b/src/tango/quic/tests/fd_quic_test_helpers.h @@ -107,6 +107,12 @@ typedef struct fd_quic_udpsock fd_quic_udpsock_t; FD_PROTOTYPES_BEGIN +fd_quic_udpsock_t * +fd_quic_client_create_udpsock(fd_quic_udpsock_t * udpsock, + fd_wksp_t * wksp, + fd_aio_t const * rx_aio, + uint listen_ip); + fd_quic_udpsock_t * fd_quic_udpsock_create( void * _sock, int * argc, diff --git a/src/tango/quic/tests/quic_txn.bin b/src/tango/quic/tests/quic_txn.bin new file mode 100644 index 0000000000..f05412cf87 Binary files /dev/null and b/src/tango/quic/tests/quic_txn.bin differ diff --git a/src/tango/quic/tests/test_quic_txn.c b/src/tango/quic/tests/test_quic_txn.c new file mode 100644 index 0000000000..fcb149759d --- /dev/null +++ b/src/tango/quic/tests/test_quic_txn.c @@ -0,0 +1,214 @@ +#include "../fd_quic.h" +#include "fd_quic_test_helpers.h" +#include "../../../util/net/fd_ip4.h" +#include "../../../ballet/base64/fd_base64.h" + + +FD_IMPORT_BINARY(transaction, "src/tango/quic/tests/quic_txn.bin"); + +int g_handshake_complete = 0; +int g_conn_final = 0; +int g_stream_notify = 0; + +void +cb_conn_new( fd_quic_conn_t * conn, + void * quic_ctx ) { + (void)quic_ctx; + FD_LOG_NOTICE(( "cb_conn_new %lu", conn->tx_max_data )); +} + +void +cb_conn_handshake_complete( fd_quic_conn_t * conn, + void * quic_ctx ) { + (void)conn; + (void)quic_ctx; + FD_LOG_NOTICE(( "cb_conn_handshake_complete %lu", conn->tx_max_data )); + g_handshake_complete = 1; +} + +void +cb_conn_final( fd_quic_conn_t * conn, + void * quic_ctx ) { + (void)conn; + (void)quic_ctx; + FD_LOG_NOTICE(( "cb_conn_final" )); + g_conn_final = 1; +} + +void +cb_stream_new( fd_quic_stream_t * stream, + void * quic_ctx, + int stream_type ) { + (void)stream; + (void)quic_ctx; + (void)stream_type; + FD_LOG_NOTICE(( "cb_stream_new" )); +} + +void +cb_stream_notify( fd_quic_stream_t * stream, + void * stream_ctx, + int notify_type ) { + (void)stream; + (void)stream_ctx; + g_stream_notify = 1; + FD_LOG_NOTICE(( "cb_stream_notify %d", notify_type )); +} + +void +cb_stream_receive( fd_quic_stream_t * stream, + void * stream_ctx, + uchar const * data, + ulong data_sz, + ulong offset, + int fin ) { + (void)stream; + (void)stream_ctx; + (void)data; + (void)data_sz; + (void)offset; + (void)fin; +} + +ulong +cb_now( void * context ) { + (void)context; + return (ulong)fd_log_wallclock(); +} + +int +run_quic_client( fd_quic_t * quic, + fd_quic_udpsock_t * udpsock, + fd_aio_pkt_info_t * pkt ) { + uint dst_ip; + if( FD_UNLIKELY( !fd_cstr_to_ip4_addr( "198.18.0.1", &dst_ip ) ) ) FD_LOG_ERR(( "invalid --dst-ip" )); + ushort dst_port = 9001; + + + #define MSG_SZ_MIN (1UL) + #define MSG_SZ_MAX (1232UL-64UL-32UL) + #define MSG_SIZE_RANGE (MSG_SZ_MAX - MSG_SZ_MIN + 1UL) + + quic->cb.conn_new = cb_conn_new; + quic->cb.conn_hs_complete = cb_conn_handshake_complete; + quic->cb.conn_final = cb_conn_final; + quic->cb.stream_new = cb_stream_new; + quic->cb.stream_notify = cb_stream_notify; + quic->cb.stream_receive = cb_stream_receive; + quic->cb.now = cb_now; + quic->cb.now_ctx = NULL; + + fd_quic_set_aio_net_tx( quic, udpsock->aio ); + FD_TEST( fd_quic_init( quic ) ); + + fd_quic_conn_t * conn = fd_quic_connect( quic, dst_ip, dst_port, NULL ); + while ( FD_UNLIKELY( !( g_handshake_complete || g_conn_final ) ) ) { + fd_quic_service( quic ); + fd_quic_udpsock_service( udpsock ); + } + FD_TEST( conn ); + FD_TEST( conn->state == FD_QUIC_CONN_STATE_ACTIVE ); + + fd_quic_stream_t * stream = fd_quic_conn_new_stream( conn, FD_QUIC_TYPE_UNIDIR ); + FD_TEST( stream ); + int rc = 0; + if( stream ) { + rc = fd_quic_stream_send( stream, pkt, 1, 1 ); + FD_LOG_NOTICE(( "rc %d", rc )); + } + while ( FD_UNLIKELY( !( g_stream_notify || g_conn_final ) ) ) { + fd_quic_service( quic ); + fd_quic_udpsock_service( udpsock ); + } + + if( conn ) { + fd_quic_conn_close( conn, 0 ); + } + fd_quic_fini( quic ); + + return rc; +} + +int +main( int argc, + char ** argv ) { + fd_boot( &argc, &argv ); + const char * payload = fd_env_strip_cmdline_cstr( &argc, &argv, "--payload-base64-encoded", NULL, NULL ); + + fd_aio_pkt_info_t pkt; + uchar buf[1300]; + if( !payload ) { + pkt.buf = ( void * )transaction; + pkt.buf_sz = ( ushort )transaction_sz; + } else { + int buf_sz = fd_base64_decode( payload, buf ); + if ( buf_sz == -1 ) { + FD_LOG_NOTICE(( "bad input %s", payload )); + return -1; + } + FD_LOG_NOTICE(( "transaction size %d!", buf_sz )); + pkt.buf = (void *)buf; + pkt.buf_sz = ( ushort ) buf_sz; + } + + fd_wksp_t * wksp = fd_wksp_new_anonymous( fd_cstr_to_shmem_page_sz("gigantic"), + 1UL, + fd_shmem_cpu_idx( 0 ), + "wksp", + 0UL ); + FD_TEST( wksp ); + + fd_quic_limits_t quic_limits = { + .conn_cnt = 1024UL, + .handshake_cnt = 256UL, + .conn_id_cnt = 16UL, + .conn_id_sparsity = 4.0, + .stream_cnt = { 0UL, // FD_QUIC_STREAM_TYPE_BIDI_CLIENT + 0UL, // FD_QUIC_STREAM_TYPE_BIDI_SERVER + 2UL, // FD_QUIC_STREAM_TYPE_UNI_CLIENT + 0UL }, // FD_QUIC_STREAM_TYPE_UNI_SERVER + .stream_sparsity = 4.0, + .inflight_pkt_cnt = 64UL, + .tx_buf_sz = 1UL<<15UL + }; + ulong quic_footprint = fd_quic_footprint( &quic_limits ); + FD_TEST( quic_footprint ); + + void * mem = fd_wksp_alloc_laddr( wksp, fd_quic_align(), quic_footprint, 1UL ); + fd_quic_t * quic = fd_quic_new( mem, &quic_limits ); + FD_TEST( quic ); + + fd_quic_udpsock_t _udpsock; + uint listen_ip; + if( FD_UNLIKELY( !fd_cstr_to_ip4_addr("0.0.0.0", &listen_ip ) ) ) { + FD_LOG_NOTICE(( "invalid listen-ip" )); + return 1; + } + fd_quic_udpsock_t * udpsock = fd_quic_client_create_udpsock( &_udpsock, wksp, fd_quic_get_aio_net_rx( quic ), listen_ip ); + FD_TEST( udpsock == &_udpsock ); + + fd_quic_config_t * client_cfg = &quic->config; + client_cfg->role = FD_QUIC_ROLE_CLIENT; + memcpy( client_cfg->alpns, "\xasolana-tpu", 11UL ); + client_cfg->alpns_sz = 11U; + FD_TEST( fd_quic_config_from_env( &argc, &argv, client_cfg ) ); + memcpy(client_cfg->link.dst_mac_addr, "\x52\xF1\x7E\xDA\x2C\xE0", 6UL); + client_cfg->net.ip_addr = udpsock->listen_ip; + client_cfg->net.ephem_udp_port.lo = (ushort)udpsock->listen_port; + client_cfg->net.ephem_udp_port.hi = (ushort)(udpsock->listen_port + 1); + client_cfg->initial_rx_max_stream_data = 1<<15; + + int num_sent = run_quic_client( quic, udpsock, &pkt ); + + fd_wksp_free_laddr( fd_quic_delete( fd_quic_leave( quic ) ) ); + fd_quic_udpsock_destroy( udpsock ); + fd_wksp_delete_anonymous( wksp ); + + fd_halt(); + + switch( num_sent ) { + case 1: return 0; /* If no packets were successfully transmitted return one. */ + case 0: return 1; /* If the single packet was transmitted successfully return zero. */ + default: return -num_sent; + } +} diff --git a/src/test/frank-single-transaction.sh b/src/test/frank-single-transaction.sh new file mode 100755 index 0000000000..ea2a8c5845 --- /dev/null +++ b/src/test/frank-single-transaction.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# bash strict mode +set -euo pipefail +IFS=$'\n\t' +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +cd "${SCRIPT_DIR}/../../" + +# create test configuration for fddev +TMPDIR=$(mktemp -d) +cat > ${TMPDIR}/config.toml <