LCOV - code coverage report
Current view: top level - discof/restore/utils - fd_sshttp.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 135 579 23.3 %
Date: 2026-09-17 04:28:31 Functions: 9 25 36.0 %

          Line data    Source code
       1             : #define _GNU_SOURCE
       2             : #include "fd_sshttp_private.h"
       3             : #include "fd_ssarchive.h"
       4             : 
       5             : #include "../../../third_party/picohttpparser/picohttpparser.h"
       6             : #include "../../../util/log/fd_log.h"
       7             : #include "../../../util/fd_util.h"
       8             : #include "../../../waltz/http/fd_http.h"
       9             : #include "../../../ballet/ed25519/fd_x25519.h"
      10             : 
      11             : FD_STATIC_ASSERT( FD_HASH_FOOTPRINT==32UL, resolved_hash_sz );
      12             : 
      13             : #include <unistd.h>
      14             : #include <errno.h>
      15             : #include <poll.h>
      16             : #include <stdlib.h>
      17             : 
      18             : #include <sys/socket.h>
      19             : #include <sys/random.h>
      20             : #include <netinet/in.h>
      21             : 
      22             : _Bool fd_sshttp_fuzz = 0;
      23             : 
      24             : static void
      25          12 : fd_sshttp_tls_init( fd_tls_t * tls, fd_sshttp_t * http ) {
      26          12 :   fd_memset( tls, 0, sizeof(fd_tls_t) );
      27             : 
      28             :   /* Seed the CSPRNG that fd_tls draws handshake randomness from */
      29             : 
      30          12 :   uchar rng_key[ FD_CHACHA_KEY_SZ ];
      31          12 :   if( FD_UNLIKELY( !fd_rng_secure( rng_key, sizeof(rng_key) ) ) ) FD_LOG_CRIT(( "fd_rng_secure failed" ));
      32          12 :   fd_chacha_rng_init( http->rng, rng_key, FD_CHACHA_RNG_ALGO_CHACHA8 );
      33          12 :   fd_memzero_explicit( rng_key, sizeof(rng_key) );
      34          12 :   tls->rng = http->rng;
      35             : 
      36          12 :   static uchar const alpn[] = { 8, 'h', 't', 't', 'p', '/', '1', '.', '1' };
      37          12 :   fd_memcpy( tls->alpn, alpn, sizeof(alpn) );
      38          12 :   tls->alpn_sz = sizeof(alpn);
      39             : 
      40          12 :   tls->quic = 0;
      41             : 
      42          12 :   tls->ca_store = &http->ca_store;
      43          12 : }
      44             : 
      45             : static int
      46           0 : http_init_tls( fd_sshttp_t * http ) {
      47           0 :   ulong hostname_len = strlen( http->hostname );
      48           0 :   if( FD_UNLIKELY( hostname_len >= sizeof(http->tls.server_name) ) ) {
      49           0 :     FD_LOG_WARNING(( "hostname too long for SNI: %s", http->hostname ));
      50           0 :     return -1;
      51           0 :   }
      52           0 :   fd_memcpy( http->tls.server_name, http->hostname, hostname_len );
      53           0 :   http->tls.server_name[ hostname_len ] = '\0';
      54           0 :   http->tls.server_name_len = (ushort)hostname_len;
      55             : 
      56             :   /* Generate a fresh ephemeral X25519 key for this handshake */
      57             : 
      58           0 :   if( FD_UNLIKELY( !fd_rng_secure( http->tls.kex_private_key, 32UL ) ) ) FD_LOG_CRIT(( "fd_rng_secure failed" ));
      59           0 :   fd_x25519_public( http->tls.kex_public_key, http->tls.kex_private_key );
      60             : 
      61           0 :   fd_tlsrec_conn_init( &http->tls_conn, &http->tls, 0 );
      62           0 :   fd_tlsrec_sock_init( http->tls_sock );
      63             : 
      64           0 :   return 0;
      65           0 : }
      66             : 
      67             : /* io_backoff sleeps for up to a millisecond, or until the socket is
      68             :    ready for events, once several iterations in a row have moved no
      69             :    bytes.  Returns -1 if the poll failed fatally. */
      70             : 
      71             : static int
      72             : io_backoff( fd_sshttp_t * http,
      73           0 :             short         events ) {
      74           0 :   if( FD_LIKELY( ++http->empty_recvs<=8UL || fd_sshttp_fuzz ) ) return 0;
      75             : 
      76           0 :   struct pollfd pfd = {
      77           0 :     .fd     = http->sockfd,
      78           0 :     .events = events,
      79           0 :   };
      80           0 :   if( FD_UNLIKELY( -1==fd_syscall_poll( &pfd, 1 /*fds*/, 1 /*ms*/ ) && errno!=EINTR ) ) {
      81           0 :     FD_LOG_WARNING(( "fd_syscall_poll() failed (%d-%s) for " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
      82           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
      83           0 :     return -1;
      84           0 :   }
      85           0 :   return 0;
      86           0 : }
      87             : 
      88             : static int
      89             : http_connect_tls( fd_sshttp_t * http,
      90           0 :                   long          now ) {
      91           0 :   if( FD_UNLIKELY( now > http->deadline ) ) {
      92           0 :     FD_LOG_WARNING(( "TLS handshake timeout" ));
      93           0 :     fd_sshttp_cancel( http );
      94           0 :     return FD_SSHTTP_ADVANCE_ERROR;
      95           0 :   }
      96             : 
      97             :   /* Flush any buffered outgoing data (e.g., ClientHello from a
      98             :      previous call where the TCP connect was still in progress). */
      99             : 
     100           0 :   int flush = fd_tlsrec_sock_flush( http->tls_sock, http->sockfd );
     101           0 :   if( flush<0 ) { fd_sshttp_cancel( http ); return FD_SSHTTP_ADVANCE_ERROR; }
     102           0 :   if( flush>0 ) {
     103           0 :     if( FD_UNLIKELY( -1==io_backoff( http, POLLOUT ) ) ) { fd_sshttp_cancel( http ); return FD_SSHTTP_ADVANCE_ERROR; }
     104           0 :     return FD_SSHTTP_ADVANCE_AGAIN;
     105           0 :   }
     106             : 
     107             :   /* Drive the TLS handshake */
     108             : 
     109           0 :   ulong tcp_rx_sz;
     110           0 :   int err = fd_tlsrec_sock_rx( http->tls_sock, &http->tls_conn, http->sockfd, &tcp_rx_sz );
     111           0 :   if( FD_UNLIKELY( err ) ) {
     112           0 :     if( err==FD_TLSREC_SOCK_ERR_EOF ) {
     113           0 :       FD_LOG_WARNING(( "peer " FD_IP4_ADDR_FMT ":%hu closed the connection during TLS handshake",
     114           0 :                        FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     115           0 :     } else {
     116           0 :       FD_LOG_WARNING(( "TLS handshake failed (%d-%s)", err, fd_tlsrec_sock_strerror( err ) ));
     117           0 :     }
     118           0 :     fd_sshttp_cancel( http );
     119           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     120           0 :   }
     121           0 :   if( tcp_rx_sz ) http->empty_recvs = 0UL;
     122           0 :   else if( FD_UNLIKELY( -1==io_backoff( http, POLLIN ) ) ) { fd_sshttp_cancel( http ); return FD_SSHTTP_ADVANCE_ERROR; }
     123             : 
     124             :   /* Transition to request state once handshake completes */
     125             : 
     126           0 :   if( fd_tlsrec_conn_is_ready( &http->tls_conn ) ) {
     127           0 :     http->state    = FD_SSHTTP_STATE_REQ;
     128           0 :     http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
     129           0 :     return FD_SSHTTP_ADVANCE_AGAIN;
     130           0 :   }
     131             : 
     132           0 :   if( fd_tlsrec_conn_is_failed( &http->tls_conn ) ) {
     133           0 :     FD_LOG_WARNING(( "TLS handshake failed" ));
     134           0 :     fd_sshttp_cancel( http );
     135           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     136           0 :   }
     137             : 
     138           0 :   return FD_SSHTTP_ADVANCE_AGAIN;
     139           0 : }
     140             : 
     141             : static long
     142             : http_send_tls( fd_sshttp_t * http,
     143             :                void *        buf,
     144           0 :                ulong         bufsz ) {
     145           0 :   int flush = fd_tlsrec_sock_flush( http->tls_sock, http->sockfd );
     146           0 :   if( flush<0 ) return FD_SSHTTP_ADVANCE_ERROR;
     147           0 :   if( flush>0 ) {
     148           0 :     if( FD_UNLIKELY( -1==io_backoff( http, POLLOUT ) ) ) return FD_SSHTTP_ADVANCE_ERROR;
     149           0 :     return FD_SSHTTP_ADVANCE_AGAIN;
     150           0 :   }
     151             : 
     152           0 :   ulong consumed;
     153           0 :   if( FD_UNLIKELY( fd_tlsrec_sock_tx( http->tls_sock, &http->tls_conn, http->sockfd, buf, bufsz, &consumed ) ) )
     154           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     155           0 :   return (long)consumed;
     156           0 : }
     157             : 
     158             : static long
     159             : http_recv_tls( fd_sshttp_t * http,
     160             :                void *        buf,
     161           0 :                ulong         bufsz ) {
     162             : 
     163             :   /* Drain any buffered plaintext from a previous call */
     164             : 
     165           0 :   ulong n = fd_tlsrec_sock_rx_pop( http->tls_sock, buf, bufsz );
     166           0 :   if( n ) {
     167           0 :     http->empty_recvs = 0UL;
     168           0 :     return (long)n;
     169           0 :   }
     170             : 
     171             :   /* Drain pending ciphertext before generating any new records */
     172             : 
     173           0 :   int flush = fd_tlsrec_sock_flush( http->tls_sock, http->sockfd );
     174           0 :   if( flush<0 ) return FD_SSHTTP_ADVANCE_ERROR;
     175           0 :   if( flush>0 ) {
     176           0 :     if( FD_UNLIKELY( -1==io_backoff( http, POLLOUT ) ) ) return FD_SSHTTP_ADVANCE_ERROR;
     177           0 :     return FD_SSHTTP_ADVANCE_AGAIN;
     178           0 :   }
     179             : 
     180             :   /* Read and decrypt */
     181             : 
     182           0 :   ulong tcp_rx_sz;
     183           0 :   int err = fd_tlsrec_sock_rx( http->tls_sock, &http->tls_conn, http->sockfd, &tcp_rx_sz );
     184           0 :   if( FD_UNLIKELY( err ) ) {
     185           0 :     if( err==FD_TLSREC_SOCK_ERR_EOF ) {
     186           0 :       FD_LOG_WARNING(( "peer " FD_IP4_ADDR_FMT ":%hu closed the connection mid-response",
     187           0 :                        FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     188           0 :     } else if( err==FD_TLSREC_SOCK_ERR_RECV ) {
     189           0 :       FD_LOG_WARNING(( "recv() failed (%d-%s) from " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
     190           0 :                        FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     191           0 :     }
     192           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     193           0 :   }
     194           0 :   if( !tcp_rx_sz ) {
     195           0 :     if( FD_UNLIKELY( -1==io_backoff( http, POLLIN ) ) ) return FD_SSHTTP_ADVANCE_ERROR;
     196           0 :     return FD_SSHTTP_ADVANCE_AGAIN;
     197           0 :   }
     198             : 
     199           0 :   http->empty_recvs = 0UL; /* socket made progress */
     200             : 
     201           0 :   n = fd_tlsrec_sock_rx_pop( http->tls_sock, buf, bufsz );
     202           0 :   if( !n ) return FD_SSHTTP_ADVANCE_AGAIN;
     203           0 :   return (long)n;
     204           0 : }
     205             : 
     206             : static int
     207             : setup_redirect_tls( fd_sshttp_t * http,
     208           0 :                     long          now ) {
     209           0 :   fd_sshttp_cancel( http );
     210           0 :   if( FD_UNLIKELY( fd_sshttp_init( http, http->addr, http->hostname, http->is_https, http->location, http->location_len, ULONG_MAX, now ) ) ) {
     211           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     212           0 :   }
     213           0 :   return FD_SSHTTP_ADVANCE_AGAIN;
     214           0 : }
     215             : 
     216             : void
     217           0 : fd_sshttp_load_ca_store( fd_sshttp_t * sshttp ) {
     218           0 :   if( FD_UNLIKELY( fd_x509_ca_store_load_system( &sshttp->ca_store )<0L ) ) {
     219           0 :     FD_LOG_WARNING(( "No CA certificate bundle found, HTTPS snapshot sources will be refused" ));
     220           0 :     return;
     221           0 :   }
     222           0 :   sshttp->ca_store_loaded = 1;
     223           0 : }
     224             : 
     225             : FD_FN_CONST ulong
     226          24 : fd_sshttp_align( void ) {
     227          24 :   return alignof(fd_sshttp_t);
     228          24 : }
     229             : 
     230             : FD_FN_CONST ulong
     231           0 : fd_sshttp_footprint( void ) {
     232           0 :   ulong l;
     233           0 :   l = FD_LAYOUT_INIT;
     234           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_sshttp_t), sizeof(fd_sshttp_t) );
     235           0 :   return FD_LAYOUT_FINI( l, fd_sshttp_align() );
     236           0 : }
     237             : 
     238             : void *
     239          12 : fd_sshttp_new( void * shmem ) {
     240          12 :   if( FD_UNLIKELY( !shmem ) ) {
     241           0 :     FD_LOG_WARNING(( "NULL shmem" ));
     242           0 :     return NULL;
     243           0 :   }
     244             : 
     245          12 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_sshttp_align() ) ) ) {
     246           0 :     FD_LOG_WARNING(( "unaligned shmem" ));
     247           0 :     return NULL;
     248           0 :   }
     249             : 
     250          12 :   FD_SCRATCH_ALLOC_INIT( l, shmem );
     251          12 :   fd_sshttp_t * sshttp = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_sshttp_t), sizeof(fd_sshttp_t) );
     252             : 
     253          12 :   sshttp->state = FD_SSHTTP_STATE_INIT;
     254          12 :   sshttp->sockfd = -1;
     255          12 :   sshttp->content_len = 0UL;
     256          12 :   fd_cstr_fini( sshttp->snapshot_name );
     257          12 :   sshttp->resolved_slot = 0UL;
     258          12 :   fd_memset( sshttp->resolved_hash, 0, FD_HASH_FOOTPRINT );
     259             : 
     260             :   /* Initialize native TLS config */
     261          12 :   fd_tlsrec_sock_init( sshttp->tls_sock );
     262          12 :   fd_sshttp_tls_init( &sshttp->tls, sshttp );
     263             : 
     264             :   /* CA trust store — loaded via fd_sshttp_load_ca_store() during
     265             :      privileged_init, before the seccomp sandbox locks down. */
     266          12 :   sshttp->ca_store_loaded = 0;
     267             : 
     268          12 :   FD_COMPILER_MFENCE();
     269          12 :   sshttp->magic = FD_SSHTTP_MAGIC;
     270          12 :   FD_COMPILER_MFENCE();
     271             : 
     272          12 :   return (void *)sshttp;
     273          12 : }
     274             : 
     275             : fd_sshttp_t *
     276          12 : fd_sshttp_join( void * shhttp ) {
     277          12 :   if( FD_UNLIKELY( !shhttp ) ) {
     278           0 :     FD_LOG_WARNING(( "NULL shhttp" ));
     279           0 :     return NULL;
     280           0 :   }
     281             : 
     282          12 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shhttp, fd_sshttp_align() ) ) ) {
     283           0 :     FD_LOG_WARNING(( "misaligned shhttp" ));
     284           0 :     return NULL;
     285           0 :   }
     286             : 
     287          12 :   fd_sshttp_t * sshttp = (fd_sshttp_t *)shhttp;
     288             : 
     289          12 :   if( FD_UNLIKELY( sshttp->magic!=FD_SSHTTP_MAGIC ) ) {
     290           0 :     FD_LOG_WARNING(( "bad magic" ));
     291           0 :     return NULL;
     292           0 :   }
     293             : 
     294          12 :   return sshttp;
     295          12 : }
     296             : 
     297             : /* http_init_ssl removed — replaced by http_init_tls (native fd_tls) */
     298             : 
     299             : int
     300             : fd_sshttp_init( fd_sshttp_t * http,
     301             :                 fd_ip4_port_t addr,
     302             :                 char const *  hostname,
     303             :                 int           is_https,
     304             :                 char const *  path,
     305             :                 ulong         path_len,
     306             :                 ulong         hops,
     307           0 :                 long          now ) {
     308           0 :   FD_TEST( http->state==FD_SSHTTP_STATE_INIT );
     309             : 
     310           0 :   http->hostname = hostname;
     311           0 :   http->is_https = is_https;
     312             : 
     313           0 :   if( FD_LIKELY( is_https ) ) {
     314           0 :     if( FD_UNLIKELY( !http->ca_store_loaded ) ) {
     315           0 :       FD_LOG_WARNING(( "Refusing HTTPS snapshot download from %s: no CA trust store loaded", hostname ));
     316           0 :       return -1;
     317           0 :     }
     318           0 :     if( FD_UNLIKELY( http_init_tls( http ) ) ) return -1;
     319           0 :   }
     320             : 
     321           0 :   if( hops!=ULONG_MAX ) {
     322           0 :     http->hops = hops;
     323           0 :     fd_cstr_fini( http->snapshot_name );
     324           0 :     http->resolved_slot = 0UL;
     325           0 :     fd_memset( http->resolved_hash, 0, FD_HASH_FOOTPRINT );
     326           0 :   }
     327           0 :   http->request_sent = 0UL;
     328           0 :   int fmt_ok;
     329           0 :   if( FD_LIKELY( is_https ) ) {
     330           0 :     fmt_ok = fd_cstr_printf_check( http->request, sizeof(http->request), &http->request_len,
     331           0 :       "GET %.*s HTTP/1.1\r\n"
     332           0 :       "User-Agent: Firedancer\r\n"
     333           0 :       "Accept: */*\r\n"
     334           0 :       "Accept-Encoding: identity\r\n"
     335           0 :       "Host: %s\r\n\r\n",
     336           0 :       (int)path_len, path, hostname );
     337           0 :   } else {
     338           0 :     fmt_ok = fd_cstr_printf_check( http->request, sizeof(http->request), &http->request_len,
     339           0 :       "GET %.*s HTTP/1.1\r\n"
     340           0 :       "User-Agent: Firedancer\r\n"
     341           0 :       "Accept: */*\r\n"
     342           0 :       "Accept-Encoding: identity\r\n"
     343           0 :       "Host: " FD_IP4_ADDR_FMT "\r\n\r\n",
     344           0 :       (int)path_len, path, FD_IP4_ADDR_FMT_ARGS( addr.addr ) );
     345           0 :   }
     346           0 :   if( FD_UNLIKELY( !fmt_ok ) ) {
     347           0 :     FD_LOG_WARNING(( "HTTP request too long for %.*s", (int)path_len, path ));
     348           0 :     return -1;
     349           0 :   }
     350             : 
     351           0 :   http->response_len = 0UL;
     352           0 :   http->content_len  = 0UL;
     353           0 :   http->content_read = 0UL;
     354           0 :   http->empty_recvs  = 0UL;
     355             : 
     356           0 :   http->addr   = addr;
     357           0 :   http->sockfd = socket( AF_INET, SOCK_STREAM|SOCK_NONBLOCK, 0 );
     358           0 :   if( FD_UNLIKELY( -1==http->sockfd ) ) {
     359           0 :     FD_LOG_WARNING(( "socket() failed (%d-%s) for " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
     360           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     361           0 :     return -1;
     362           0 :   }
     363             : 
     364           0 :   struct sockaddr_in addr_in = {
     365           0 :     .sin_family = AF_INET,
     366           0 :     .sin_port   = addr.port,
     367           0 :     .sin_addr   = { .s_addr = addr.addr }
     368           0 :   };
     369             : 
     370           0 :   if( FD_LIKELY( -1==connect( http->sockfd, fd_type_pun_const( &addr_in ), sizeof(addr_in) ) ) ) {
     371           0 :     if( FD_UNLIKELY( errno!=EINPROGRESS ) ) {
     372           0 :       FD_LOG_WARNING(( "connect() failed (%d-%s) to " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
     373           0 :                        FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     374           0 :       if( FD_UNLIKELY( -1==close( http->sockfd ) ) ) FD_LOG_ERR(( "close() failed (%d-%s) for " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
     375           0 :                                                                   FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     376           0 :       http->sockfd = -1;
     377           0 :       return -1;
     378           0 :     }
     379           0 :   }
     380             : 
     381           0 :   if( FD_LIKELY( is_https ) ) {
     382           0 :     http->state    = FD_SSHTTP_STATE_CONNECT;
     383           0 :     http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
     384           0 :   } else {
     385           0 :     http->state    = FD_SSHTTP_STATE_REQ;
     386           0 :     http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
     387           0 :   }
     388             : 
     389           0 :   return 0;
     390           0 : }
     391             : 
     392             : void
     393          24 : fd_sshttp_cancel( fd_sshttp_t * http ) {
     394          24 :   if( FD_LIKELY( http->state!=FD_SSHTTP_STATE_INIT && -1!=http->sockfd ) ) {
     395          12 :     if( FD_UNLIKELY( -1==close( http->sockfd ) ) ) FD_LOG_ERR(( "close() failed (%d-%s) for " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
     396          12 :                                                                 FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     397          12 :     http->sockfd = -1;
     398          12 :   }
     399          24 :   http->state = FD_SSHTTP_STATE_INIT;
     400             : 
     401             :   /* Clean up native TLS state */
     402          24 :   fd_tlsrec_sock_init( http->tls_sock );
     403          24 : }
     404             : 
     405             : static long
     406             : http_send( fd_sshttp_t * http,
     407             :            void *        buf,
     408           0 :            ulong         bufsz ) {
     409           0 :   if( FD_LIKELY( http->is_https ) )
     410           0 :     return http_send_tls( http, buf, bufsz );
     411             : 
     412           0 :   long sent = sendto( http->sockfd, buf, bufsz, MSG_NOSIGNAL, NULL, 0 );
     413           0 :   if( FD_UNLIKELY( -1==sent && errno==EAGAIN ) ) {
     414           0 :     if( FD_UNLIKELY( -1==io_backoff( http, POLLOUT ) ) ) {
     415           0 :       fd_sshttp_cancel( http );
     416           0 :       return FD_SSHTTP_ADVANCE_ERROR;
     417           0 :     }
     418           0 :     return FD_SSHTTP_ADVANCE_AGAIN;
     419           0 :   } else if( FD_UNLIKELY( -1==sent ) ) {
     420           0 :     FD_LOG_WARNING(( "sendto() failed (%d-%s) to " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
     421           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     422           0 :     fd_sshttp_cancel( http );
     423           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     424           0 :   }
     425           0 :   http->empty_recvs = 0UL;
     426             : 
     427           0 :   return sent;
     428           0 : }
     429             : 
     430             : static long
     431             : http_recv( fd_sshttp_t * http,
     432             :            void *        buf,
     433          18 :            ulong         bufsz ) {
     434          18 :   if( FD_LIKELY( http->is_https ) )
     435           0 :     return http_recv_tls( http, buf, bufsz );
     436             : 
     437          18 :   long read = recvfrom( http->sockfd, buf, bufsz, 0, NULL, NULL );
     438          18 :   if( FD_UNLIKELY( -1==read && errno==EAGAIN ) ) {
     439           0 :     if( FD_UNLIKELY( -1==io_backoff( http, POLLIN ) ) ) {
     440           0 :       fd_sshttp_cancel( http );
     441           0 :       return FD_SSHTTP_ADVANCE_ERROR;
     442           0 :     }
     443           0 :     return FD_SSHTTP_ADVANCE_AGAIN;
     444          18 :   } else if( FD_UNLIKELY( -1==read ) ) {
     445           0 :     FD_LOG_WARNING(( "recv() failed (%d-%s) from " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
     446           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     447           0 :     fd_sshttp_cancel( http );
     448           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     449          18 :   } else if( FD_UNLIKELY( !read ) ) {
     450           9 :     FD_LOG_WARNING(( "peer " FD_IP4_ADDR_FMT ":%hu closed the connection mid-response",
     451           9 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     452           9 :     fd_sshttp_cancel( http );
     453           9 :     return FD_SSHTTP_ADVANCE_ERROR;
     454           9 :   }
     455           9 :   http->empty_recvs = 0UL;
     456             : 
     457           9 :   return read;
     458          18 : }
     459             : 
     460             : static int
     461             : send_request( fd_sshttp_t * http,
     462           0 :               long          now ) {
     463           0 :   if( FD_UNLIKELY( now>http->deadline ) ) {
     464           0 :     FD_LOG_WARNING(( "timeout sending request to " FD_IP4_ADDR_FMT ":%hu",
     465           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     466           0 :     fd_sshttp_cancel( http );
     467           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     468           0 :   }
     469             : 
     470           0 :   long sent = http_send( http, http->request+http->request_sent, http->request_len-http->request_sent );
     471           0 :   if( FD_UNLIKELY( sent<=0 ) ) return (int)sent;
     472             : 
     473           0 :   http->request_sent += (ulong)sent;
     474           0 :   if( FD_UNLIKELY( http->request_sent==http->request_len ) ) {
     475           0 :     http->state        = FD_SSHTTP_STATE_RESP;
     476           0 :     http->response_len = 0UL;
     477           0 :     http->deadline     = now + FD_SSHTTP_DEADLINE_NANOS;
     478           0 :   }
     479             : 
     480           0 :   return FD_SSHTTP_ADVANCE_AGAIN;
     481           0 : }
     482             : 
     483             : static int
     484             : follow_redirect( fd_sshttp_t *        http,
     485             :                   struct phr_header * headers,
     486             :                   ulong               header_cnt,
     487           0 :                   long                now ) {
     488           0 :   if( FD_UNLIKELY( !http->hops ) ) {
     489           0 :     FD_LOG_WARNING(( "too many redirects (remaining %lu) from " FD_IP4_ADDR_FMT ":%hu", http->hops,
     490           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     491           0 :     fd_sshttp_cancel( http );
     492           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     493           0 :   }
     494             :   /* The check above guarantees hops>0. */
     495           0 :   http->hops--;
     496             : 
     497           0 :   ulong        location_len = 0UL;
     498           0 :   char const * location     = NULL;
     499             : 
     500           0 :   for( ulong i=0UL; i<header_cnt; i++ ) {
     501           0 :     if( FD_UNLIKELY( headers[ i ].name_len == 8 && !strncasecmp( headers[ i ].name, "location", headers[ i ].name_len ) ) ) {
     502           0 :       if( FD_UNLIKELY( !headers [ i ].value_len || headers[ i ].value[ 0 ]!='/' ) ) {
     503           0 :         FD_LOG_WARNING(( "invalid location header `%.*s` from " FD_IP4_ADDR_FMT ":%hu", (int)headers[ i ].value_len, headers[ i ].value,
     504           0 :                          FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     505           0 :         fd_sshttp_cancel( http );
     506           0 :         return FD_SSHTTP_ADVANCE_ERROR;
     507           0 :       }
     508             : 
     509           0 :       location_len = headers[ i ].value_len;
     510           0 :       location     = headers[ i ].value;
     511             : 
     512           0 :       if( FD_UNLIKELY( location_len>=PATH_MAX-1UL ) ) {
     513           0 :         FD_LOG_WARNING(( "location header too long `%.*s` from " FD_IP4_ADDR_FMT ":%hu", (int)location_len, location,
     514           0 :                          FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     515           0 :         fd_sshttp_cancel( http );
     516           0 :         return FD_SSHTTP_ADVANCE_ERROR;
     517           0 :       }
     518             : 
     519           0 :       char snapshot_name[ PATH_MAX ];
     520           0 :       fd_memcpy( snapshot_name, location+1UL, location_len-1UL );
     521           0 :       snapshot_name[ location_len-1UL ] = '\0';
     522             : 
     523           0 :       int is_zstd;
     524           0 :       ulong full_entry_slot, incremental_entry_slot;
     525           0 :       uchar decoded_hash[ FD_HASH_FOOTPRINT ];
     526           0 :       int err = fd_ssarchive_parse_filename( snapshot_name, &full_entry_slot, &incremental_entry_slot, decoded_hash, &is_zstd );
     527             : 
     528           0 :       if( FD_UNLIKELY( err || !is_zstd ) ) {
     529           0 :         FD_LOG_WARNING(( "unrecognized snapshot file `%s` in redirect location header from " FD_IP4_ADDR_FMT ":%hu", snapshot_name,
     530           0 :                          FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     531           0 :         fd_sshttp_cancel( http );
     532           0 :         return FD_SSHTTP_ADVANCE_ERROR;
     533           0 :       }
     534             : 
     535           0 :       http->resolved_slot = (incremental_entry_slot!=ULONG_MAX)
     536           0 :                             ? incremental_entry_slot : full_entry_slot;
     537           0 :       fd_memcpy( http->resolved_hash, decoded_hash, FD_HASH_FOOTPRINT );
     538             : 
     539           0 :       char encoded_hash[ FD_BASE58_ENCODED_32_SZ ];
     540           0 :       fd_base58_encode_32( decoded_hash, NULL, encoded_hash );
     541             : 
     542           0 :       if( FD_LIKELY( incremental_entry_slot!=ULONG_MAX ) ) {
     543           0 :         FD_TEST( fd_cstr_printf_check( http->snapshot_name, PATH_MAX, NULL, "incremental-snapshot-%lu-%lu-%s.tar.zst", full_entry_slot, incremental_entry_slot, encoded_hash ) );
     544           0 :       } else {
     545           0 :         FD_TEST( fd_cstr_printf_check( http->snapshot_name, PATH_MAX, NULL, "snapshot-%lu-%s.tar.zst", full_entry_slot, encoded_hash ) );
     546           0 :       }
     547           0 :       break;
     548           0 :     }
     549           0 :   }
     550             : 
     551           0 :   if( FD_UNLIKELY( !location_len ) ) {
     552           0 :     FD_LOG_WARNING(( "no location header in redirect response from " FD_IP4_ADDR_FMT ":%hu",
     553           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     554           0 :     fd_sshttp_cancel( http );
     555           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     556           0 :   }
     557             : 
     558             :   /* Pre-validate that the redirect request will fit in the request
     559             :      buffer.  The request is rebuilt from scratch by fd_sshttp_init
     560             :      during the redirect, but the format must match so that a path
     561             :      accepted here will not overflow in fd_sshttp_init. */
     562           0 :   int pre_check;
     563           0 :   if( FD_LIKELY( http->is_https ) ) {
     564           0 :     pre_check = fd_cstr_printf_check( http->request, sizeof(http->request), &http->request_len,
     565           0 :       "GET %.*s HTTP/1.1\r\n"
     566           0 :       "User-Agent: Firedancer\r\n"
     567           0 :       "Accept: */*\r\n"
     568           0 :       "Accept-Encoding: identity\r\n"
     569           0 :       "Host: %s\r\n\r\n",
     570           0 :       (int)location_len, location, http->hostname );
     571           0 :   } else {
     572           0 :     pre_check = fd_cstr_printf_check( http->request, sizeof(http->request), &http->request_len,
     573           0 :       "GET %.*s HTTP/1.1\r\n"
     574           0 :       "User-Agent: Firedancer\r\n"
     575           0 :       "Accept: */*\r\n"
     576           0 :       "Accept-Encoding: identity\r\n"
     577           0 :       "Host: " FD_IP4_ADDR_FMT "\r\n\r\n",
     578           0 :       (int)location_len, location, FD_IP4_ADDR_FMT_ARGS( http->addr.addr ) );
     579           0 :   }
     580           0 :   if( FD_UNLIKELY( !pre_check ) ) {
     581           0 :     FD_LOG_WARNING(( "redirect request too long `%.*s` from " FD_IP4_ADDR_FMT ":%hu", (int)location_len, location,
     582           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     583           0 :     fd_sshttp_cancel( http );
     584           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     585           0 :   }
     586             : 
     587           0 :   FD_LOG_INFO(( "following redirect to %s://" FD_IP4_ADDR_FMT ":%hu%.*s", http->is_https ? "https" : "http",
     588           0 :                 FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ),
     589           0 :                 (int)location_len, location ));
     590             : 
     591           0 :   if( FD_UNLIKELY( http->is_https ) ) {
     592           0 :     http->state        = FD_SSHTTP_STATE_REDIRECT;
     593           0 :     http->location_len = location_len;
     594           0 :     FD_TEST( location_len<PATH_MAX-1UL );
     595           0 :     fd_memcpy( http->location, location, location_len );
     596           0 :     http->location[ location_len ] = '\0';
     597           0 :   } else {
     598           0 :     if( FD_LIKELY( !fd_sshttp_fuzz ) ) {
     599           0 :       fd_sshttp_cancel( http );
     600           0 :       if( FD_UNLIKELY( fd_sshttp_init( http, http->addr, http->hostname, http->is_https, location, location_len, ULONG_MAX, now ) ) ) {
     601           0 :         return FD_SSHTTP_ADVANCE_ERROR;
     602           0 :       }
     603           0 :     } else {
     604           0 :       http->state = FD_SSHTTP_STATE_RESP;
     605           0 :       http->response_len = 0UL;
     606           0 :     }
     607           0 :   }
     608             : 
     609           0 :   return FD_SSHTTP_ADVANCE_AGAIN;
     610           0 : }
     611             : 
     612             : static int
     613             : read_response( fd_sshttp_t * http,
     614             :                ulong *       data_len,
     615             :                uchar *       data,
     616          18 :                long          now ) {
     617          18 :   if( FD_UNLIKELY( now>http->deadline ) ) {
     618           0 :     FD_LOG_WARNING(( "timeout reading response from " FD_IP4_ADDR_FMT ":%hu",
     619           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     620           0 :     fd_sshttp_cancel( http );
     621           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     622           0 :   }
     623             : 
     624          18 :   if( FD_UNLIKELY( http->response_len>=sizeof(http->response) ) ) {
     625           3 :     FD_LOG_WARNING(( "response headers too large from " FD_IP4_ADDR_FMT ":%hu",
     626           3 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     627           3 :     fd_sshttp_cancel( http );
     628           3 :     return FD_SSHTTP_ADVANCE_ERROR;
     629           3 :   }
     630             : 
     631          15 :   long read = http_recv( http, http->response+http->response_len, sizeof(http->response)-http->response_len );
     632          15 :   if( FD_UNLIKELY( read<=0 ) ) return (int)read;
     633             : 
     634           9 :   http->response_len += (ulong)read;
     635             : 
     636           9 :   int               minor_version;
     637           9 :   int               status;
     638           9 :   const char *      message;
     639           9 :   ulong             message_len;
     640           9 :   struct phr_header headers[ 128UL ];
     641           9 :   ulong             header_cnt = 128UL;
     642           9 :   int parsed = phr_parse_response( http->response,
     643           9 :                                     http->response_len,
     644           9 :                                     &minor_version,
     645           9 :                                     &status,
     646           9 :                                     &message,
     647           9 :                                     &message_len,
     648           9 :                                     headers,
     649           9 :                                     &header_cnt,
     650           9 :                                     http->response_len - (ulong)read );
     651           9 :   if( FD_UNLIKELY( parsed==-1 ) ) {
     652           0 :     FD_LOG_WARNING(( "malformed response headers from " FD_IP4_ADDR_FMT ":%hu",
     653           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     654           0 :     fd_sshttp_cancel( http );
     655           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     656           9 :   } else if( parsed==-2 ) {
     657           6 :     return FD_SSHTTP_ADVANCE_AGAIN;
     658           6 :   }
     659             : 
     660           3 :   int is_redirect = (status==301) | (status==302) | (status==303) | (status==307) | (status==308);
     661           3 :   if( FD_UNLIKELY( is_redirect ) ) {
     662           0 :     return follow_redirect( http, headers, header_cnt, now );
     663           0 :   }
     664             : 
     665           3 :   if( FD_UNLIKELY( status!=200 ) ) {
     666           0 :     FD_LOG_WARNING(( "unexpected response status %d %.*s from " FD_IP4_ADDR_FMT ":%hu", status, (int)message_len, message,
     667           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     668           0 :     fd_sshttp_cancel( http );
     669           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     670           0 :   }
     671             : 
     672           3 :   http->content_read = 0UL;
     673           3 :   http->content_len = ULONG_MAX;
     674           3 :   for( ulong i=0UL; i<header_cnt; i++ ) {
     675           3 :     if( FD_LIKELY( headers[i].name_len!=14UL ) ) continue;
     676           3 :     if( FD_LIKELY( strncasecmp( headers[i].name, "content-length", 14UL ) ) ) continue;
     677             : 
     678           3 :     ulong val = 0UL;
     679           3 :     if( FD_UNLIKELY( fd_http_parse_content_len( headers[i].value, (ulong)headers[i].value_len, &val ) || val==0UL ) ) {
     680           0 :       FD_LOG_WARNING(( "invalid content-length in response from " FD_IP4_ADDR_FMT ":%hu", FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     681           0 :       fd_sshttp_cancel( http );
     682           0 :       return FD_SSHTTP_ADVANCE_ERROR;
     683           0 :     }
     684           3 :     http->content_len = val;
     685           3 :     break;
     686           3 :   }
     687             : 
     688           3 :   if( FD_UNLIKELY( http->content_len==ULONG_MAX ) ) {
     689           0 :     FD_LOG_WARNING(( "no content-length header in response from " FD_IP4_ADDR_FMT ":%hu",
     690           0 :                      FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
     691           0 :     fd_sshttp_cancel( http );
     692           0 :     return FD_SSHTTP_ADVANCE_ERROR;
     693           0 :   }
     694             : 
     695           3 :   http->state = FD_SSHTTP_STATE_DL;
     696           3 :   if( FD_UNLIKELY( (ulong)parsed<http->response_len ) ) {
     697             :     /* Body bytes past the caller's buffer are kept in response, with
     698             :        response_len repurposed as the residual length, drained by
     699             :        read_body before it reads the socket again. */
     700           3 :     ulong leftover = fd_ulong_min( http->response_len - (ulong)parsed, http->content_len );
     701           3 :     ulong copy_len = fd_ulong_min( leftover, *data_len );
     702           3 :     fd_memcpy( data, http->response+parsed, copy_len );
     703           3 :     memmove( http->response, http->response+(ulong)parsed+copy_len, leftover-copy_len );
     704           3 :     http->response_len  = leftover-copy_len;
     705           3 :     http->content_read += copy_len;
     706           3 :     *data_len = copy_len;
     707           3 :     return FD_SSHTTP_ADVANCE_DATA;
     708           3 :   } else {
     709           0 :     FD_TEST( http->response_len==(ulong)parsed );
     710           0 :     http->response_len = 0UL;
     711           0 :     return FD_SSHTTP_ADVANCE_AGAIN;
     712           0 :   }
     713           3 : }
     714             : 
     715             : static int
     716             : read_body( fd_sshttp_t * http,
     717             :            ulong *       data_len,
     718             :            uchar *       data,
     719           3 :            long          now ) {
     720           3 :   if( FD_UNLIKELY( http->content_read>=http->content_len ) ) {
     721           0 :     if( FD_UNLIKELY( http->is_https ) ) {
     722           0 :       http->state = FD_SSHTTP_STATE_DONE;
     723           0 :       http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
     724           0 :       return FD_SSHTTP_ADVANCE_AGAIN;
     725           0 :     } else {
     726           0 :       fd_sshttp_cancel( http );
     727           0 :       http->state = FD_SSHTTP_STATE_INIT;
     728           0 :       return FD_SSHTTP_ADVANCE_DONE;
     729           0 :     }
     730           0 :   }
     731             : 
     732           3 :   FD_TEST( http->content_read<http->content_len );
     733             : 
     734           3 :   if( FD_UNLIKELY( http->response_len ) ) { /* residual body bytes from read_response */
     735           0 :     ulong copy_len = fd_ulong_min( http->response_len, *data_len );
     736           0 :     fd_memcpy( data, http->response, copy_len );
     737           0 :     memmove( http->response, http->response+copy_len, http->response_len-copy_len );
     738           0 :     http->response_len  -= copy_len;
     739           0 :     http->content_read  += copy_len;
     740           0 :     *data_len = copy_len;
     741           0 :     return FD_SSHTTP_ADVANCE_DATA;
     742           0 :   }
     743             : 
     744           3 :   long read = http_recv( http, data, fd_ulong_min( *data_len, http->content_len-http->content_read ) );
     745           3 :   if( FD_UNLIKELY( read<=0 ) ) return (int)read;
     746             : 
     747           0 :   *data_len = (ulong)read;
     748           0 :   http->content_read += (ulong)read;
     749             : 
     750           0 :   return FD_SSHTTP_ADVANCE_DATA;
     751           3 : }
     752             : 
     753             : char const *
     754           0 : fd_sshttp_snapshot_name( fd_sshttp_t const * http ) {
     755           0 :   return http->snapshot_name;
     756           0 : }
     757             : 
     758             : ulong
     759           0 : fd_sshttp_content_len( fd_sshttp_t const * http ) {
     760           0 :   return http->content_len;
     761           0 : }
     762             : 
     763             : ulong
     764           0 : fd_sshttp_resolved_slot( fd_sshttp_t const * http ) {
     765           0 :   return http->resolved_slot;
     766           0 : }
     767             : 
     768             : uchar const *
     769           0 : fd_sshttp_resolved_hash( fd_sshttp_t const * http ) {
     770           0 :   return http->resolved_hash;
     771           0 : }
     772             : 
     773             : int
     774             : fd_sshttp_advance( fd_sshttp_t * http,
     775             :                    ulong *       data_len,
     776             :                    uchar *       data,
     777             :                    int *         downloading,
     778          21 :                    long          now ) {
     779          21 :   *downloading = 0;
     780          21 :   switch( http->state ) {
     781           0 :     case FD_SSHTTP_STATE_INIT:          return FD_SSHTTP_ADVANCE_AGAIN;
     782           0 :     case FD_SSHTTP_STATE_CONNECT:
     783           0 :       return http_connect_tls( http, now );
     784           0 :     case FD_SSHTTP_STATE_REDIRECT:
     785           0 :       return setup_redirect_tls( http, now );
     786           0 :     case FD_SSHTTP_STATE_REQ:           return send_request( http, now );
     787          18 :     case FD_SSHTTP_STATE_RESP:          return read_response( http, data_len, data, now );
     788           3 :     case FD_SSHTTP_STATE_DL:            *downloading = 1; return read_body( http, data_len, data, now );
     789           0 :     case FD_SSHTTP_STATE_DONE:
     790           0 :       fd_sshttp_cancel( http );
     791           0 :       http->state = FD_SSHTTP_STATE_INIT;
     792           0 :       return FD_SSHTTP_ADVANCE_DONE;
     793           0 :     default:                            return FD_SSHTTP_ADVANCE_ERROR;
     794          21 :   }
     795          21 : }

Generated by: LCOV version 1.14