LCOV - code coverage report
Current view: top level - waltz/grpc - fd_grpc_client.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 691 0.0 %
Date: 2026-11-11 12:40:43 Functions: 0 48 0.0 %

          Line data    Source code
       1             : #include "fd_grpc_client.h"
       2             : #include "fd_grpc_client_private.h"
       3             : #include <sys/socket.h>
       4             : #include <poll.h>
       5             : #include "../h2/fd_h2_rbuf_sock.h"
       6             : #include "../tlsrec/fd_tlsrec.h"
       7             : #include "fd_grpc_codec.h"
       8             : 
       9             : static int
      10             : fd_grpc_client_request_continue( fd_grpc_client_t * client );
      11             : 
      12             : ulong
      13           0 : fd_grpc_client_align( void ) {
      14           0 :   return fd_ulong_max( alignof(fd_grpc_client_t), fd_grpc_h2_stream_pool_align() );
      15           0 : }
      16             : 
      17             : ulong
      18           0 : fd_grpc_client_footprint( ulong buf_max ) {
      19           0 :   ulong l = FD_LAYOUT_INIT;
      20           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_grpc_client_t), sizeof(fd_grpc_client_t) );
      21           0 :   l = FD_LAYOUT_APPEND( l, 1UL, buf_max ); /* pb_tx */
      22           0 :   l = FD_LAYOUT_APPEND( l, 1UL, buf_max ); /* frame_scratch */
      23           0 :   l = FD_LAYOUT_APPEND( l, 1UL, buf_max ); /* frame_rx_buf */
      24           0 :   l = FD_LAYOUT_APPEND( l, 1UL, buf_max ); /* frame_tx_buf */
      25           0 :   l = FD_LAYOUT_APPEND( l, fd_grpc_h2_stream_pool_align(), fd_grpc_h2_stream_pool_footprint( FD_GRPC_CLIENT_MAX_STREAMS ) );
      26           0 :   l = FD_LAYOUT_APPEND( l, 1UL, buf_max*FD_GRPC_CLIENT_MAX_STREAMS );
      27           0 :   return FD_LAYOUT_FINI( l, fd_grpc_client_align() );
      28           0 : }
      29             : 
      30             : static void
      31           0 : fd_grpc_h2_stream_reset( fd_grpc_h2_stream_t * stream ) {
      32           0 :   memset( &stream->s, 0, sizeof(fd_h2_stream_t) );
      33           0 :   stream->request_ctx = 0UL;
      34           0 :   memset( &stream->hdrs, 0, sizeof(fd_grpc_resp_hdrs_t) );
      35           0 :   stream->hdrs.grpc_status    = FD_GRPC_STATUS_UNKNOWN;
      36           0 :   stream->hdrs_received       = 0;
      37           0 :   stream->msg_buf_used        = 0UL;
      38           0 :   stream->msg_sz              = 0UL;
      39           0 :   stream->has_header_deadline = 0;
      40           0 :   stream->has_rx_end_deadline = 0;
      41           0 :   stream->rst_pending         = 0;
      42           0 :   stream->tx_wnd_debt         = 0L;
      43           0 : }
      44             : 
      45             : fd_grpc_client_t *
      46             : fd_grpc_client_new( void *                             mem,
      47             :                     fd_grpc_client_callbacks_t const * callbacks,
      48             :                     fd_grpc_client_metrics_t *         metrics,
      49             :                     void *                             app_ctx,
      50             :                     ulong                              buf_max,
      51           0 :                     ulong                              rng_seed ) {
      52           0 :   if( FD_UNLIKELY( !mem ) ) {
      53           0 :     FD_LOG_WARNING(( "NULL mem" ));
      54           0 :     return NULL;
      55           0 :   }
      56           0 :   if( FD_UNLIKELY( buf_max<4096UL ) ) {
      57           0 :     FD_LOG_WARNING(( "undersz buf_max" ));
      58           0 :     return NULL;
      59           0 :   }
      60           0 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_grpc_client_align() ) ) ) {
      61           0 :     FD_LOG_WARNING(( "unaligned mem" ));
      62           0 :     return NULL;
      63           0 :   }
      64             : 
      65           0 :   FD_SCRATCH_ALLOC_INIT( l, mem );
      66           0 :   void * client_mem      = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_grpc_client_t), sizeof(fd_grpc_client_t) );
      67           0 :   void * pb_tx       = FD_SCRATCH_ALLOC_APPEND( l, 1UL, buf_max ); /* pb_tx */
      68           0 :   void * frame_scratch   = FD_SCRATCH_ALLOC_APPEND( l, 1UL, buf_max ); /* frame_scratch */
      69           0 :   void * frame_rx_buf    = FD_SCRATCH_ALLOC_APPEND( l, 1UL, buf_max ); /* frame_rx_buf */
      70           0 :   void * frame_tx_buf    = FD_SCRATCH_ALLOC_APPEND( l, 1UL, buf_max ); /* frame_tx_buf */
      71           0 :   void * stream_pool_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_grpc_h2_stream_pool_align(), fd_grpc_h2_stream_pool_footprint( FD_GRPC_CLIENT_MAX_STREAMS ) );
      72           0 :   void * stream_buf_mem  = FD_SCRATCH_ALLOC_APPEND( l, 1UL, buf_max*FD_GRPC_CLIENT_MAX_STREAMS );
      73           0 :   ulong end = FD_SCRATCH_ALLOC_FINI( l, fd_grpc_client_align() );
      74           0 :   FD_TEST( end-(ulong)mem == fd_grpc_client_footprint( buf_max ) );
      75             : 
      76           0 :   fd_grpc_client_t * client = client_mem;
      77             : 
      78           0 :   fd_grpc_h2_stream_t * stream_pool =
      79           0 :     fd_grpc_h2_stream_pool_join( fd_grpc_h2_stream_pool_new( stream_pool_mem, FD_GRPC_CLIENT_MAX_STREAMS ) );
      80           0 :   if( FD_UNLIKELY( !stream_pool ) ) FD_LOG_CRIT(( "Failed to create stream pool" )); /* unreachable */
      81             : 
      82           0 :   *client = (fd_grpc_client_t){
      83           0 :     .callbacks         = callbacks,
      84           0 :     .ctx               = app_ctx,
      85           0 :     .stream_pool       = stream_pool,
      86           0 :     .stream_bufs       = stream_buf_mem,
      87           0 :     .pb_tx         = pb_tx,
      88           0 :     .pb_tx_max     = buf_max,
      89           0 :     .frame_scratch     = frame_scratch,
      90           0 :     .frame_scratch_max = buf_max,
      91           0 :     .frame_rx_buf      = frame_rx_buf,
      92           0 :     .frame_rx_buf_max  = buf_max,
      93           0 :     .frame_tx_buf      = frame_tx_buf,
      94           0 :     .frame_tx_buf_max  = buf_max,
      95           0 :     .https             = 1,
      96           0 :     .metrics           = metrics
      97           0 :   };
      98             : 
      99             :   /* FIXME for performance, cache this? */
     100           0 :   fd_h2_hdr_matcher_init( client->matcher, rng_seed );
     101           0 :   fd_h2_hdr_matcher_insert_literal( client->matcher, FD_GRPC_HDR_STATUS,  "grpc-status"  );
     102           0 :   fd_h2_hdr_matcher_insert_literal( client->matcher, FD_GRPC_HDR_MESSAGE, "grpc-message" );
     103             : 
     104           0 :   client->version_len = 5;
     105           0 :   memcpy( client->version, "0.0.0", 5 );
     106             : 
     107           0 :   for( ulong i=0UL; i<FD_GRPC_CLIENT_MAX_STREAMS; i++ ) {
     108           0 :     fd_grpc_h2_stream_t * stream = &client->stream_pool[ i ];
     109           0 :     stream->msg_buf     = (uchar *)stream_buf_mem + (i*buf_max);
     110           0 :     stream->msg_buf_max = buf_max;
     111           0 :     FD_TEST( (ulong)( stream->msg_buf + stream->msg_buf_max )<=end );
     112           0 :   }
     113           0 :   fd_grpc_client_reset( client );
     114             : 
     115           0 :   return client;
     116           0 : }
     117             : 
     118             : void *
     119           0 : fd_grpc_client_delete( fd_grpc_client_t * client ) {
     120           0 :   return client;
     121           0 : }
     122             : 
     123             : void
     124             : fd_grpc_client_set_version( fd_grpc_client_t * client,
     125             :                             char const *       version,
     126           0 :                             ulong              version_len ) {
     127           0 :   if( FD_UNLIKELY( version_len > FD_GRPC_CLIENT_VERSION_LEN_MAX ) ) {
     128           0 :     FD_LOG_WARNING(( "Version string too long (%lu chars), ignoring", version_len ));
     129           0 :     return;
     130           0 :   }
     131           0 :   client->version_len = (uchar)version_len;
     132           0 :   memcpy( client->version, version, version_len );
     133           0 : }
     134             : 
     135             : void
     136             : fd_grpc_client_set_authority( fd_grpc_client_t * client,
     137             :                               char const *       host,
     138             :                               ulong              host_len,
     139           0 :                               ushort             port ) {
     140           0 :   host_len = fd_ulong_min( host_len, sizeof(client->host)-1 );
     141           0 :   fd_cstr_fini( fd_cstr_append_text( fd_cstr_init( client->host ), host, host_len ) );
     142           0 :   client->host_len = (uchar)host_len;
     143           0 :   client->port     = (ushort)port;
     144           0 : }
     145             : 
     146             : void
     147             : fd_grpc_client_set_https( fd_grpc_client_t * client,
     148           0 :                           int                https ) {
     149           0 :   client->https = !!https;
     150           0 : }
     151             : 
     152             : int
     153           0 : fd_grpc_client_stream_acquire_is_safe( fd_grpc_client_t * client ) {
     154             :   /* Sufficient quota to start a stream? */
     155           0 :   if( FD_UNLIKELY( client->conn->stream_active_cnt[1]+1 > client->conn->peer_settings.max_concurrent_streams ) ) {
     156           0 :     return 0;
     157           0 :   }
     158             : 
     159             :   /* Free stream object available? */
     160           0 :   if( FD_UNLIKELY( !fd_grpc_h2_stream_pool_free( client->stream_pool ) ) ) {
     161           0 :     return 0;
     162           0 :   }
     163           0 :   if( FD_UNLIKELY( client->stream_cnt >= FD_GRPC_CLIENT_MAX_STREAMS ) ) {
     164           0 :     return 0;
     165           0 :   }
     166             : 
     167           0 :   return 1;
     168           0 : }
     169             : 
     170             : fd_grpc_h2_stream_t *
     171             : fd_grpc_client_stream_acquire( fd_grpc_client_t * client,
     172           0 :                                ulong              request_ctx ) {
     173           0 :   if( FD_UNLIKELY( client->stream_cnt >= FD_GRPC_CLIENT_MAX_STREAMS ) ) {
     174           0 :     FD_LOG_CRIT(( "stream pool exhausted" ));
     175           0 :   }
     176             : 
     177           0 :   fd_h2_conn_t * conn = client->conn;
     178           0 :   uint const stream_id = client->conn->tx_stream_next;
     179           0 :   conn->tx_stream_next += 2U;
     180             : 
     181           0 :   fd_grpc_h2_stream_t * stream = fd_grpc_h2_stream_pool_ele_acquire( client->stream_pool );
     182           0 :   fd_grpc_h2_stream_reset( stream );
     183           0 :   stream->request_ctx = request_ctx;
     184             : 
     185           0 :   fd_h2_stream_open( fd_h2_stream_init( &stream->s ), conn, stream_id );
     186           0 :   client->request_stream = stream;
     187           0 :   client->stream_ids[ client->stream_cnt ] = stream_id;
     188           0 :   client->streams   [ client->stream_cnt ] = stream;
     189           0 :   client->stream_cnt++;
     190           0 :   client->metrics->streams_active++;
     191             : 
     192           0 :   return stream;
     193           0 : }
     194             : 
     195             : void
     196             : fd_grpc_client_stream_release( fd_grpc_client_t *    client,
     197           0 :                                fd_grpc_h2_stream_t * stream ) {
     198           0 :   if( FD_UNLIKELY( !client->stream_cnt ) ) FD_LOG_CRIT(( "stream map corrupt" )); /* unreachable */
     199             : 
     200             :   /* Deallocate tx_op */
     201           0 :   if( FD_UNLIKELY( stream == client->request_stream ) ) {
     202           0 :     client->request_stream = NULL;
     203           0 :     *client->request_tx_op = (fd_h2_tx_op_t){0};
     204           0 :   }
     205             : 
     206             :   /* Remove stream from map */
     207           0 :   int map_idx = -1;
     208           0 :   for( uint i=0UL; i<(client->stream_cnt); i++ ) {
     209           0 :     if( client->stream_ids[ i ] == stream->s.stream_id ) {
     210           0 :       map_idx = (int)i;
     211           0 :     }
     212           0 :   }
     213           0 :   if( FD_UNLIKELY( map_idx<0 ) ) FD_LOG_CRIT(( "stream map corrupt" )); /* unreachable */
     214           0 :   if( (ulong)map_idx+1 < client->stream_cnt ) {
     215           0 :     client->stream_ids[ map_idx ] = client->stream_ids[ client->stream_cnt-1 ];
     216           0 :     client->streams   [ map_idx ] = client->streams   [ client->stream_cnt-1 ];
     217           0 :   }
     218           0 :   client->stream_cnt--;
     219           0 :   client->metrics->streams_active--;
     220             : 
     221           0 :   fd_grpc_h2_stream_pool_ele_release( client->stream_pool, stream );
     222           0 : }
     223             : 
     224             : void
     225           0 : fd_grpc_client_reset( fd_grpc_client_t * client ) {
     226           0 :   fd_h2_rbuf_init( client->frame_rx, client->frame_rx_buf, client->frame_rx_buf_max );
     227           0 :   fd_h2_rbuf_init( client->frame_tx, client->frame_tx_buf, client->frame_tx_buf_max );
     228           0 :   fd_h2_conn_init_client( client->conn );
     229           0 :   client->conn->ctx      = client;
     230           0 :   client->h2_hs_done     = 0;
     231           0 :   client->window_update_pending = 0;
     232           0 :   client->request_stream = NULL;
     233           0 :   fd_tlsrec_sock_init( client->tls_sock );
     234           0 :   *client->request_tx_op = (fd_h2_tx_op_t){0};
     235             : 
     236             :   /* Disable RX flow control */
     237           0 :   client->conn->self_settings.initial_window_size = (1U<<31)-1U;
     238           0 :   client->conn->rx_wnd_max   = (1U<<31)-1U;
     239           0 :   client->conn->rx_wnd_wmark = client->conn->rx_wnd_max - (1U<<20);
     240             : 
     241             :   /* Free all stream objects */
     242           0 :   while( client->stream_cnt ) {
     243           0 :     fd_grpc_h2_stream_t * stream = client->streams[ client->stream_cnt-1 ];
     244           0 :     fd_grpc_client_stream_release( client, stream );
     245           0 :   }
     246           0 : }
     247             : 
     248             : /* fd_grpc_client_send_stream_quota writes a WINDOW_UPDATE frame, which
     249             :    eventually allows the peer to send more data bytes. */
     250             : 
     251             : static void
     252             : fd_grpc_client_send_stream_quota( fd_h2_rbuf_t *        rbuf_tx,
     253             :                                   fd_grpc_h2_stream_t * stream,
     254           0 :                                   uint                  bump ) {
     255           0 :   fd_h2_window_update_t window_update = {
     256           0 :     .hdr = {
     257           0 :       .typlen      = fd_h2_frame_typlen( FD_H2_FRAME_TYPE_WINDOW_UPDATE, 4UL ),
     258           0 :       .r_stream_id = fd_uint_bswap( stream->s.stream_id )
     259           0 :     },
     260           0 :     .increment = fd_uint_bswap( bump )
     261           0 :   };
     262           0 :   fd_h2_rbuf_push( rbuf_tx, &window_update, sizeof(fd_h2_window_update_t) );
     263           0 :   stream->s.rx_wnd += bump;
     264           0 : }
     265             : 
     266             : /* fd_grpc_client_send_timeout is called when a stream timeout triggers.
     267             :    Writes a RST_STREAM frame unless the stream is closed or frame_tx is
     268             :    full, then calls back to the user.  The caller frees the stream. */
     269             : 
     270             : static void
     271             : fd_grpc_client_send_timeout( fd_h2_rbuf_t *        rbuf_tx,
     272             :                              fd_grpc_client_t *    client,
     273             :                              fd_grpc_h2_stream_t * stream,
     274           0 :                              int                   deadline_kind ) {
     275           0 :   if( fd_h2_rbuf_free_sz( rbuf_tx )>=sizeof(fd_h2_rst_stream_t) ) fd_h2_stream_error( &stream->s, client->conn, rbuf_tx, FD_H2_ERR_CANCEL );
     276           0 :   stream->rst_pending         = 1;
     277           0 :   stream->has_header_deadline = 0;
     278           0 :   stream->has_rx_end_deadline = 0;
     279           0 :   if( stream==client->request_stream ) {
     280           0 :     client->request_stream = NULL;
     281           0 :     *client->request_tx_op = (fd_h2_tx_op_t){0};
     282           0 :   }
     283           0 :   client->callbacks->rx_timeout( client->ctx, stream->request_ctx, deadline_kind );
     284           0 : }
     285             : 
     286             : long
     287           0 : fd_grpc_client_next_deadline( fd_grpc_client_t const * client ) {
     288           0 :   long deadline = LONG_MAX;
     289           0 :   for( ulong i=0UL; i<(client->stream_cnt); i++ ) {
     290           0 :     fd_grpc_h2_stream_t const * stream = client->streams[ i ];
     291           0 :     if( stream->rst_pending && fd_h2_rbuf_free_sz( client->frame_tx )>=sizeof(fd_h2_rst_stream_t) &&
     292           0 :         !( client->conn->flags & (FD_H2_CONN_FLAGS_DEAD|FD_H2_CONN_FLAGS_SEND_GOAWAY) ) ) return 0L; /* deferred RST_STREAM can go out now */
     293           0 :     if( stream->has_header_deadline ) deadline = fd_long_min( deadline, stream->header_deadline_nanos );
     294           0 :     if( stream->has_rx_end_deadline ) deadline = fd_long_min( deadline, stream->rx_end_deadline_nanos );
     295           0 :   }
     296           0 :   return deadline;
     297           0 : }
     298             : 
     299             : int
     300           0 : fd_grpc_client_tx_pending( fd_grpc_client_t const * client ) {
     301           0 :   return !!fd_h2_rbuf_used_sz( client->frame_tx );
     302           0 : }
     303             : 
     304             : int
     305           0 : fd_grpc_client_tls_rx_pending( fd_grpc_client_t const * client ) {
     306           0 :   return !!fd_tlsrec_sock_rx_avail( client->tls_sock );
     307           0 : }
     308             : 
     309             : int
     310           0 : fd_grpc_client_tls_tx_pending( fd_grpc_client_t const * client ) {
     311           0 :   return fd_tlsrec_sock_tx_pending( client->tls_sock );
     312           0 : }
     313             : 
     314             : ulong
     315           0 : fd_grpc_client_tx_starved( fd_grpc_client_t const * client ) {
     316           0 :   fd_grpc_h2_stream_t const * stream = client->request_stream;
     317           0 :   if( !stream || fd_uint_min( client->conn->tx_wnd, stream->s.tx_wnd ) ) return 0UL;
     318           0 :   return client->request_tx_op->chunk_sz;
     319           0 : }
     320             : 
     321             : void
     322             : fd_grpc_client_service_streams( fd_grpc_client_t * client,
     323           0 :                                 long               ts_nanos ) {
     324           0 :   ulong const meta_frame_max =
     325           0 :     fd_ulong_max( sizeof(fd_h2_window_update_t), sizeof(fd_h2_rst_stream_t) );
     326           0 :   fd_h2_conn_t * conn    = client->conn;
     327           0 :   fd_h2_rbuf_t * rbuf_tx = client->frame_tx;
     328           0 :   if( FD_UNLIKELY( conn->flags & (FD_H2_CONN_FLAGS_DEAD|FD_H2_CONN_FLAGS_SEND_GOAWAY) ) ) return;
     329           0 :   uint  const wnd_max    = conn->self_settings.initial_window_size;
     330           0 :   uint  const wnd_thres  = wnd_max / 2;
     331           0 :   for( ulong i=0UL; i<(client->stream_cnt); i++ ) {
     332           0 :     fd_grpc_h2_stream_t * stream = client->streams[ i ];
     333             : 
     334           0 :     if( FD_UNLIKELY( ( stream->has_header_deadline ) &
     335           0 :                      ( stream->header_deadline_nanos - ts_nanos <= 0L ) ) ) {
     336           0 :       fd_grpc_client_send_timeout( rbuf_tx, client, stream, FD_GRPC_DEADLINE_HEADER );
     337           0 :     }
     338             : 
     339           0 :     if( FD_UNLIKELY( ( stream->has_rx_end_deadline ) &
     340           0 :                      ( stream->rx_end_deadline_nanos - ts_nanos <= 0L ) ) ) {
     341           0 :       fd_grpc_client_send_timeout( rbuf_tx, client, stream, FD_GRPC_DEADLINE_RX_END );
     342           0 :     }
     343             : 
     344           0 :     if( FD_UNLIKELY( stream->rst_pending ) ) {
     345           0 :       if( fd_h2_rbuf_free_sz( rbuf_tx )<meta_frame_max && stream->s.state!=FD_H2_STREAM_STATE_CLOSED ) continue;
     346           0 :       fd_h2_stream_error( &stream->s, conn, rbuf_tx, FD_H2_ERR_CANCEL );
     347           0 :       fd_grpc_client_stream_release( client, stream );
     348           0 :       i--; /* stream removed */
     349           0 :       continue;
     350           0 :     }
     351             : 
     352           0 :     if( FD_UNLIKELY( stream->s.rx_wnd < wnd_thres && stream->s.state!=FD_H2_STREAM_STATE_CLOSED && fd_h2_rbuf_free_sz( rbuf_tx )>=meta_frame_max ) ) {
     353           0 :       uint const bump = wnd_max - stream->s.rx_wnd;
     354           0 :       fd_grpc_client_send_stream_quota( rbuf_tx, stream, bump );
     355           0 :     }
     356           0 :   }
     357           0 : }
     358             : 
     359             : #if FD_H2_HAS_SOCKETS
     360             : 
     361             : int
     362             : fd_grpc_client_tls_flush( fd_grpc_client_t * client,
     363           0 :                           int                sock_fd ) {
     364           0 :   int rc = fd_tlsrec_sock_flush( client->tls_sock, sock_fd );
     365           0 :   return rc<0 ? -1 : rc;
     366           0 : }
     367             : 
     368             : int
     369             : fd_grpc_client_rxtx_socket( fd_grpc_client_t * client,
     370             :                             int                sock_fd,
     371             :                             long               now,
     372           0 :                             int *              charge_busy ) {
     373           0 :   fd_h2_conn_t * conn = client->conn;
     374           0 :   ulong const frame_rx_lo_0 = client->frame_rx->lo_off;
     375           0 :   ulong const frame_rx_hi_0 = client->frame_rx->hi_off;
     376           0 :   ulong const frame_tx_lo_1 = client->frame_tx->lo_off;
     377           0 :   ulong const frame_tx_hi_1 = client->frame_tx->hi_off;
     378             : 
     379           0 :   int rx_err = fd_h2_rbuf_recvmsg( client->frame_rx, sock_fd, MSG_NOSIGNAL|MSG_DONTWAIT );
     380           0 :   if( FD_UNLIKELY( rx_err ) ) {
     381           0 :     FD_LOG_INFO(( "Disconnected: recvmsg error (%i-%s)", rx_err, fd_io_strerror( rx_err ) ));
     382           0 :     errno = rx_err;
     383           0 :     return -1;
     384           0 :   }
     385             : 
     386           0 :   if( FD_UNLIKELY( conn->flags ) ) fd_h2_tx_control( conn, client->frame_tx, &fd_grpc_client_h2_callbacks );
     387           0 :   fd_h2_rx( conn, client->frame_rx, client->frame_tx, client->frame_scratch, client->frame_scratch_max, &fd_grpc_client_h2_callbacks );
     388           0 :   if( FD_UNLIKELY( client->window_update_pending || client->request_stream ) ) {
     389           0 :     client->window_update_pending = 0;
     390           0 :     fd_grpc_client_request_continue( client ); /* credit or TX ring space may have freed */
     391           0 :   }
     392           0 :   fd_grpc_client_service_streams( client, now );
     393             : 
     394           0 :   int tx_err = fd_h2_rbuf_sendmsg( client->frame_tx, sock_fd, MSG_NOSIGNAL|MSG_DONTWAIT );
     395           0 :   if( FD_UNLIKELY( tx_err && tx_err!=EAGAIN ) ) {
     396           0 :     FD_LOG_WARNING(( "fd_h2_rbuf_sendmsg failed (%i-%s)", tx_err, fd_io_strerror( tx_err ) ));
     397           0 :     errno = tx_err;
     398           0 :     return -1;
     399           0 :   }
     400             : 
     401           0 :   ulong const frame_rx_lo_1 = client->frame_rx->lo_off;
     402           0 :   ulong const frame_rx_hi_1 = client->frame_rx->hi_off;
     403           0 :   ulong const frame_tx_lo_0 = client->frame_tx->lo_off;
     404           0 :   ulong const frame_tx_hi_0 = client->frame_tx->hi_off;
     405             : 
     406           0 :   client->metrics->stream_chunks_rx_bytes += frame_rx_hi_1 - frame_rx_hi_0;
     407           0 :   client->metrics->stream_chunks_tx_bytes += frame_tx_lo_0 - frame_tx_lo_1;
     408             : 
     409           0 :   if( frame_rx_lo_0!=frame_rx_lo_1 || frame_rx_hi_0!=frame_rx_hi_1 ||
     410           0 :       frame_tx_lo_0!=frame_tx_lo_1 || frame_tx_hi_0!=frame_tx_hi_1 ) {
     411           0 :     *charge_busy = 1;
     412           0 :   }
     413             : 
     414           0 :   return tx_err==EAGAIN ? 1 : 0;
     415           0 : }
     416             : 
     417             : int
     418             : fd_grpc_client_tx_flush_socket( fd_grpc_client_t * client,
     419           0 :                                 int                sock_fd ) {
     420           0 :   ulong const lo_0 = client->frame_tx->lo_off;
     421           0 :   int tx_err = fd_h2_rbuf_sendmsg( client->frame_tx, sock_fd, MSG_NOSIGNAL|MSG_DONTWAIT );
     422           0 :   if( FD_UNLIKELY( tx_err && tx_err!=EAGAIN ) ) {
     423           0 :     FD_LOG_WARNING(( "fd_h2_rbuf_sendmsg failed (%i-%s)", tx_err, fd_io_strerror( tx_err ) ));
     424           0 :     errno = tx_err;
     425           0 :     return -1;
     426           0 :   }
     427           0 :   client->metrics->stream_chunks_tx_bytes += client->frame_tx->lo_off - lo_0;
     428           0 :   return tx_err==EAGAIN ? 1 : 0;
     429           0 : }
     430             : 
     431             : int
     432             : fd_grpc_client_rxtx_tls( fd_grpc_client_t * client,
     433             :                          fd_tlsrec_conn_t * tls_conn,
     434             :                          int                sock_fd,
     435             :                          long               now,
     436           0 :                          int *              charge_busy ) {
     437           0 :   fd_h2_conn_t *     conn = client->conn;
     438           0 :   fd_tlsrec_sock_t * sock = client->tls_sock;
     439             : 
     440             :   /* Busy means progress was made.  A flush that moves nothing because
     441             :      the peer stopped reading is not progress: the caller would spin on
     442             :      send(2)/EAGAIN instead of waiting for the EPOLLOUT it armed. */
     443           0 :   ulong parked_0 = sock->tx_sz - sock->tx_off;
     444           0 :   if( FD_UNLIKELY( fd_tlsrec_sock_flush( sock, sock_fd )<0 ) ) return -1;
     445           0 :   if( parked_0 != sock->tx_sz - sock->tx_off ) *charge_busy = 1;
     446             : 
     447           0 :   ulong tcp_rx_sz;
     448           0 :   int rx_err = fd_tlsrec_sock_rx( sock, tls_conn, sock_fd, &tcp_rx_sz );
     449           0 :   if( FD_UNLIKELY( rx_err ) ) {
     450           0 :     if( rx_err==FD_TLSREC_SOCK_ERR_RECV || rx_err==FD_TLSREC_SOCK_ERR_SEND )
     451           0 :       FD_LOG_INFO(( "Disconnected: %s (%i-%s)", fd_tlsrec_sock_strerror( rx_err ), errno, fd_io_strerror( errno ) ));
     452           0 :     else
     453           0 :       FD_LOG_INFO(( "Disconnected: %s", fd_tlsrec_sock_strerror( rx_err ) ));
     454           0 :     return -1;
     455           0 :   }
     456           0 :   if( tcp_rx_sz ) *charge_busy = 1;
     457             : 
     458           0 :   if( FD_UNLIKELY( !fd_tlsrec_conn_is_ready( tls_conn ) ) ) {
     459           0 :     if( FD_UNLIKELY( fd_tlsrec_conn_is_failed( tls_conn ) ) ) {
     460           0 :       FD_LOG_WARNING(( "TLS handshake failed" ));
     461           0 :       return -1;
     462           0 :     }
     463           0 :     return 0;
     464           0 :   }
     465             : 
     466           0 :   if( FD_UNLIKELY( !client->h2_hs_done && !tls_conn->hs.cli.alpn_negotiated ) ) {
     467           0 :     FD_LOG_WARNING(( "TLS handshake failed: not a gRPC server (no h2 ALPN)" ));
     468           0 :     return -1;
     469           0 :   }
     470             : 
     471           0 :   ulong push_sz = fd_ulong_min( fd_tlsrec_sock_rx_avail( sock ),
     472           0 :                                 fd_h2_rbuf_free_sz( client->frame_rx ) );
     473           0 :   if( FD_LIKELY( push_sz ) ) {
     474           0 :     fd_h2_rbuf_push( client->frame_rx, fd_tlsrec_sock_rx_data( sock ), push_sz );
     475           0 :     fd_tlsrec_sock_rx_consume( sock, push_sz );
     476           0 :     client->metrics->stream_chunks_rx_bytes += push_sz;
     477           0 :     *charge_busy = 1;
     478           0 :   }
     479             : 
     480           0 :   if( FD_UNLIKELY( conn->flags ) ) fd_h2_tx_control( conn, client->frame_tx, &fd_grpc_client_h2_callbacks );
     481           0 :   fd_h2_rx( conn, client->frame_rx, client->frame_tx, client->frame_scratch, client->frame_scratch_max, &fd_grpc_client_h2_callbacks );
     482           0 :   if( FD_UNLIKELY( client->window_update_pending || client->request_stream ) ) {
     483           0 :     client->window_update_pending = 0;
     484           0 :     fd_grpc_client_request_continue( client ); /* credit or TX ring space may have freed */
     485           0 :   }
     486           0 :   fd_grpc_client_service_streams( client, now );
     487             : 
     488             :   /* HTTP/2 bytes wait behind parked ciphertext until EPOLLOUT */
     489           0 :   ulong tx_used = fd_h2_rbuf_used_sz( client->frame_tx );
     490           0 :   if( FD_LIKELY( tx_used && !fd_tlsrec_sock_tx_pending( sock ) ) ) {
     491           0 :     uchar plaintext[ FD_TLSREC_PLAINTEXT_MAX ];
     492           0 :     ulong pop_sz = fd_ulong_min( tx_used, sizeof(plaintext) );
     493           0 :     fd_h2_rbuf_pop_copy( client->frame_tx, plaintext, pop_sz );
     494             : 
     495           0 :     ulong consumed;
     496           0 :     int tx_err = fd_tlsrec_sock_tx( sock, tls_conn, sock_fd, plaintext, pop_sz, &consumed );
     497           0 :     if( FD_UNLIKELY( tx_err ) ) {
     498           0 :       FD_LOG_WARNING(( "fd_tlsrec_sock_tx failed: %s", fd_tlsrec_sock_strerror( tx_err ) ));
     499           0 :       return -1;
     500           0 :     }
     501           0 :     FD_CHECK_CRIT( consumed==pop_sz, "mismatched buffer sizes" );
     502             : 
     503           0 :     client->metrics->stream_chunks_tx_bytes += pop_sz;
     504           0 :     *charge_busy = 1;
     505           0 :   }
     506           0 :   return 0;
     507           0 : }
     508             : 
     509             : #endif /* FD_H2_HAS_SOCKETS */
     510             : 
     511             : /* fd_grpc_client_request continue attempts to write a request data
     512             :    frame. */
     513             : 
     514             : static int
     515           0 : fd_grpc_client_request_continue1( fd_grpc_client_t * client ) {
     516           0 :   fd_grpc_h2_stream_t * stream    = client->request_stream;
     517           0 :   fd_h2_stream_t *      h2_stream = &stream->s;
     518           0 :   fd_h2_tx_op_copy( client->conn, h2_stream, client->frame_tx, client->request_tx_op );
     519           0 :   if( FD_UNLIKELY( client->request_tx_op->chunk_sz ) ) return 0;
     520           0 :   client->request_stream = NULL;
     521           0 :   if( FD_UNLIKELY( h2_stream->state != FD_H2_STREAM_STATE_CLOSING_TX ) ) return 0;
     522           0 :   client->metrics->stream_chunks_tx_cnt++;
     523             :   /* Request finished */
     524           0 :   client->callbacks->tx_complete( client->ctx, stream->request_ctx );
     525           0 :   return 1;
     526           0 : }
     527             : 
     528             : static int
     529           0 : fd_grpc_client_request_continue( fd_grpc_client_t * client ) {
     530           0 :   if( FD_UNLIKELY( client->conn->flags & (FD_H2_CONN_FLAGS_DEAD|FD_H2_CONN_FLAGS_SEND_GOAWAY) ) ) return 0;
     531           0 :   if( FD_UNLIKELY( !client->request_stream ) ) return 0;
     532           0 :   if( FD_UNLIKELY( !client->request_tx_op->chunk_sz ) ) return 0;
     533           0 :   return fd_grpc_client_request_continue1( client );
     534           0 : }
     535             : 
     536             : int
     537           0 : fd_grpc_client_is_connected( fd_grpc_client_t * client ) {
     538           0 :   if( FD_UNLIKELY( !client                                          ) ) return 0;
     539           0 :   if( FD_UNLIKELY( client->conn->flags & FD_H2_CONN_FLAGS_DEAD      ) ) return 0;
     540           0 :   if( FD_UNLIKELY( !client->h2_hs_done                              ) ) return 0;
     541           0 :   return 1;
     542           0 : }
     543             : 
     544             : int
     545           0 : fd_grpc_client_request_is_blocked( fd_grpc_client_t * client ) {
     546           0 :   if( FD_UNLIKELY( !client                                          ) ) return 1;
     547           0 :   if( FD_UNLIKELY( client->conn->flags & FD_H2_CONN_FLAGS_DEAD      ) ) return 1;
     548           0 :   if( FD_UNLIKELY( !client->h2_hs_done                              ) ) return 1;
     549           0 :   if( FD_UNLIKELY( !fd_h2_rbuf_is_empty( client->frame_tx )         ) ) return 1;
     550           0 :   if( FD_UNLIKELY( client->request_tx_op->chunk_sz > 0UL            ) ) return 1;
     551           0 :   if( FD_UNLIKELY( !fd_grpc_client_stream_acquire_is_safe( client ) ) ) return 1;
     552           0 :   return 0;
     553           0 : }
     554             : 
     555             : int
     556           0 : fd_grpc_client_stream_send_is_blocked( fd_grpc_client_t * client ) {
     557           0 :   if( FD_UNLIKELY( !client                                     ) ) return 1;
     558           0 :   if( FD_UNLIKELY( client->conn->flags & FD_H2_CONN_FLAGS_DEAD ) ) return 1;
     559           0 :   if( FD_UNLIKELY( !client->h2_hs_done                         ) ) return 1;
     560           0 :   if( FD_UNLIKELY( !fd_h2_rbuf_is_empty( client->frame_tx )    ) ) return 1;
     561           0 :   if( FD_UNLIKELY( client->request_tx_op->chunk_sz > 0UL       ) ) return 1;
     562           0 :   return 0;
     563           0 : }
     564             : 
     565             : int
     566           0 : fd_grpc_client_request_stream_busy( fd_grpc_client_t * client ) {
     567           0 :   return client->request_stream && client->request_tx_op->chunk_sz;
     568           0 : }
     569             : 
     570             : fd_grpc_h2_stream_t *
     571             : fd_grpc_client_request_start(
     572             :     fd_grpc_client_t *   client,
     573             :     char const *         path,
     574             :     ulong                path_len,
     575             :     ulong                request_ctx,
     576             :     uchar const *        protobuf,
     577             :     ulong                protobuf_sz,
     578             :     char const *         auth_token,
     579             :     ulong                auth_token_sz,
     580             :     int                  is_streaming
     581           0 : ) {
     582           0 :   if( FD_UNLIKELY( fd_grpc_client_request_is_blocked( client ) ) ) return NULL;
     583             : 
     584           0 :   int const headers_only = (protobuf==NULL);
     585           0 :   if( FD_UNLIKELY( headers_only && !is_streaming ) ) {
     586           0 :     FD_LOG_WARNING(( "headers-only request requires is_streaming (path %.*s). This is a bug", (int)path_len, path ));
     587           0 :     return NULL;
     588           0 :   }
     589             : 
     590             :   /* Validate protobuf size */
     591           0 :   FD_TEST( client->pb_tx_max > sizeof(fd_grpc_hdr_t) );
     592           0 :   ulong const max_proto_sz = client->pb_tx_max - sizeof(fd_grpc_hdr_t);
     593           0 :   if( FD_UNLIKELY( protobuf_sz > max_proto_sz ) ) {
     594           0 :     FD_LOG_WARNING(( "Protobuf message too large (%lu bytes) for path (%.*s). Max size is %lu bytes", protobuf_sz, (int)path_len, path, max_proto_sz ));
     595           0 :     return NULL;
     596           0 :   }
     597             : 
     598           0 :   if( FD_LIKELY( !headers_only ) ) {
     599             :     /* Copy protobuf to buffer after gRPC header */
     600           0 :     uchar * proto_buf = client->pb_tx + sizeof(fd_grpc_hdr_t);
     601           0 :     memcpy( proto_buf, protobuf, protobuf_sz );
     602             : 
     603             :     /* Create gRPC length prefix */
     604           0 :     fd_grpc_hdr_t hdr = {
     605           0 :       .compressed=0,
     606           0 :       .msg_sz=fd_uint_bswap( (uint)protobuf_sz )
     607           0 :     };
     608           0 :     memcpy( client->pb_tx, &hdr, sizeof(fd_grpc_hdr_t) );
     609           0 :   }
     610           0 :   ulong const payload_sz = protobuf_sz + sizeof(fd_grpc_hdr_t);
     611             : 
     612             :   /* Allocate stream descriptor */
     613           0 :   fd_grpc_h2_stream_t * stream    = fd_grpc_client_stream_acquire( client, request_ctx );
     614           0 :   uint const            stream_id = stream->s.stream_id;
     615             : 
     616             :   /* Write HTTP/2 request headers */
     617           0 :   fd_h2_tx_prepare( client->conn, client->frame_tx, FD_H2_FRAME_TYPE_HEADERS, FD_H2_FLAG_END_HEADERS, stream_id );
     618           0 :   fd_grpc_req_hdrs_t req_meta = {
     619           0 :     .host     = client->host,
     620           0 :     .host_len = client->host_len,
     621           0 :     .port     = client->port,
     622           0 :     .path     = path,
     623           0 :     .path_len = path_len,
     624           0 :     .https    = client->https,
     625             : 
     626           0 :     .bearer_auth     = auth_token,
     627           0 :     .bearer_auth_len = auth_token_sz
     628           0 :   };
     629           0 :   if( FD_UNLIKELY( !fd_grpc_h2_gen_request_hdrs(
     630           0 :       &req_meta,
     631           0 :       client->frame_tx,
     632           0 :       client->version,
     633           0 :       client->version_len
     634           0 :   ) ) ) {
     635           0 :     FD_LOG_WARNING(( "Failed to generate gRPC request headers (%.*s). This is a bug", (int)path_len, path ));
     636           0 :     fd_grpc_client_stream_release( client, stream );
     637           0 :     return NULL;
     638           0 :   }
     639           0 :   fd_h2_tx_commit( client->conn, client->frame_tx );
     640             : 
     641           0 :   if( FD_UNLIKELY( headers_only ) ) {
     642           0 :     client->request_stream = NULL;
     643           0 :     return stream;
     644           0 :   }
     645             : 
     646             :   /* Queue request payload for send
     647             :      (Protobuf message might have to be fragmented into multiple HTTP/2
     648             :      DATA frames if the client gets blocked)
     649             :      For streaming requests, don't set END_STREAM flag yet */
     650           0 :   uint flags = is_streaming ? 0U : FD_H2_FLAG_END_STREAM;
     651           0 :   fd_h2_tx_op_init( client->request_tx_op, client->pb_tx, payload_sz, flags );
     652           0 :   fd_grpc_client_request_continue1( client );
     653           0 :   client->metrics->requests_sent++;
     654             : 
     655           0 :   FD_LOG_DEBUG(( "gRPC request path=%.*s sz=%lu streaming=%d", (int)path_len, path, protobuf_sz, is_streaming ));
     656             : 
     657           0 :   return stream;
     658           0 : }
     659             : 
     660             : int
     661             : fd_grpc_client_stream_send_msg(
     662             :     fd_grpc_client_t *    client,
     663             :     fd_grpc_h2_stream_t * stream,
     664             :     uchar const *         protobuf,
     665             :     ulong                 protobuf_sz
     666           0 : ) {
     667           0 :   if( FD_UNLIKELY( !client || !stream ) ) return 0;
     668           0 :   if( FD_UNLIKELY( client->conn->flags & FD_H2_CONN_FLAGS_DEAD ) ) return 0;
     669           0 :   if( FD_UNLIKELY( !fd_h2_rbuf_is_empty( client->frame_tx ) ) ) return 0;
     670           0 :   if( FD_UNLIKELY( client->request_tx_op->chunk_sz > 0UL ) ) return 0;
     671           0 :   if( FD_UNLIKELY( client->request_stream != NULL && client->request_stream != stream ) ) return 0; /* Another stream has a request in progress */
     672           0 :   if( FD_UNLIKELY( stream->s.state!=FD_H2_STREAM_STATE_OPEN &&
     673           0 :                    stream->s.state!=FD_H2_STREAM_STATE_CLOSING_RX ) ) return 0;
     674             : 
     675             :   /* Validate protobuf size */
     676           0 :   FD_TEST( client->pb_tx_max > sizeof(fd_grpc_hdr_t) );
     677           0 :   ulong const max_proto_sz = client->pb_tx_max - sizeof(fd_grpc_hdr_t);
     678           0 :   if( FD_UNLIKELY( protobuf_sz > max_proto_sz ) ) {
     679           0 :     FD_LOG_WARNING(( "Protobuf message too large (%lu bytes) for stream_id=%u. Max size is %lu bytes", protobuf_sz, stream->s.stream_id, max_proto_sz ));
     680           0 :     return 0;
     681           0 :   }
     682             : 
     683             :   /* Copy protobuf to buffer after gRPC header */
     684           0 :   uchar * proto_buf = client->pb_tx + sizeof(fd_grpc_hdr_t);
     685           0 :   if( protobuf!=proto_buf ) memcpy( proto_buf, protobuf, protobuf_sz );
     686             : 
     687             :   /* Create gRPC length prefix */
     688           0 :   fd_grpc_hdr_t hdr = {
     689           0 :     .compressed=0,
     690           0 :     .msg_sz=fd_uint_bswap( (uint)protobuf_sz )
     691           0 :   };
     692           0 :   memcpy( client->pb_tx, &hdr, sizeof(fd_grpc_hdr_t) );
     693           0 :   ulong const payload_sz = protobuf_sz + sizeof(fd_grpc_hdr_t);
     694             : 
     695             :   /* Queue message payload for send (without END_STREAM flag) */
     696           0 :   client->request_stream = stream;
     697           0 :   fd_h2_tx_op_init( client->request_tx_op, client->pb_tx, payload_sz, 0U );
     698           0 :   fd_grpc_client_request_continue1( client );
     699             : 
     700           0 :   FD_LOG_DEBUG(( "gRPC stream_send_msg stream_id=%u sz=%lu", stream->s.stream_id, protobuf_sz ));
     701             : 
     702           0 :   return 1;
     703           0 : }
     704             : 
     705             : int
     706             : fd_grpc_client_stream_close(
     707             :     fd_grpc_client_t *    client,
     708             :     fd_grpc_h2_stream_t * stream
     709           0 : ) {
     710           0 :   if( FD_UNLIKELY( !client || !stream ) ) return 0;
     711           0 :   if( FD_UNLIKELY( client->conn->flags & FD_H2_CONN_FLAGS_DEAD ) ) return 0;
     712           0 :   if( FD_UNLIKELY( !fd_h2_rbuf_is_empty( client->frame_tx ) ) ) return 0;
     713           0 :   if( FD_UNLIKELY( client->request_stream != NULL ) ) return 0; /* Another request in progress */
     714           0 :   if( FD_UNLIKELY( stream->s.state!=FD_H2_STREAM_STATE_OPEN &&
     715           0 :                    stream->s.state!=FD_H2_STREAM_STATE_CLOSING_RX ) ) return 0;
     716             : 
     717             :   /* Send empty DATA frame with END_STREAM flag to close the stream */
     718           0 :   fd_h2_stream_close_tx( &stream->s, client->conn );
     719           0 :   fd_h2_tx_prepare( client->conn, client->frame_tx, FD_H2_FRAME_TYPE_DATA, FD_H2_FLAG_END_STREAM, stream->s.stream_id );
     720           0 :   fd_h2_tx_commit( client->conn, client->frame_tx );
     721             : 
     722           0 :   FD_LOG_DEBUG(( "gRPC stream_close stream_id=%u", stream->s.stream_id ));
     723             : 
     724           0 :   return 1;
     725           0 : }
     726             : 
     727             : int
     728             : fd_grpc_client_stream_cancel( fd_grpc_client_t *    client,
     729           0 :                               fd_grpc_h2_stream_t * stream ) {
     730           0 :   if( FD_UNLIKELY( fd_h2_rbuf_free_sz( client->frame_tx )<sizeof(fd_h2_rst_stream_t) ) ) return 0;
     731           0 :   fd_h2_stream_error( &stream->s, client->conn, client->frame_tx, FD_H2_ERR_CANCEL );
     732           0 :   fd_grpc_client_stream_release( client, stream );
     733           0 :   return 1;
     734           0 : }
     735             : 
     736             : void
     737             : fd_grpc_client_deadline_set( fd_grpc_h2_stream_t * stream,
     738             :                              int                   deadline_kind,
     739           0 :                              long                  ts_nanos ) {
     740           0 :   switch( deadline_kind ) {
     741           0 :   case FD_GRPC_DEADLINE_HEADER:
     742           0 :     stream->header_deadline_nanos = ts_nanos;
     743           0 :     stream->has_header_deadline   = 1;
     744           0 :     break;
     745           0 :   case FD_GRPC_DEADLINE_RX_END:
     746           0 :     stream->rx_end_deadline_nanos = ts_nanos;
     747           0 :     stream->has_rx_end_deadline   = 1;
     748           0 :     break;
     749           0 :   }
     750           0 : }
     751             : 
     752             : /* Lookup stream by ID */
     753             : 
     754             : static fd_h2_stream_t *
     755             : fd_grpc_h2_stream_query( fd_h2_conn_t * conn,
     756           0 :                          uint           stream_id ) {
     757           0 :   fd_grpc_client_t * client = conn->ctx;
     758           0 :   for( ulong i=0UL; i<client->stream_cnt; i++ ) {
     759           0 :     if( client->stream_ids[ i ] == stream_id ) {
     760           0 :       return &client->streams[ i ]->s;
     761           0 :     }
     762           0 :   }
     763           0 :   return NULL;
     764           0 : }
     765             : 
     766             : static void
     767           0 : fd_grpc_h2_conn_established( fd_h2_conn_t * conn ) {
     768           0 :   fd_grpc_client_t * client = conn->ctx;
     769           0 :   client->h2_hs_done = 1;
     770           0 :   client->callbacks->conn_established( client->ctx );
     771           0 : }
     772             : 
     773             : static void
     774             : fd_grpc_h2_conn_final( fd_h2_conn_t * conn,
     775             :                        uint           h2_err,
     776           0 :                        int            closed_by ) {
     777           0 :   fd_grpc_client_t * client = conn->ctx;
     778           0 :   client->callbacks->conn_dead( client->ctx, h2_err, closed_by );
     779           0 : }
     780             : 
     781             : /* React to response data */
     782             : 
     783             : void
     784             : fd_grpc_h2_cb_headers(
     785             :     fd_h2_conn_t *   conn,
     786             :     fd_h2_stream_t * h2_stream,
     787             :     void const *     data,
     788             :     ulong            data_sz,
     789             :     ulong            flags
     790           0 : ) {
     791           0 :   fd_grpc_h2_stream_t * stream = fd_grpc_h2_stream_upcast( h2_stream );
     792           0 :   fd_grpc_client_t * client = conn->ctx;
     793             : 
     794           0 :   int h2_status = fd_grpc_h2_read_response_hdrs( &stream->hdrs, client->matcher, data, data_sz );
     795           0 :   if( FD_UNLIKELY( h2_status!=FD_H2_SUCCESS ) ) {
     796             :     /* Failed to parse HTTP/2 headers */
     797           0 :     fd_h2_stream_error( h2_stream, conn, client->frame_tx, FD_H2_ERR_PROTOCOL );
     798           0 :     if( FD_LIKELY( !stream->rst_pending ) ) client->callbacks->rx_end( client->ctx, stream->request_ctx, &stream->hdrs ); /* invalidates stream->hdrs */
     799           0 :     fd_grpc_client_stream_release( client, stream );
     800           0 :     return;
     801           0 :   }
     802           0 :   if( FD_UNLIKELY( stream->rst_pending ) ) return; /* timed out */
     803             : 
     804           0 :   if( !( flags & FD_H2_FLAG_END_HEADERS ) ) return;
     805             :   /* END_STREAM may have arrived earlier in the block.  Reset before
     806             :      callbacks consume the TX space reserved by fd_h2_rx. */
     807           0 :   int const rx_end = ( flags & FD_H2_FLAG_END_STREAM ) ||
     808           0 :                     h2_stream->state==FD_H2_STREAM_STATE_CLOSING_RX || h2_stream->state==FD_H2_STREAM_STATE_CLOSED;
     809           0 :   if( rx_end ) fd_h2_stream_error( h2_stream, conn, client->frame_tx, FD_H2_SUCCESS );
     810             : 
     811           0 :   if( !stream->hdrs_received ) {
     812           0 :     if( FD_UNLIKELY( stream->hdrs.h2_status>=200U &&
     813           0 :                     ( stream->hdrs.h2_status!=200U || !stream->hdrs.is_grpc_proto ) ) ) {
     814           0 :       if( !rx_end ) fd_h2_stream_error( h2_stream, conn, client->frame_tx, FD_H2_ERR_CANCEL );
     815           0 :       client->callbacks->rx_end( client->ctx, stream->request_ctx, &stream->hdrs );
     816           0 :       fd_grpc_client_stream_release( client, stream );
     817           0 :       return;
     818           0 :     }
     819             :     /* Got initial response header */
     820           0 :     stream->hdrs_received = 1;
     821           0 :     stream->has_header_deadline = 0;
     822           0 :     if( FD_LIKELY( ( stream->hdrs.h2_status==200  ) &
     823           0 :                    ( !!stream->hdrs.is_grpc_proto ) ) ) {
     824           0 :       client->callbacks->rx_start( client->ctx, stream->request_ctx );
     825           0 :     }
     826           0 :   }
     827             : 
     828           0 :   if( rx_end ) {
     829           0 :     client->callbacks->rx_end( client->ctx, stream->request_ctx, &stream->hdrs );
     830           0 :     fd_grpc_client_stream_release( client, stream );
     831           0 :     return;
     832           0 :   }
     833           0 : }
     834             : 
     835             : void
     836             : fd_grpc_h2_cb_data(
     837             :     fd_h2_conn_t *   conn,
     838             :     fd_h2_stream_t * h2_stream,
     839             :     void const *     data,
     840             :     ulong            data_sz,
     841             :     ulong            flags
     842           0 : ) {
     843           0 :   fd_grpc_client_t *    client = conn->ctx;
     844           0 :   fd_grpc_h2_stream_t * stream = fd_grpc_h2_stream_upcast( h2_stream );
     845           0 :   if( FD_UNLIKELY( stream->rst_pending ) ) return; /* timed out */
     846           0 :   if( FD_UNLIKELY( ( stream->hdrs.h2_status!=200 ) |
     847           0 :                    ( !stream->hdrs.is_grpc_proto ) ) ) {
     848           0 :     goto check_end_stream;
     849           0 :   }
     850             : 
     851           0 :   do {
     852             : 
     853             :     /* Read header bytes */
     854           0 :     if( stream->msg_buf_used < sizeof(fd_grpc_hdr_t) ) {
     855           0 :       ulong hdr_frag_sz = fd_ulong_min( sizeof(fd_grpc_hdr_t) - stream->msg_buf_used, data_sz );
     856           0 :       fd_memcpy( stream->msg_buf + stream->msg_buf_used, data, hdr_frag_sz );
     857           0 :       stream->msg_buf_used += hdr_frag_sz;
     858           0 :       data     = (void const *)( (ulong)data + (ulong)hdr_frag_sz );
     859           0 :       data_sz -= hdr_frag_sz;
     860           0 :       if( FD_UNLIKELY( stream->msg_buf_used < sizeof(fd_grpc_hdr_t) ) ) goto check_end_stream;
     861             : 
     862             :       /* Header complete.  Message compression is not supported. */
     863           0 :       if( FD_UNLIKELY( stream->msg_buf[0] ) ) {
     864           0 :         stream->hdrs.grpc_status = FD_GRPC_STATUS_INTERNAL;
     865           0 :         fd_h2_stream_error( h2_stream, conn, client->frame_tx, FD_H2_ERR_INTERNAL );
     866           0 :         client->callbacks->rx_end( client->ctx, stream->request_ctx, &stream->hdrs );
     867           0 :         fd_grpc_client_stream_release( client, stream );
     868           0 :         return;
     869           0 :       }
     870           0 :       stream->msg_sz = fd_uint_bswap( FD_LOAD( uint, (void *)( (ulong)stream->msg_buf+1 ) ) );
     871           0 :       if( FD_UNLIKELY( sizeof(fd_grpc_hdr_t)  + stream->msg_sz > stream->msg_buf_max ) ) {
     872           0 :         FD_LOG_WARNING(( "Received oversized gRPC message (%lu bytes), killing request", stream->msg_sz ));
     873           0 :         fd_h2_stream_error( h2_stream, conn, client->frame_tx, FD_H2_ERR_INTERNAL );
     874           0 :         client->callbacks->rx_end( client->ctx, stream->request_ctx, &stream->hdrs );
     875           0 :         fd_grpc_client_stream_release( client, stream );
     876           0 :         return;
     877           0 :       }
     878           0 :     }
     879             : 
     880             :     /* Read payload bytes */
     881           0 :     ulong wmark    = sizeof(fd_grpc_hdr_t) + stream->msg_sz;
     882           0 :     ulong chunk_sz = fd_ulong_min( stream->msg_buf_used+data_sz, wmark ) - stream->msg_buf_used;
     883           0 :     if( FD_UNLIKELY( chunk_sz>data_sz ) ) FD_LOG_CRIT(( "integer underflow" )); /* unreachable */
     884           0 :     fd_memcpy( stream->msg_buf + stream->msg_buf_used, data, chunk_sz );
     885           0 :     stream->msg_buf_used += chunk_sz;
     886           0 :     data     = (void const *)( (ulong)data + (ulong)chunk_sz );
     887           0 :     data_sz -= chunk_sz;
     888             : 
     889           0 :     client->metrics->stream_chunks_rx_cnt++;
     890             : 
     891           0 :     if( stream->msg_buf_used >= wmark ) {
     892             :       /* Data complete */
     893           0 :       void const * msg_ptr = stream->msg_buf + sizeof(fd_grpc_hdr_t);
     894             :       /* The first wrapped callback has no END_STREAM flag, but the H2
     895             :          state already reflects the end of the carrying DATA frame. */
     896           0 :       if( FD_UNLIKELY( h2_stream->state==FD_H2_STREAM_STATE_CLOSING_RX ) ) fd_h2_stream_error( h2_stream, conn, client->frame_tx, FD_H2_SUCCESS );
     897           0 :       client->callbacks->rx_msg( client->ctx, msg_ptr, stream->msg_sz, stream->request_ctx );
     898           0 :       stream->msg_buf_used = 0UL;
     899           0 :       stream->msg_sz       = 0UL;
     900           0 :     }
     901             : 
     902           0 :   } while( data_sz );
     903             : 
     904             : 
     905             :   /* Check whether the server might want to end the stream in a DATA frame so no stream is leaked.
     906             :      This shouldn't happen in gRPC as server responses should indicate end of stream in headers */
     907           0 :   check_end_stream:
     908           0 :   if( flags & FD_H2_FLAG_END_STREAM ) {
     909             :     /* FIXME incomplete gRPC message */
     910           0 :     if( FD_UNLIKELY( stream->msg_buf_used ) ) {
     911           0 :       FD_LOG_WARNING(( "Received incomplete gRPC message" ));
     912           0 :     }
     913           0 :     fd_h2_stream_error( h2_stream, conn, client->frame_tx, FD_H2_SUCCESS );
     914           0 :     client->callbacks->rx_end( client->ctx, stream->request_ctx, &stream->hdrs );
     915           0 :     fd_grpc_client_stream_release( client, stream );
     916           0 :   }
     917           0 : }
     918             : 
     919             : /* Server might kill our request */
     920             : 
     921             : static void
     922             : fd_grpc_h2_rst_stream( fd_h2_conn_t *   conn,
     923             :                        fd_h2_stream_t * h2_stream,
     924             :                        uint             error_code,
     925           0 :                        int              closed_by ) {
     926           0 :   fd_grpc_client_t * client = conn->ctx;
     927           0 :   if( closed_by==1 ) {
     928           0 :     FD_LOG_WARNING(( "server %s%.*s%s terminated request %s(%u-%s)%s",
     929           0 :                      fd_log_style_bold(), (int)client->host_len, client->host, fd_log_style_normal(),
     930           0 :                      fd_log_style_dim(), error_code, fd_h2_strerror( error_code ), fd_log_style_normal() ));
     931           0 :   } else {
     932           0 :     FD_LOG_WARNING(( "request to server %s%.*s%s failed %s(%u-%s)%s",
     933           0 :                      fd_log_style_bold(), (int)client->host_len, client->host, fd_log_style_normal(),
     934           0 :                      fd_log_style_dim(), error_code, fd_h2_strerror( error_code ), fd_log_style_normal() ));
     935           0 :   }
     936           0 :   fd_grpc_h2_stream_t * stream = fd_grpc_h2_stream_upcast( h2_stream );
     937           0 :   if( FD_LIKELY( !stream->rst_pending ) ) client->callbacks->rx_end( client->ctx, stream->request_ctx, &stream->hdrs ); /* invalidates stream->hdrs */
     938           0 :   fd_grpc_client_stream_release( client, stream );
     939           0 : }
     940             : 
     941             : /* A HTTP/2 flow control change might unblock a queued request send op */
     942             : 
     943             : void
     944             : fd_grpc_h2_window_update( fd_h2_conn_t * conn,
     945           0 :                           uint           increment ) {
     946           0 :   (void)increment;
     947             :   /* Defer tx resumption to after fd_h2_rx: filling frame_tx mid-rx can
     948             :      starve control-frame responses (SETTINGS ACK, PONG). */
     949           0 :   ((fd_grpc_client_t *)conn->ctx)->window_update_pending = 1;
     950           0 : }
     951             : 
     952             : void
     953             : fd_grpc_h2_stream_window_update( fd_h2_conn_t *   conn,
     954             :                                  fd_h2_stream_t * stream,
     955           0 :                                  uint             increment ) {
     956           0 :   (void)increment;
     957             :   /* Repay initial-window-shrink debt before the new credit counts. */
     958           0 :   fd_grpc_h2_stream_t * st = fd_grpc_h2_stream_upcast( stream );
     959           0 :   long repay = fd_long_min( st->tx_wnd_debt, (long)stream->tx_wnd );
     960           0 :   if( FD_UNLIKELY( repay>0L ) ) {
     961           0 :     stream->tx_wnd  -= (uint)repay;
     962           0 :     st->tx_wnd_debt -= repay;
     963           0 :   }
     964           0 :   ((fd_grpc_client_t *)conn->ctx)->window_update_pending = 1;
     965           0 : }
     966             : 
     967             : void
     968             : fd_grpc_h2_initial_window_update( fd_h2_conn_t * conn,
     969           0 :                                   long           delta ) {
     970           0 :   fd_grpc_client_t * client = conn->ctx;
     971           0 :   for( uint i=0U; i<client->stream_cnt; i++ ) {
     972           0 :     fd_grpc_h2_stream_t * stream = client->streams[ i ];
     973           0 :     fd_h2_stream_t *      s      = &stream->s;
     974             :     /* The effective window can go negative on a shrink below consumed
     975             :        credit (RFC 9113 section 6.9.2).  tx_wnd is unsigned, so carry
     976             :        the deficit in tx_wnd_debt; WINDOW_UPDATEs repay it before
     977             :        granting new credit. */
     978           0 :     long wnd = (long)s->tx_wnd - stream->tx_wnd_debt + delta;
     979           0 :     if( FD_UNLIKELY( wnd > 0x7fffffffL ) ) {
     980           0 :       fd_h2_conn_error( conn, FD_H2_ERR_FLOW_CONTROL );
     981           0 :       return;
     982           0 :     }
     983           0 :     s->tx_wnd           = (uint)fd_long_max( wnd, 0L );
     984           0 :     stream->tx_wnd_debt =       fd_long_max( -wnd, 0L );
     985           0 :   }
     986             :   /* Do not continue the tx op here: this runs inside fd_h2_rx SETTINGS
     987             :      processing, and filling frame_tx now can starve the SETTINGS ACK
     988             :      push (conn death via FD_H2_ERR_INTERNAL).  Defer to after rx. */
     989           0 :   if( FD_UNLIKELY( delta>0L ) ) client->window_update_pending = 1;
     990           0 : }
     991             : 
     992             : void
     993           0 : fd_grpc_h2_ping_ack( fd_h2_conn_t * conn ) {
     994           0 :   fd_grpc_client_t * client = conn->ctx;
     995           0 :   client->callbacks->ping_ack( client->ctx );
     996           0 : }
     997             : 
     998             : fd_h2_rbuf_t *
     999           0 : fd_grpc_client_rbuf_tx( fd_grpc_client_t * client ) {
    1000           0 :   return client->frame_tx;
    1001           0 : }
    1002             : 
    1003             : fd_h2_rbuf_t *
    1004           0 : fd_grpc_client_rbuf_rx( fd_grpc_client_t * client ) {
    1005           0 :   return client->frame_rx;
    1006           0 : }
    1007             : 
    1008             : fd_h2_conn_t *
    1009           0 : fd_grpc_client_h2_conn( fd_grpc_client_t * client ) {
    1010           0 :   return client->conn;
    1011           0 : }
    1012             : 
    1013             : /* fd_grpc_client_h2_callbacks specifies h2->grpc_client callbacks.
    1014             :    Stored in .rodata for security.  Must be kept in sync with fd_h2 to
    1015             :    avoid NULL pointers. */
    1016             : 
    1017             : fd_h2_callbacks_t const fd_grpc_client_h2_callbacks = {
    1018             :   .stream_create         = fd_h2_noop_stream_create,
    1019             :   .stream_query          = fd_grpc_h2_stream_query,
    1020             :   .conn_established      = fd_grpc_h2_conn_established,
    1021             :   .conn_final            = fd_grpc_h2_conn_final,
    1022             :   .headers               = fd_grpc_h2_cb_headers,
    1023             :   .data                  = fd_grpc_h2_cb_data,
    1024             :   .rst_stream            = fd_grpc_h2_rst_stream,
    1025             :   .window_update         = fd_grpc_h2_window_update,
    1026             :   .stream_window_update  = fd_grpc_h2_stream_window_update,
    1027             :   .initial_window_update = fd_grpc_h2_initial_window_update,
    1028             :   .ping_ack              = fd_grpc_h2_ping_ack,
    1029             : };

Generated by: LCOV version 1.14