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

Generated by: LCOV version 1.14