LCOV - code coverage report
Current view: top level - waltz/h2 - fd_h2_conn.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 481 599 80.3 %
Date: 2026-11-12 04:25:03 Functions: 20 22 90.9 %

          Line data    Source code
       1             : #include "fd_h2_conn.h"
       2             : #include "fd_h2_callback.h"
       3             : #include "fd_h2_proto.h"
       4             : #include "fd_h2_rbuf.h"
       5             : #include "fd_h2_stream.h"
       6             : #include <float.h>
       7             : 
       8             : #if FD_USING_GCC && __GNUC__ >= 15
       9             : #pragma GCC diagnostic ignored "-Wunterminated-string-initialization"
      10             : #endif
      11             : 
      12             : char const fd_h2_client_preface[24] = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
      13             : 
      14             : static fd_h2_settings_t const fd_h2_settings_initial = {
      15             :   .max_concurrent_streams = UINT_MAX,
      16             :   .initial_window_size    = 65535U,
      17             :   .max_frame_size         = 16384U,
      18             :   .max_header_list_size   = UINT_MAX
      19             : };
      20             : 
      21             : static void
      22         480 : fd_h2_conn_init_window( fd_h2_conn_t * conn ) {
      23         480 :   conn->rx_wnd_max   = 65535U;
      24         480 :   conn->rx_wnd       = conn->rx_wnd_max;
      25         480 :   conn->rx_wnd_wmark = (uint)( 0.7f * (float)conn->rx_wnd_max );
      26         480 :   conn->tx_wnd       = 65535U;
      27         480 : }
      28             : 
      29             : fd_h2_conn_t *
      30         438 : fd_h2_conn_init_client( fd_h2_conn_t * conn ) {
      31         438 :   *conn = (fd_h2_conn_t) {
      32         438 :     .self_settings  = fd_h2_settings_initial,
      33         438 :     .peer_settings  = fd_h2_settings_initial,
      34         438 :     .flags          = FD_H2_CONN_FLAGS_CLIENT_INITIAL,
      35         438 :     .tx_stream_next = 1U,
      36         438 :     .rx_stream_next = 2U
      37         438 :   };
      38         438 :   fd_h2_conn_init_window( conn );
      39         438 :   return conn;
      40         438 : }
      41             : 
      42             : fd_h2_conn_t *
      43          42 : fd_h2_conn_init_server( fd_h2_conn_t * conn ) {
      44          42 :   *conn = (fd_h2_conn_t) {
      45          42 :     .self_settings  = fd_h2_settings_initial,
      46          42 :     .peer_settings  = fd_h2_settings_initial,
      47          42 :     .flags          = FD_H2_CONN_FLAGS_SERVER_INITIAL,
      48          42 :     .tx_stream_next = 2U,
      49          42 :     .rx_stream_next = 1U
      50          42 :   };
      51          42 :   fd_h2_conn_init_window( conn );
      52          42 :   return conn;
      53          42 : }
      54             : 
      55             : static void
      56             : fd_h2_setting_encode( uchar * buf,
      57             :                       ushort  setting_id,
      58         198 :                       uint    setting_value ) {
      59         198 :   FD_STORE( ushort, buf,   fd_ushort_bswap( setting_id    ) );
      60         198 :   FD_STORE( uint,   buf+2, fd_uint_bswap  ( setting_value ) );
      61         198 : }
      62             : 
      63             : #define FD_H2_OUR_SETTINGS_ENCODED_SZ 45
      64             : 
      65             : static void
      66             : fd_h2_gen_settings( fd_h2_settings_t const * settings,
      67          33 :                     uchar                    buf[ FD_H2_OUR_SETTINGS_ENCODED_SZ ] ) {
      68          33 :   fd_h2_frame_hdr_t hdr = {
      69          33 :     .typlen = fd_h2_frame_typlen( FD_H2_FRAME_TYPE_SETTINGS, 36UL ),
      70          33 :   };
      71          33 :   fd_memcpy( buf, &hdr, 9UL );
      72             : 
      73          33 :   fd_h2_setting_encode( buf+9,  FD_H2_SETTINGS_HEADER_TABLE_SIZE,      0U                               );
      74          33 :   fd_h2_setting_encode( buf+15, FD_H2_SETTINGS_ENABLE_PUSH,            0U                               );
      75          33 :   fd_h2_setting_encode( buf+21, FD_H2_SETTINGS_MAX_CONCURRENT_STREAMS, settings->max_concurrent_streams );
      76          33 :   fd_h2_setting_encode( buf+27, FD_H2_SETTINGS_INITIAL_WINDOW_SIZE,    settings->initial_window_size    );
      77          33 :   fd_h2_setting_encode( buf+33, FD_H2_SETTINGS_MAX_FRAME_SIZE,         settings->max_frame_size         );
      78          33 :   fd_h2_setting_encode( buf+39, FD_H2_SETTINGS_MAX_HEADER_LIST_SIZE,   settings->max_header_list_size   );
      79          33 : }
      80             : 
      81             : /* fd_h2_rx_stream_used returns 1 if stream_id was already opened or
      82             :    implicitly closed by opening a larger ID of the same initiator. */
      83             : 
      84             : static inline int
      85             : fd_h2_rx_stream_used( fd_h2_conn_t const * conn,
      86         183 :                       uint                 stream_id ) {
      87         183 :   uint next = ((stream_id^conn->tx_stream_next)&1U) ? conn->rx_stream_next : conn->tx_stream_next;
      88         183 :   return stream_id<next;
      89         183 : }
      90             : 
      91             : /* fd_h2_rx_data handles a partial DATA frame.  The caller reserves TX
      92             :    space for a single RST_STREAM or two WINDOW_UPDATE frames. */
      93             : 
      94             : static void
      95             : fd_h2_rx_data( fd_h2_conn_t *            conn,
      96             :                fd_h2_rbuf_t *            rbuf_rx,
      97             :                fd_h2_rbuf_t *            rbuf_tx,
      98         219 :                fd_h2_callbacks_t const * cb ) {
      99         219 :   ulong frame_rem  = conn->rx_data_cnt_rem;
     100         219 :   ulong rbuf_avail = fd_h2_rbuf_used_sz( rbuf_rx );
     101         219 :   uint  stream_id  = conn->rx_stream_id;
     102         219 :   uint  chunk_sz   = (uint)fd_ulong_min( frame_rem, rbuf_avail );
     103         219 :   uint  wnd_sz     = chunk_sz; /* + Pad Length and Padding on the last chunk */
     104         219 :   uint  fin_flag   = conn->rx_frame_flags & FD_H2_FLAG_END_STREAM;
     105             :   /* The rest of the frame must fit before any of it is delivered, even
     106             :      if credit is replenished between chunks (RFC 9113 6.9.1). */
     107         219 :   uint  frame_wnd_sz = (uint)frame_rem + ( conn->rx_frame_flags & FD_H2_FLAG_PADDED ? 1U + conn->rx_pad_rem : 0U );
     108         219 :   if( rbuf_avail<frame_rem ) fin_flag = 0;
     109         183 :   else wnd_sz = frame_wnd_sz;
     110             : 
     111         219 :   if( FD_UNLIKELY( frame_wnd_sz > conn->rx_wnd ) ) {
     112           6 :     fd_h2_conn_error( conn, FD_H2_ERR_FLOW_CONTROL );
     113           6 :     return;
     114           6 :   }
     115         213 :   conn->rx_wnd -= wnd_sz;
     116             : 
     117         213 :   fd_h2_stream_t * stream = cb->stream_query( conn, stream_id );
     118         213 :   if( FD_UNLIKELY( !stream ) ) {
     119          45 :     if( fd_h2_rx_stream_used( conn, stream_id ) ) goto skip_frame; /* RFC 9113 5.1 */
     120           9 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     121           9 :     return;
     122          45 :   }
     123         168 :   if( FD_UNLIKELY( stream->state!=FD_H2_STREAM_STATE_OPEN &&
     124         168 :                    stream->state!=FD_H2_STREAM_STATE_CLOSING_TX ) ) {
     125          21 :     if( stream->state==FD_H2_STREAM_STATE_CLOSED ) goto skip_frame;
     126           9 :     if( FD_UNLIKELY( stream->state!=FD_H2_STREAM_STATE_CLOSING_RX ) ) {
     127           6 :       fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     128           6 :       return;
     129           6 :     }
     130           3 :     fd_h2_stream_error( stream, conn, rbuf_tx, FD_H2_ERR_STREAM_CLOSED );
     131           3 :     cb->rst_stream( conn, stream, FD_H2_ERR_STREAM_CLOSED, 0 );
     132           3 :     goto skip_frame;
     133           9 :   }
     134             : 
     135         147 :   if( FD_UNLIKELY( frame_wnd_sz > stream->rx_wnd ) ) {
     136          18 :     fd_h2_stream_error( stream, conn, rbuf_tx, FD_H2_ERR_FLOW_CONTROL );
     137          18 :     cb->rst_stream( conn, stream, FD_H2_ERR_FLOW_CONTROL, 0 );
     138          18 :     goto skip_frame;
     139          18 :   }
     140         129 :   stream->rx_wnd -= wnd_sz;
     141             : 
     142         129 :   fd_h2_stream_rx_data( stream, conn, fin_flag );
     143             : 
     144         129 :   ulong sz0, sz1;
     145         129 :   uchar const * peek = fd_h2_rbuf_peek_used( rbuf_rx, &sz0, &sz1 );
     146         129 :   if( sz0>=chunk_sz ) {
     147          93 :     sz0 = chunk_sz;
     148          93 :     sz1 = 0;
     149          93 :   } else if( sz0+sz1>chunk_sz ) {
     150           0 :     sz1 = chunk_sz-sz0;
     151           0 :   }
     152         129 :   if( FD_LIKELY( !sz1 ) ) {
     153          93 :     cb->data( conn, stream, peek, sz0, fin_flag );
     154          93 :   } else {
     155          36 :     cb->data( conn, stream, peek,          sz0, 0        );
     156             :     /* The first callback may have released the stream.  Re-query the stream
     157             :        map before dispatching the wrapped tail chunk. */
     158          36 :     stream = cb->stream_query( conn, stream_id );
     159          36 :     if( FD_LIKELY( stream && !stream->rx_aborted && !(conn->flags & (FD_H2_CONN_FLAGS_SEND_GOAWAY|FD_H2_CONN_FLAGS_DEAD)) ) ) {
     160          12 :       cb->data( conn, stream, rbuf_rx->buf0, sz1, fin_flag );
     161          12 :     }
     162          36 :   }
     163             : 
     164         198 : skip_frame:
     165         198 :   conn->rx_data_cnt_rem -= chunk_sz;
     166         198 :   fd_h2_rbuf_skip( rbuf_rx, chunk_sz );
     167         198 :   if( FD_UNLIKELY( conn->rx_wnd < conn->rx_wnd_wmark ) ) {
     168          69 :     conn->flags |= FD_H2_CONN_FLAGS_WINDOW_UPDATE;
     169          69 :   }
     170         198 : }
     171             : 
     172             : static int
     173             : fd_h2_rx_headers( fd_h2_conn_t *            conn,
     174             :                   fd_h2_rbuf_t *            rbuf_tx,
     175             :                   uchar *                   payload,
     176             :                   ulong                     payload_sz,
     177             :                   fd_h2_callbacks_t const * cb,
     178             :                   uint                      frame_flags,
     179         138 :                   uint                      stream_id ) {
     180             : 
     181         138 :   if( FD_UNLIKELY( !stream_id ) ) {
     182           0 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     183           0 :     return 0;
     184           0 :   }
     185             : 
     186         138 :   if( FD_UNLIKELY( frame_flags & FD_H2_FLAG_PRIORITY ) ) {
     187           0 :     if( FD_UNLIKELY( payload_sz<5UL ) ) {
     188           0 :       fd_h2_conn_error( conn, FD_H2_ERR_FRAME_SIZE );
     189           0 :       return 0;
     190           0 :     }
     191           0 :     payload    += 5UL;
     192           0 :     payload_sz -= 5UL;
     193           0 :   }
     194             : 
     195         138 :   fd_h2_stream_t * stream = cb->stream_query( conn, stream_id );
     196         138 :   if( FD_UNLIKELY( !stream || stream->state==FD_H2_STREAM_STATE_CLOSED ) ) {
     197          63 :     stream = NULL;
     198          63 :     if( !fd_h2_rx_stream_used( conn, stream_id ) ) {
     199          27 :       if( FD_UNLIKELY( ((stream_id^conn->tx_stream_next)&1U)==0U ||
     200          27 :                       ((conn->tx_stream_next&1U) && !conn->allow_server_requests) ) ) {
     201           6 :         fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     202           6 :         return 0;
     203           6 :       }
     204             :       /* Even a refused stream consumes its ID and closes skipped IDs. */
     205          21 :       conn->rx_stream_next = stream_id+2U;
     206          21 :       if( FD_LIKELY( conn->stream_active_cnt[0] < conn->self_settings.max_concurrent_streams ) ) {
     207          18 :         stream = cb->stream_create( conn, stream_id );
     208          18 :       }
     209          21 :       if( FD_LIKELY( stream ) ) fd_h2_stream_open( stream, conn, stream_id );
     210           6 :       else fd_h2_tx_rst_stream( rbuf_tx, stream_id, FD_H2_ERR_REFUSED_STREAM );
     211          21 :     }
     212          63 :   }
     213             : 
     214         132 :   conn->rx_stream_id = stream_id;
     215             : 
     216             :   /* Validate every block, as HPACK errors are connection errors (RFC
     217             :      9113 4.3), and keep state from the first fragment of a live block
     218             :      in case its stream is released before CONTINUATION. */
     219         132 :   conn->rx_hdrs_discard = (uchar)!stream;
     220         132 :   if( !(frame_flags & FD_H2_FLAG_END_HEADERS) ) conn->flags |= FD_H2_CONN_FLAGS_CONTINUATION;
     221         132 :   fd_hpack_skip_init( &conn->rx_hpack );
     222         132 :   if( FD_UNLIKELY( fd_hpack_skip_feed( &conn->rx_hpack, payload, payload_sz, !!(frame_flags & FD_H2_FLAG_END_HEADERS) ) ) ) {
     223           6 :     fd_h2_conn_error( conn, FD_H2_ERR_COMPRESSION );
     224           6 :     return 0;
     225           6 :   }
     226         126 :   if( !stream ) return 1;
     227             : 
     228          87 :   fd_h2_stream_rx_headers( stream, conn, frame_flags );
     229          87 :   if( FD_UNLIKELY( stream->state==FD_H2_STREAM_STATE_ILLEGAL ) ) {
     230           0 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     231           0 :     return 0;
     232           0 :   }
     233             : 
     234          87 :   cb->headers( conn, stream, payload, payload_sz, frame_flags );
     235             : 
     236          87 :   return 1;
     237          87 : }
     238             : 
     239             : static int
     240             : fd_h2_rx_priority( fd_h2_conn_t * conn,
     241             :                    ulong          payload_sz,
     242           0 :                    uint           stream_id ) {
     243           0 :   if( FD_UNLIKELY( payload_sz!=5UL ) ) {
     244           0 :     fd_h2_conn_error( conn, FD_H2_ERR_FRAME_SIZE );
     245           0 :     return 0;
     246           0 :   }
     247           0 :   if( FD_UNLIKELY( !stream_id ) ) {
     248           0 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     249           0 :     return 0;
     250           0 :   }
     251           0 :   return 1;
     252           0 : }
     253             : 
     254             : static int
     255             : fd_h2_rx_continuation( fd_h2_conn_t *            conn,
     256             :                        uchar *                   payload,
     257             :                        ulong                     payload_sz,
     258             :                        fd_h2_callbacks_t const * cb,
     259             :                        uint                      frame_flags,
     260          87 :                        uint                      stream_id ) {
     261             : 
     262          87 :   if( FD_UNLIKELY( ( conn->rx_stream_id!=stream_id                    ) |
     263          87 :                    ( !( conn->flags & FD_H2_CONN_FLAGS_CONTINUATION ) ) |
     264          87 :                    ( !stream_id                                       ) ) ) {
     265           0 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     266           0 :     return 0;
     267           0 :   }
     268             : 
     269          87 :   if( FD_UNLIKELY( frame_flags & FD_H2_FLAG_END_HEADERS ) ) {
     270          87 :     conn->flags &= (uchar)~FD_H2_CONN_FLAGS_CONTINUATION;
     271          87 :   }
     272             : 
     273          87 :   if( FD_UNLIKELY( fd_hpack_skip_feed( &conn->rx_hpack, payload, payload_sz, !!(frame_flags & FD_H2_FLAG_END_HEADERS) ) ) ) {
     274          21 :     fd_h2_conn_error( conn, FD_H2_ERR_COMPRESSION );
     275          21 :     return 0;
     276          21 :   }
     277          66 :   if( FD_UNLIKELY( conn->rx_hdrs_discard ) ) return 1;
     278             : 
     279          18 :   fd_h2_stream_t * stream = cb->stream_query( conn, stream_id );
     280          18 :   if( FD_UNLIKELY( !stream ) ) return 1;
     281             : 
     282          18 :   fd_h2_stream_rx_headers( stream, conn, frame_flags );
     283          18 :   if( FD_UNLIKELY( stream->state==FD_H2_STREAM_STATE_ILLEGAL ) ) {
     284           0 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     285           0 :     return 0;
     286           0 :   }
     287             : 
     288          18 :   cb->headers( conn, stream, payload, payload_sz, frame_flags );
     289             : 
     290          18 :   return 1;
     291          18 : }
     292             : 
     293             : static int
     294             : fd_h2_rx_rst_stream( fd_h2_conn_t *            conn,
     295             :                      uchar const *             payload,
     296             :                      ulong                     payload_sz,
     297             :                      fd_h2_callbacks_t const * cb,
     298          30 :                      uint                      stream_id ) {
     299          30 :   if( FD_UNLIKELY( payload_sz!=4UL ) ) {
     300           0 :     fd_h2_conn_error( conn, FD_H2_ERR_FRAME_SIZE );
     301           0 :     return 0;
     302           0 :   }
     303          30 :   if( FD_UNLIKELY( !stream_id || !fd_h2_rx_stream_used( conn, stream_id ) ) ) {
     304           6 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     305           6 :     return 0;
     306           6 :   }
     307          24 :   fd_h2_stream_t * stream = cb->stream_query( conn, stream_id );
     308          24 :   if( FD_LIKELY( stream && stream->state!=FD_H2_STREAM_STATE_CLOSED ) ) {
     309           3 :     uint error_code = fd_uint_bswap( FD_LOAD( uint, payload ) );
     310           3 :     fd_h2_stream_reset( stream, conn );
     311           3 :     cb->rst_stream( conn, stream, error_code, 1 );
     312             :     /* stream points to freed memory at this point */
     313           3 :   }
     314          24 :   return 1;
     315          30 : }
     316             : 
     317             : static int
     318             : fd_h2_rx_settings( fd_h2_conn_t *            conn,
     319             :                    fd_h2_rbuf_t *            rbuf_tx,
     320             :                    uchar const *             payload,
     321             :                    ulong                     payload_sz,
     322             :                    fd_h2_callbacks_t const * cb,
     323             :                    uint                      frame_flags,
     324          42 :                    uint                      stream_id ) {
     325             : 
     326          42 :   if( FD_UNLIKELY( stream_id ) ) {
     327           0 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     328           0 :     return 0;
     329           0 :   }
     330             : 
     331          42 :   if( frame_flags & FD_H2_FLAG_ACK ) {
     332          12 :     if( FD_UNLIKELY( payload_sz ) ) {
     333           0 :       fd_h2_conn_error( conn, FD_H2_ERR_FRAME_SIZE );
     334           0 :       return 0;
     335           0 :     }
     336          12 :     if( FD_UNLIKELY( !conn->setting_tx ) ) {
     337           0 :       fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     338           0 :       return 0;
     339           0 :     }
     340          12 :     if( conn->flags & FD_H2_CONN_FLAGS_WAIT_SETTINGS_ACK_0 ) {
     341          12 :       conn->flags &= (uchar)~FD_H2_CONN_FLAGS_WAIT_SETTINGS_ACK_0;
     342          12 :       if( !( conn->flags & FD_H2_CONN_FLAGS_HANDSHAKING ) ) {
     343          12 :         cb->conn_established( conn );
     344          12 :       }
     345          12 :     }
     346          12 :     conn->setting_tx--;
     347          12 :     return 1;
     348          12 :   }
     349             : 
     350          30 :   if( FD_UNLIKELY( payload_sz % 6 ) ) {
     351           0 :     fd_h2_conn_error( conn, FD_H2_ERR_FRAME_SIZE );
     352           0 :     return 0;
     353           0 :   }
     354             : 
     355          66 :   for( ulong off=0UL; off<payload_sz; off+=sizeof(fd_h2_setting_t) ) {
     356          42 :     fd_h2_setting_t setting = FD_LOAD( fd_h2_setting_t, payload+off );
     357          42 :     ushort id    = fd_ushort_bswap( setting.id );
     358          42 :     uint   value = fd_uint_bswap( setting.value );
     359             : 
     360          42 :     switch( id ) {
     361           6 :     case FD_H2_SETTINGS_ENABLE_PUSH:
     362           6 :       if( FD_UNLIKELY( value>1 ) ) {
     363           0 :         fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     364           0 :         return 0;
     365           0 :       }
     366           6 :       break;
     367           6 :     case FD_H2_SETTINGS_INITIAL_WINDOW_SIZE: {
     368           6 :       if( FD_UNLIKELY( value>0x7fffffff ) ) {
     369           0 :         fd_h2_conn_error( conn, FD_H2_ERR_FLOW_CONTROL );
     370           0 :         return 0;
     371           0 :       }
     372           6 :       long delta = (long)value - (long)conn->peer_settings.initial_window_size;
     373           6 :       conn->peer_settings.initial_window_size = value;
     374           6 :       if( FD_UNLIKELY( delta && cb->initial_window_update ) ) {
     375           3 :         cb->initial_window_update( conn, delta );
     376           3 :         if( FD_UNLIKELY( conn->flags & FD_H2_CONN_FLAGS_SEND_GOAWAY ) ) return 0;
     377           3 :       }
     378           6 :       break;
     379           6 :     }
     380          12 :     case FD_H2_SETTINGS_MAX_FRAME_SIZE:
     381          12 :       if( FD_UNLIKELY( value<0x4000 || value>0xffffff ) ) {
     382             :         /* Values outside this range MUST be treated as a connection error
     383             :            (Section 5.4.1) of type PROTOCOL_ERROR. */
     384           6 :         fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     385           6 :         return 0;
     386           6 :       }
     387           6 :       conn->peer_settings.max_frame_size = value;
     388             :       /* FIXME validate min */
     389           6 :       break;
     390           6 :     case FD_H2_SETTINGS_MAX_HEADER_LIST_SIZE:
     391           6 :       conn->peer_settings.max_header_list_size = value;
     392           6 :       break;
     393           6 :     case FD_H2_SETTINGS_MAX_CONCURRENT_STREAMS:
     394           6 :       conn->peer_settings.max_concurrent_streams = value;
     395           6 :       break;
     396          42 :     }
     397          42 :   }
     398             : 
     399          24 :   fd_h2_frame_hdr_t hdr = {
     400          24 :     .typlen = fd_h2_frame_typlen( FD_H2_FRAME_TYPE_SETTINGS, 0UL ),
     401          24 :     .flags  = FD_H2_FLAG_ACK
     402          24 :   };
     403          24 :   if( FD_UNLIKELY( fd_h2_rbuf_free_sz( rbuf_tx )<sizeof(fd_h2_frame_hdr_t) ) ) {
     404           0 :     fd_h2_conn_error( conn, FD_H2_ERR_INTERNAL );
     405           0 :     return 0;
     406           0 :   }
     407          24 :   fd_h2_rbuf_push( rbuf_tx, &hdr, sizeof(fd_h2_frame_hdr_t) );
     408             : 
     409          24 :   if( conn->flags & FD_H2_CONN_FLAGS_WAIT_SETTINGS_0 ) {
     410          18 :     conn->flags &= (uchar)~FD_H2_CONN_FLAGS_WAIT_SETTINGS_0;
     411          18 :     if( !( conn->flags & FD_H2_CONN_FLAGS_HANDSHAKING ) ) {
     412           0 :       cb->conn_established( conn );
     413           0 :     }
     414          18 :   }
     415             : 
     416          24 :   return 1;
     417          24 : }
     418             : 
     419             : static int
     420           0 : fd_h2_rx_push_promise( fd_h2_conn_t * conn ) {
     421           0 :   fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     422           0 :   return 0;
     423           0 : }
     424             : 
     425             : static int
     426             : fd_h2_rx_ping( fd_h2_conn_t *            conn,
     427             :                fd_h2_rbuf_t *            rbuf_tx,
     428             :                uchar const *             payload,
     429             :                ulong                     payload_sz,
     430             :                fd_h2_callbacks_t const * cb,
     431             :                uint                      frame_flags,
     432          27 :                uint                      stream_id ) {
     433          27 :   if( FD_UNLIKELY( payload_sz!=8UL ) ) {
     434           0 :     fd_h2_conn_error( conn, FD_H2_ERR_FRAME_SIZE );
     435           0 :     return 0;
     436           0 :   }
     437          27 :   if( FD_UNLIKELY( stream_id ) ) {
     438           0 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     439           0 :     return 0;
     440           0 :   }
     441             : 
     442          27 :   if( FD_UNLIKELY( frame_flags & FD_H2_FLAG_ACK ) ) {
     443             : 
     444             :     /* Received an acknowledgement for a PING frame. */
     445          12 :     if( FD_UNLIKELY( conn->ping_tx==0 || FD_LOAD( ulong, payload )!=0UL ) ) {
     446           6 :       return 1;
     447           6 :     }
     448           6 :     cb->ping_ack( conn );
     449           6 :     conn->ping_tx = (uchar)( conn->ping_tx-1 );
     450             : 
     451          15 :   } else {
     452             : 
     453             :     /* Received a new PING frame.  Generate a PONG. */
     454             :     /* FIMXE rate limit */
     455          15 :     fd_h2_ping_t pong = {
     456          15 :       .hdr = {
     457          15 :         .typlen = fd_h2_frame_typlen( FD_H2_FRAME_TYPE_PING, 8UL ),
     458          15 :         .flags  = FD_H2_FLAG_ACK,
     459          15 :       },
     460          15 :       .payload = FD_LOAD( ulong, payload )
     461          15 :     };
     462          15 :     if( FD_UNLIKELY( fd_h2_rbuf_free_sz( rbuf_tx )<sizeof(fd_h2_ping_t) ) ) {
     463           0 :       fd_h2_conn_error( conn, FD_H2_ERR_INTERNAL );
     464           0 :       return 0;
     465           0 :     }
     466          15 :     fd_h2_rbuf_push( rbuf_tx, &pong, sizeof(fd_h2_ping_t) );
     467             : 
     468          15 :   }
     469             : 
     470          21 :   return 1;
     471          27 : }
     472             : 
     473             : void
     474             : fd_h2_tx_rst_stream( fd_h2_rbuf_t * rbuf_tx,
     475             :                      uint           stream_id,
     476         147 :                      uint           h2_err ) {
     477         147 :   fd_h2_rst_stream_t rst_stream = {
     478         147 :     .hdr = {
     479         147 :       .typlen      = fd_h2_frame_typlen( FD_H2_FRAME_TYPE_RST_STREAM, 4UL ),
     480         147 :       .flags       = 0U,
     481         147 :       .r_stream_id = fd_uint_bswap( stream_id )
     482         147 :     },
     483         147 :     .error_code = fd_uint_bswap( h2_err )
     484         147 :   };
     485         147 :   fd_h2_rbuf_push( rbuf_tx, &rst_stream, sizeof(fd_h2_rst_stream_t) );
     486         147 : }
     487             : 
     488             : int
     489             : fd_h2_tx_ping( fd_h2_conn_t * conn,
     490          12 :                fd_h2_rbuf_t * rbuf_tx ) {
     491          12 :   ulong ping_tx = conn->ping_tx;
     492          12 :   if( FD_UNLIKELY( ( fd_h2_rbuf_free_sz( rbuf_tx )<sizeof(fd_h2_ping_t) ) |
     493          12 :                    ( ping_tx>=UCHAR_MAX ) ) ) {
     494           6 :     return 0; /* blocked */
     495           6 :   }
     496             : 
     497           6 :   fd_h2_ping_t ping = {
     498           6 :     .hdr = {
     499           6 :       .typlen      = fd_h2_frame_typlen( FD_H2_FRAME_TYPE_PING, 8UL ),
     500           6 :       .flags       = 0U,
     501           6 :       .r_stream_id = 0UL
     502           6 :     },
     503           6 :     .payload = 0UL
     504           6 :   };
     505           6 :   fd_h2_rbuf_push( rbuf_tx, &ping, sizeof(fd_h2_ping_t) );
     506           6 :   conn->ping_tx = (uchar)( ping_tx+1 );
     507           6 :   return 1;
     508          12 : }
     509             : 
     510             : static int
     511             : fd_h2_rx_goaway( fd_h2_conn_t *            conn,
     512             :                  fd_h2_callbacks_t const * cb,
     513             :                  uchar const *             payload,
     514             :                  ulong                     payload_sz,
     515           6 :                  uint                      stream_id ) {
     516             : 
     517           6 :   if( FD_UNLIKELY( stream_id ) ) {
     518           0 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     519           0 :     return 0;
     520           0 :   }
     521           6 :   if( FD_UNLIKELY( payload_sz<8UL ) ) {
     522           0 :     fd_h2_conn_error( conn, FD_H2_ERR_FRAME_SIZE );
     523           0 :     return 0;
     524           0 :   }
     525             : 
     526           6 :   uint error_code = fd_uint_bswap( FD_LOAD( uint, payload+4UL ) );
     527           6 :   conn->flags = FD_H2_CONN_FLAGS_DEAD;
     528           6 :   cb->conn_final( conn, error_code, 1 /* peer */ );
     529             : 
     530           6 :   return 1;
     531           6 : }
     532             : 
     533             : static int
     534             : fd_h2_rx_window_update( fd_h2_conn_t *            conn,
     535             :                         fd_h2_rbuf_t *            rbuf_tx,
     536             :                         fd_h2_callbacks_t const * cb,
     537             :                         uchar const *             payload,
     538             :                         ulong                     payload_sz,
     539          51 :                         uint                      stream_id ) {
     540          51 :   if( FD_UNLIKELY( payload_sz!=4UL ) ) {
     541           0 :     fd_h2_conn_error( conn, FD_H2_ERR_FRAME_SIZE );
     542           0 :     return 0;
     543           0 :   }
     544          51 :   uint increment = fd_uint_bswap( FD_LOAD( uint, payload ) ) & 0x7fffffff;
     545             : 
     546          51 :   if( !stream_id ) {
     547             : 
     548             :     /* Connection-level window update */
     549           6 :     uint tx_wnd = conn->tx_wnd;
     550           6 :     if( FD_UNLIKELY( !increment ) ) {
     551           0 :       fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     552           0 :       return 0;
     553           0 :     }
     554           6 :     uint tx_wnd_new;
     555           6 :     if( FD_UNLIKELY( __builtin_uadd_overflow( tx_wnd, increment, &tx_wnd_new ) || tx_wnd_new>0x7fffffffU ) ) {
     556           3 :       fd_h2_conn_error( conn, FD_H2_ERR_FLOW_CONTROL );
     557           3 :       return 0;
     558           3 :     }
     559           3 :     conn->tx_wnd = tx_wnd_new;
     560           3 :     cb->window_update( conn, (uint)increment );
     561             : 
     562          45 :   } else {
     563             : 
     564          45 :     if( FD_UNLIKELY( !fd_h2_rx_stream_used( conn, stream_id ) ) ) {
     565           6 :       fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     566           6 :       return 0;
     567           6 :     }
     568             : 
     569          39 :     fd_h2_stream_t * stream = cb->stream_query( conn, stream_id );
     570          39 :     if( FD_UNLIKELY( !stream || stream->state==FD_H2_STREAM_STATE_CLOSED ) ) {
     571          30 :       return 1;
     572          30 :     }
     573             : 
     574           9 :     if( FD_UNLIKELY( !increment ) ) {
     575           3 :       fd_h2_stream_error( stream, conn, rbuf_tx, FD_H2_ERR_PROTOCOL );
     576           3 :       cb->rst_stream( conn, stream, FD_H2_ERR_PROTOCOL, 0 );
     577           3 :       return 1;
     578           3 :     }
     579             : 
     580             :     /* Stream-level window update */
     581           6 :     uint tx_wnd_new;
     582           6 :     if( FD_UNLIKELY( __builtin_uadd_overflow( stream->tx_wnd, increment, &tx_wnd_new ) || tx_wnd_new>0x7fffffffU ) ) {
     583           3 :       fd_h2_stream_error( stream, conn, rbuf_tx, FD_H2_ERR_FLOW_CONTROL );
     584           3 :       cb->rst_stream( conn, stream, FD_H2_ERR_FLOW_CONTROL, 0 );
     585             :       /* stream points to freed memory at this point */
     586           3 :       return 1;
     587           3 :     }
     588           3 :     stream->tx_wnd = tx_wnd_new;
     589           3 :     cb->stream_window_update( conn, stream, (uint)increment );
     590             : 
     591           3 :   }
     592             : 
     593           6 :   return 1;
     594          51 : }
     595             : 
     596             : /* fd_h2_rx_frame handles a complete frame.  Returns 1 on success, and
     597             :    0 on connection error. */
     598             : 
     599             : static int
     600             : fd_h2_rx_frame( fd_h2_conn_t *            conn,
     601             :                 fd_h2_rbuf_t *            rbuf_tx,
     602             :                 uchar *                   payload,
     603             :                 ulong                     payload_sz,
     604             :                 fd_h2_callbacks_t const * cb,
     605             :                 uint                      frame_type,
     606             :                 uint                      frame_flags,
     607         381 :                 uint                      stream_id ) {
     608         381 :   switch( frame_type ) {
     609         138 :   case FD_H2_FRAME_TYPE_HEADERS:
     610         138 :     return fd_h2_rx_headers( conn, rbuf_tx, payload, payload_sz, cb, frame_flags, stream_id );
     611           0 :   case FD_H2_FRAME_TYPE_PRIORITY:
     612           0 :     return fd_h2_rx_priority( conn, payload_sz, stream_id );
     613          30 :   case FD_H2_FRAME_TYPE_RST_STREAM:
     614          30 :     return fd_h2_rx_rst_stream( conn, payload, payload_sz, cb, stream_id );
     615          42 :   case FD_H2_FRAME_TYPE_SETTINGS:
     616          42 :     return fd_h2_rx_settings( conn, rbuf_tx, payload, payload_sz, cb, frame_flags, stream_id );
     617           0 :   case FD_H2_FRAME_TYPE_PUSH_PROMISE:
     618           0 :     return fd_h2_rx_push_promise( conn );
     619          87 :   case FD_H2_FRAME_TYPE_CONTINUATION:
     620          87 :     return fd_h2_rx_continuation( conn, payload, payload_sz, cb, frame_flags&FD_H2_FLAG_END_HEADERS, stream_id );
     621          27 :   case FD_H2_FRAME_TYPE_PING:
     622          27 :     return fd_h2_rx_ping( conn, rbuf_tx, payload, payload_sz, cb, frame_flags, stream_id );
     623           6 :   case FD_H2_FRAME_TYPE_GOAWAY:
     624           6 :     return fd_h2_rx_goaway( conn, cb, payload, payload_sz, stream_id );
     625          51 :   case FD_H2_FRAME_TYPE_WINDOW_UPDATE:
     626          51 :     return fd_h2_rx_window_update( conn, rbuf_tx, cb, payload, payload_sz, stream_id );
     627           0 :   default:
     628           0 :     return 1;
     629         381 :   }
     630         381 : }
     631             : 
     632             : /* fd_h2_rx1 handles one frame. */
     633             : 
     634             : static void
     635             : fd_h2_rx1( fd_h2_conn_t *            conn,
     636             :            fd_h2_rbuf_t *            rbuf_rx,
     637             :            fd_h2_rbuf_t *            rbuf_tx,
     638             :            uchar *                   scratch,
     639             :            ulong                     scratch_sz,
     640         651 :            fd_h2_callbacks_t const * cb ) {
     641             :   /* All frames except DATA are fully buffered, thus assume that current
     642             :      frame is a DATA frame if rx_data_cnt_rem != 0. */
     643         651 :   if( conn->rx_data_cnt_rem ) {
     644          30 :     if( FD_UNLIKELY( fd_h2_rbuf_free_sz( rbuf_tx )<2UL*sizeof(fd_h2_window_update_t) ) ) return;
     645          27 :     fd_h2_rx_data( conn, rbuf_rx, rbuf_tx, cb );
     646          27 :     return;
     647          30 :   }
     648         621 :   if( FD_UNLIKELY( conn->rx_pad_rem ) ) {
     649          24 :     ulong pad_rem    = conn->rx_pad_rem;
     650          24 :     ulong rbuf_avail = fd_h2_rbuf_used_sz( rbuf_rx );
     651          24 :     uint  chunk_sz   = (uint)fd_ulong_min( pad_rem, rbuf_avail );
     652          24 :     fd_h2_rbuf_skip( rbuf_rx, chunk_sz );
     653          24 :     conn->rx_pad_rem = (uchar)( conn->rx_pad_rem - chunk_sz );
     654          24 :     return;
     655          24 :   }
     656             : 
     657             :   /* A new frame starts.  Peek the header. */
     658         597 :   if( FD_UNLIKELY( fd_h2_rbuf_used_sz( rbuf_rx )<sizeof(fd_h2_frame_hdr_t) ) ) {
     659           0 :     conn->rx_suppress = rbuf_rx->lo_off + sizeof(fd_h2_frame_hdr_t);
     660           0 :     return;
     661           0 :   }
     662         597 :   fd_h2_rbuf_t rx_peek = *rbuf_rx;
     663         597 :   fd_h2_frame_hdr_t hdr;
     664         597 :   fd_h2_rbuf_pop_copy( &rx_peek, &hdr, sizeof(fd_h2_frame_hdr_t) );
     665         597 :   uint const frame_type = fd_h2_frame_type  ( hdr.typlen );
     666         597 :   uint const frame_sz   = fd_h2_frame_length( hdr.typlen );
     667             : 
     668         597 :   if( FD_UNLIKELY( frame_sz > conn->self_settings.max_frame_size ) ) {
     669           6 :     fd_h2_conn_error( conn, FD_H2_ERR_FRAME_SIZE );
     670           6 :     return;
     671           6 :   }
     672         591 :   if( FD_UNLIKELY( (!!( conn->flags & FD_H2_CONN_FLAGS_CONTINUATION ) ) &
     673         591 :                    (    frame_type!=FD_H2_FRAME_TYPE_CONTINUATION     ) ) ) {
     674           0 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     675           0 :     return;
     676           0 :   }
     677             :   /* The peer's preface is a non-ACK SETTINGS frame (RFC 9113 3.4) */
     678         591 :   if( FD_UNLIKELY( (conn->flags & FD_H2_CONN_FLAGS_WAIT_SETTINGS_0) &&
     679         591 :                    (frame_type!=FD_H2_FRAME_TYPE_SETTINGS || (hdr.flags & FD_H2_FLAG_ACK)) ) ) {
     680           9 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     681           9 :     return;
     682           9 :   }
     683             : 
     684             :   /* Peek padding */
     685         582 :   uint pad_sz = 0U;
     686             :   /* Bytes remaining in this frame payload excluding padding length and padding. */
     687         582 :   uint payload_sz = frame_sz;
     688         582 :   if( ( frame_type==FD_H2_FRAME_TYPE_DATA    ||
     689         582 :         frame_type==FD_H2_FRAME_TYPE_HEADERS ||
     690         582 :         frame_type==FD_H2_FRAME_TYPE_PUSH_PROMISE ) &&
     691         582 :       !!( hdr.flags & FD_H2_FLAG_PADDED ) ) {
     692          36 :     if( FD_UNLIKELY( fd_h2_rbuf_used_sz( &rx_peek )<1UL ) ) return;
     693          36 :     pad_sz = rx_peek.lo[0];
     694          36 :     payload_sz -= 1U; // Exclude Pad Length field
     695          36 :     payload_sz -= pad_sz; // Exclude padding
     696             :     /* If the length of the padding is the length of the
     697             :        frame payload or greater, the recipient MUST treat this as a
     698             :        connection error (Section 5.4.1) of type PROTOCOL_ERROR. */
     699          36 :     if( FD_UNLIKELY( pad_sz>=frame_sz ) ) {
     700           0 :       fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     701           0 :       return;
     702           0 :     }
     703          36 :     fd_h2_rbuf_skip( &rx_peek, 1UL );
     704          36 :   }
     705             : 
     706             :   /* Special case: Process data incrementally */
     707         582 :   if( frame_type==FD_H2_FRAME_TYPE_DATA ) {
     708             :     /* Reserve responses before consuming the header, including for
     709             :        empty DATA frames that have no pending-data counter to retry. */
     710         198 :     if( FD_UNLIKELY( fd_h2_rbuf_free_sz( rbuf_tx )<2UL*sizeof(fd_h2_window_update_t) ) ) return;
     711             :     /* The amount of data is the remainder of the
     712             :       frame payload after subtracting the length of the other fields
     713             :       that are present [that is, padding length and padding]. */
     714         192 :     conn->rx_data_cnt_rem   = payload_sz;
     715         192 :     conn->rx_frame_flags = hdr.flags;
     716         192 :     conn->rx_stream_id   = fd_h2_frame_stream_id( hdr.r_stream_id );
     717         192 :     conn->rx_pad_rem     = (uchar)pad_sz;
     718         192 :     *rbuf_rx = rx_peek;
     719         192 :     if( FD_UNLIKELY( !conn->rx_stream_id ) ) {
     720           0 :       fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     721           0 :       return;
     722           0 :     }
     723         192 :     fd_h2_rx_data( conn, rbuf_rx, rbuf_tx, cb );
     724         192 :     return;
     725         192 :   }
     726             : 
     727             :   /* Consume all or nothing */
     728         384 :   ulong const tot_sz = sizeof(fd_h2_frame_hdr_t) + frame_sz;
     729         384 :   if( FD_UNLIKELY( tot_sz>rbuf_rx->bufsz ) ) {
     730             :     /* Frame will never fit in the buffer */
     731           3 :     fd_h2_conn_error( conn, FD_H2_ERR_INTERNAL );
     732           3 :     return;
     733           3 :   }
     734         381 :   if( FD_UNLIKELY( tot_sz>fd_h2_rbuf_used_sz( rbuf_rx ) ) ) {
     735           0 :     conn->rx_suppress = rbuf_rx->lo_off + tot_sz;
     736           0 :     return;
     737           0 :   }
     738             : 
     739         381 :   if( FD_UNLIKELY( scratch_sz < payload_sz ) ) {
     740           0 :     if( FD_UNLIKELY( scratch_sz < conn->self_settings.max_frame_size ) ) {
     741           0 :       FD_LOG_WARNING(( "scratch buffer too small: scratch_sz=%lu max_frame_size=%u)",
     742           0 :                        scratch_sz, conn->self_settings.max_frame_size ));
     743           0 :       fd_h2_conn_error( conn, FD_H2_ERR_INTERNAL );
     744           0 :       return;
     745           0 :     }
     746           0 :     fd_h2_conn_error( conn, FD_H2_ERR_FRAME_SIZE );
     747           0 :     return;
     748           0 :   }
     749             : 
     750             :   /* Ensure TX buffer has enough free space for control frame responses
     751             :      that frame handlers might generate.  The largest is the PONG reply
     752             :      (17 bytes); RST_STREAM is 13.  If there is not enough space, defer
     753             :      processing until the TX buffer drains. */
     754         381 :   if( FD_UNLIKELY( fd_h2_rbuf_free_sz( rbuf_tx )<sizeof(fd_h2_ping_t) ) ) return;
     755             : 
     756         381 :   *rbuf_rx = rx_peek;
     757         381 :   uchar * frame = fd_h2_rbuf_pop( rbuf_rx, scratch, payload_sz );
     758         381 :   fd_h2_rx_frame( conn, rbuf_tx, frame, payload_sz, cb,
     759         381 :                   frame_type,
     760         381 :                   hdr.flags,
     761         381 :                   fd_h2_frame_stream_id( hdr.r_stream_id ) );
     762         381 :   fd_h2_rbuf_skip( rbuf_rx, pad_sz );
     763         381 : }
     764             : 
     765             : void
     766             : fd_h2_rx( fd_h2_conn_t *            conn,
     767             :           fd_h2_rbuf_t *            rbuf_rx,
     768             :           fd_h2_rbuf_t *            rbuf_tx,
     769             :           uchar *                   scratch,
     770             :           ulong                     scratch_sz,
     771         552 :           fd_h2_callbacks_t const * cb ) {
     772             :   /* Pre-receive TX work */
     773             : 
     774             :   /* Stop handling frames on conn error.  Defer them until our preface
     775             :      is queued, so no reply precedes it (RFC 9113 3.4). */
     776         552 :   if( FD_UNLIKELY( conn->flags & (FD_H2_CONN_FLAGS_SEND_GOAWAY|FD_H2_CONN_FLAGS_DEAD|
     777         552 :                                   FD_H2_CONN_FLAGS_CLIENT_INITIAL|FD_H2_CONN_FLAGS_SERVER_INITIAL) ) ) return;
     778             : 
     779             :   /* All other logic below can only proceed if new data arrived. */
     780         522 :   if( FD_UNLIKELY( !fd_h2_rbuf_used_sz( rbuf_rx ) ) ) return;
     781             : 
     782             :   /* Slowloris defense: Guess how much bytes are required to progress
     783             :      ahead of time based on the frame's type and size. */
     784         474 :   if( FD_UNLIKELY( rbuf_rx->hi_off < conn->rx_suppress ) ) return;
     785             : 
     786             :   /* Handle frames */
     787         651 :   for(;;) {
     788         651 :     ulong lo0 = rbuf_rx->lo_off;
     789         651 :     fd_h2_rx1( conn, rbuf_rx, rbuf_tx, scratch, scratch_sz, cb );
     790         651 :     ulong lo1 = rbuf_rx->lo_off;
     791             : 
     792             :     /* Terminate when no more bytes are available to read */
     793         651 :     if( !fd_h2_rbuf_used_sz( rbuf_rx ) ) break;
     794             : 
     795             :     /* Terminate when the frame handler didn't make progress (e.g. due
     796             :        to rbuf_tx full, or due to incomplete read from rbuf_tx)*/
     797         252 :     if( FD_UNLIKELY( lo0==lo1 ) ) break;
     798             : 
     799             :     /* Terminate if the conn died */
     800         225 :     if( FD_UNLIKELY( conn->flags & (FD_H2_CONN_FLAGS_SEND_GOAWAY|FD_H2_CONN_FLAGS_DEAD) ) ) break;
     801         225 :   }
     802         474 : }
     803             : 
     804             : void
     805             : fd_h2_tx_control( fd_h2_conn_t *            conn,
     806             :                   fd_h2_rbuf_t *            rbuf_tx,
     807          78 :                   fd_h2_callbacks_t const * cb ) {
     808             : 
     809          78 :   if( FD_UNLIKELY( fd_h2_rbuf_free_sz( rbuf_tx )<128 ) ) return;
     810             : 
     811          78 :   switch( fd_uint_find_lsb( (uint)conn->flags | 0x10000u ) ) {
     812             : 
     813          18 :   case FD_H2_CONN_FLAGS_LG_CLIENT_INITIAL:
     814          18 :     fd_h2_rbuf_push( rbuf_tx, fd_h2_client_preface, sizeof(fd_h2_client_preface) );
     815          18 :     __attribute__((fallthrough));
     816             : 
     817          33 :   case FD_H2_CONN_FLAGS_LG_SERVER_INITIAL: {
     818          33 :     uchar buf[ FD_H2_OUR_SETTINGS_ENCODED_SZ ];
     819          33 :     fd_h2_gen_settings( &conn->self_settings, buf );
     820          33 :     fd_h2_rbuf_push( rbuf_tx, buf, sizeof(buf) );
     821          33 :     conn->setting_tx++;
     822          33 :     conn->flags = FD_H2_CONN_FLAGS_WAIT_SETTINGS_0 | FD_H2_CONN_FLAGS_WAIT_SETTINGS_ACK_0;
     823          33 :     break;
     824          18 :   }
     825             : 
     826           0 : goaway:
     827          15 :   case FD_H2_CONN_FLAGS_LG_SEND_GOAWAY: {
     828          15 :     fd_h2_goaway_t goaway = {
     829          15 :       .hdr = {
     830          15 :         .typlen = fd_h2_frame_typlen( FD_H2_FRAME_TYPE_GOAWAY, 8UL )
     831          15 :       },
     832          15 :       .last_stream_id = 0, /* FIXME */
     833          15 :       .error_code     = fd_uint_bswap( (uint)conn->conn_error )
     834          15 :     };
     835          15 :     conn->flags = FD_H2_CONN_FLAGS_DEAD;
     836          15 :     fd_h2_rbuf_push( rbuf_tx, &goaway, sizeof(fd_h2_goaway_t) );
     837          15 :     cb->conn_final( conn, conn->conn_error, 0 /* local */ );
     838          15 :     break;
     839           0 :   }
     840             : 
     841          15 :   case FD_H2_CONN_FLAGS_LG_WINDOW_UPDATE: {
     842          15 :     uint increment = conn->rx_wnd_max - conn->rx_wnd;
     843          15 :     if( FD_UNLIKELY( increment>0x7fffffff ) ) {
     844           0 :       fd_h2_conn_error( conn, FD_H2_ERR_INTERNAL );
     845           0 :       goto goaway;
     846           0 :     }
     847          15 :     if( FD_UNLIKELY( increment==0 ) ) break;
     848          12 :     fd_h2_window_update_t window_update = {
     849          12 :       .hdr = {
     850          12 :         .typlen = fd_h2_frame_typlen( FD_H2_FRAME_TYPE_WINDOW_UPDATE, 4UL )
     851          12 :       },
     852          12 :       .increment = fd_uint_bswap( increment )
     853          12 :     };
     854          12 :     fd_h2_rbuf_push( rbuf_tx, &window_update, sizeof(fd_h2_window_update_t) );
     855          12 :     conn->rx_wnd = conn->rx_wnd_max;
     856          12 :     conn->flags = (ushort)( (conn->flags) & (~FD_H2_CONN_FLAGS_WINDOW_UPDATE) );
     857          12 :     break;
     858          15 :   }
     859             : 
     860          15 :   default:
     861          15 :     break;
     862             : 
     863          78 :   }
     864             : 
     865          78 : }

Generated by: LCOV version 1.14