LCOV - code coverage report
Current view: top level - waltz/h2 - fd_h2_conn.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 186 559 33.3 %
Date: 2025-07-01 05:00:49 Functions: 11 20 55.0 %

          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          27 : fd_h2_conn_init_window( fd_h2_conn_t * conn ) {
      23          27 :   conn->rx_wnd_max   = 65535U;
      24          27 :   conn->rx_wnd       = conn->rx_wnd_max;
      25          27 :   conn->rx_wnd_wmark = (uint)( 0.7f * (float)conn->rx_wnd_max );
      26          27 :   conn->tx_wnd       = 65535U;
      27          27 : }
      28             : 
      29             : fd_h2_conn_t *
      30          27 : fd_h2_conn_init_client( fd_h2_conn_t * conn ) {
      31          27 :   *conn = (fd_h2_conn_t) {
      32          27 :     .self_settings  = fd_h2_settings_initial,
      33          27 :     .peer_settings  = fd_h2_settings_initial,
      34          27 :     .flags          = FD_H2_CONN_FLAGS_CLIENT_INITIAL,
      35          27 :     .tx_stream_next = 1U,
      36          27 :     .rx_stream_next = 2U
      37          27 :   };
      38          27 :   fd_h2_conn_init_window( conn );
      39          27 :   return conn;
      40          27 : }
      41             : 
      42             : fd_h2_conn_t *
      43           0 : fd_h2_conn_init_server( fd_h2_conn_t * conn ) {
      44           0 :   *conn = (fd_h2_conn_t) {
      45           0 :     .self_settings  = fd_h2_settings_initial,
      46           0 :     .peer_settings  = fd_h2_settings_initial,
      47           0 :     .flags          = FD_H2_CONN_FLAGS_SERVER_INITIAL,
      48           0 :     .tx_stream_next = 2U,
      49           0 :     .rx_stream_next = 1U
      50           0 :   };
      51           0 :   fd_h2_conn_init_window( conn );
      52           0 :   return conn;
      53           0 : }
      54             : 
      55             : static void
      56             : fd_h2_setting_encode( uchar * buf,
      57             :                       ushort  setting_id,
      58          18 :                       uint    setting_value ) {
      59          18 :   FD_STORE( ushort, buf,   fd_ushort_bswap( setting_id    ) );
      60          18 :   FD_STORE( uint,   buf+2, fd_uint_bswap  ( setting_value ) );
      61          18 : }
      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           3 :                     uchar                    buf[ FD_H2_OUR_SETTINGS_ENCODED_SZ ] ) {
      68           3 :   fd_h2_frame_hdr_t hdr = {
      69           3 :     .typlen = fd_h2_frame_typlen( FD_H2_FRAME_TYPE_SETTINGS, 36UL ),
      70           3 :   };
      71           3 :   fd_memcpy( buf, &hdr, 9UL );
      72             : 
      73           3 :   fd_h2_setting_encode( buf+9,  FD_H2_SETTINGS_HEADER_TABLE_SIZE,      0U                               );
      74           3 :   fd_h2_setting_encode( buf+15, FD_H2_SETTINGS_ENABLE_PUSH,            0U                               );
      75           3 :   fd_h2_setting_encode( buf+21, FD_H2_SETTINGS_MAX_CONCURRENT_STREAMS, settings->max_concurrent_streams );
      76           3 :   fd_h2_setting_encode( buf+27, FD_H2_SETTINGS_INITIAL_WINDOW_SIZE,    settings->initial_window_size    );
      77           3 :   fd_h2_setting_encode( buf+33, FD_H2_SETTINGS_MAX_FRAME_SIZE,         settings->max_frame_size         );
      78           3 :   fd_h2_setting_encode( buf+39, FD_H2_SETTINGS_MAX_HEADER_LIST_SIZE,   settings->max_header_list_size   );
      79           3 : }
      80             : 
      81             : /* fd_h2_rx_data handles a partial DATA frame. */
      82             : 
      83             : static void
      84             : fd_h2_rx_data( fd_h2_conn_t *            conn,
      85             :                fd_h2_rbuf_t *            rbuf_rx,
      86             :                fd_h2_rbuf_t *            rbuf_tx,
      87           0 :                fd_h2_callbacks_t const * cb ) {
      88             :   /* A receive might generate two WINDOW_UPDATE frames */
      89           0 :   if( FD_UNLIKELY( fd_h2_rbuf_free_sz( rbuf_tx ) < 2*sizeof(fd_h2_window_update_t) ) ) return;
      90             : 
      91           0 :   ulong frame_rem  = conn->rx_frame_rem;
      92           0 :   ulong rbuf_avail = fd_h2_rbuf_used_sz( rbuf_rx );
      93           0 :   uint  stream_id  = conn->rx_stream_id;
      94           0 :   uint  chunk_sz   = (uint)fd_ulong_min( frame_rem, rbuf_avail );
      95           0 :   uint  fin_flag   = conn->rx_frame_flags & FD_H2_FLAG_END_STREAM;
      96           0 :   if( rbuf_avail<frame_rem ) fin_flag = 0;
      97             : 
      98           0 :   fd_h2_stream_t * stream = cb->stream_query( conn, stream_id );
      99           0 :   if( FD_UNLIKELY( !stream ||
     100           0 :                    ( stream->state!=FD_H2_STREAM_STATE_OPEN        &&
     101           0 :                      stream->state!=FD_H2_STREAM_STATE_CLOSING_TX ) ) ) {
     102           0 :     fd_h2_tx_rst_stream( rbuf_tx, stream_id, FD_H2_ERR_STREAM_CLOSED );
     103           0 :     goto skip_frame;
     104           0 :   }
     105             : 
     106           0 :   if( FD_UNLIKELY( chunk_sz > conn->rx_wnd ) ) {
     107           0 :     fd_h2_conn_error( conn, FD_H2_ERR_FLOW_CONTROL );
     108           0 :     return;
     109           0 :   }
     110           0 :   conn->rx_wnd -= chunk_sz;
     111             : 
     112           0 :   if( FD_UNLIKELY( chunk_sz > stream->rx_wnd ) ) {
     113           0 :     fd_h2_tx_rst_stream( rbuf_tx, stream_id, FD_H2_ERR_FLOW_CONTROL );
     114           0 :     goto skip_frame;
     115           0 :   }
     116           0 :   stream->rx_wnd -= chunk_sz;
     117             : 
     118           0 :   fd_h2_stream_rx_data( stream, conn, fin_flag ? FD_H2_FLAG_END_STREAM : 0U );
     119           0 :   if( FD_UNLIKELY( stream->state==FD_H2_STREAM_STATE_ILLEGAL ) ) {
     120           0 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     121           0 :     return;
     122           0 :   }
     123             : 
     124           0 :   ulong sz0, sz1;
     125           0 :   uchar const * peek = fd_h2_rbuf_peek_used( rbuf_rx, &sz0, &sz1 );
     126           0 :   if( sz0>=chunk_sz ) {
     127           0 :     sz0 = chunk_sz;
     128           0 :     sz1 = 0;
     129           0 :   } else if( sz0+sz1>chunk_sz ) {
     130           0 :     sz1 = chunk_sz-sz0;
     131           0 :   }
     132           0 :   if( FD_LIKELY( !sz1 ) ) {
     133           0 :     cb->data( conn, stream, peek, sz0, fin_flag );
     134           0 :   } else {
     135           0 :     cb->data( conn, stream, peek,          sz0, 0        );
     136           0 :     cb->data( conn, stream, rbuf_rx->buf0, sz1, fin_flag );
     137           0 :   }
     138             : 
     139           0 : skip_frame:
     140           0 :   conn->rx_frame_rem -= chunk_sz;
     141           0 :   fd_h2_rbuf_skip( rbuf_rx, chunk_sz );
     142           0 :   if( FD_UNLIKELY( conn->rx_wnd < conn->rx_wnd_wmark ) ) {
     143           0 :     conn->flags |= FD_H2_CONN_FLAGS_WINDOW_UPDATE;
     144           0 :   }
     145           0 : }
     146             : 
     147             : static int
     148             : fd_h2_rx_headers( fd_h2_conn_t *            conn,
     149             :                   fd_h2_rbuf_t *            rbuf_tx,
     150             :                   uchar *                   payload,
     151             :                   ulong                     payload_sz,
     152             :                   fd_h2_callbacks_t const * cb,
     153             :                   uint                      frame_flags,
     154           0 :                   uint                      stream_id ) {
     155             : 
     156           0 :   if( FD_UNLIKELY( !stream_id ) ) {
     157           0 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     158           0 :     return 0;
     159           0 :   }
     160             : 
     161           0 :   fd_h2_stream_t * stream = cb->stream_query( conn, stream_id );
     162           0 :   if( !stream ) {
     163           0 :     if( FD_UNLIKELY( (  stream_id    <   conn->rx_stream_next    ) |
     164           0 :                      ( (stream_id&1) != (conn->rx_stream_next&1) ) ) ) {
     165             :       /* FIXME should send RST_STREAM instead if the user deallocated
     166             :          stream state but we receive a HEADERS frame for a stream that
     167             :          we started ourselves. */
     168           0 :       fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     169           0 :       return 0;
     170           0 :     }
     171           0 :     if( FD_UNLIKELY( conn->stream_active_cnt[0] >= conn->self_settings.max_concurrent_streams ) ) {
     172           0 :       fd_h2_tx_rst_stream( rbuf_tx, stream_id, FD_H2_ERR_REFUSED_STREAM );
     173           0 :       return 1;
     174           0 :     }
     175           0 :     stream = cb->stream_create( conn, stream_id );
     176           0 :     if( FD_UNLIKELY( !stream ) ) {
     177           0 :       fd_h2_tx_rst_stream( rbuf_tx, stream_id, FD_H2_ERR_REFUSED_STREAM );
     178           0 :       return 1;
     179           0 :     }
     180           0 :     fd_h2_stream_open( stream, conn, stream_id );
     181           0 :     stream->tx_wnd = conn->peer_settings.initial_window_size;
     182           0 :     conn->stream_active_cnt[0]++;
     183           0 :     conn->rx_stream_next = stream_id+2;
     184           0 :   }
     185             : 
     186           0 :   conn->rx_stream_id = stream_id;
     187             : 
     188           0 :   if( FD_UNLIKELY( frame_flags & FD_H2_FLAG_PRIORITY ) ) {
     189           0 :     if( FD_UNLIKELY( payload_sz<5UL ) ) {
     190           0 :       fd_h2_conn_error( conn, FD_H2_ERR_FRAME_SIZE );
     191           0 :       return 0;
     192           0 :     }
     193           0 :     payload    += 5UL;
     194           0 :     payload_sz -= 5UL;
     195           0 :   }
     196             : 
     197           0 :   if( FD_UNLIKELY( !( frame_flags & FD_H2_FLAG_END_HEADERS ) ) ) {
     198           0 :     conn->flags |= FD_H2_CONN_FLAGS_CONTINUATION;
     199           0 :   }
     200             : 
     201           0 :   fd_h2_stream_rx_headers( stream, conn, frame_flags );
     202           0 :   if( FD_UNLIKELY( stream->state==FD_H2_STREAM_STATE_ILLEGAL ) ) {
     203           0 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     204           0 :     return 0;
     205           0 :   }
     206             : 
     207           0 :   cb->headers( conn, stream, payload, payload_sz, frame_flags );
     208             : 
     209           0 :   return 1;
     210           0 : }
     211             : 
     212             : static int
     213             : fd_h2_rx_priority( fd_h2_conn_t * conn,
     214             :                    ulong          payload_sz,
     215           0 :                    uint           stream_id ) {
     216           0 :   if( FD_UNLIKELY( payload_sz!=5UL ) ) {
     217           0 :     fd_h2_conn_error( conn, FD_H2_ERR_FRAME_SIZE );
     218           0 :     return 0;
     219           0 :   }
     220           0 :   if( FD_UNLIKELY( !stream_id ) ) {
     221           0 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     222           0 :     return 0;
     223           0 :   }
     224           0 :   return 1;
     225           0 : }
     226             : 
     227             : static int
     228             : fd_h2_rx_continuation( fd_h2_conn_t *            conn,
     229             :                        fd_h2_rbuf_t *            rbuf_tx,
     230             :                        uchar *                   payload,
     231             :                        ulong                     payload_sz,
     232             :                        fd_h2_callbacks_t const * cb,
     233             :                        uint                      frame_flags,
     234           0 :                        uint                      stream_id ) {
     235             : 
     236           0 :   if( FD_UNLIKELY( ( conn->rx_stream_id!=stream_id                    ) |
     237           0 :                    ( !( conn->flags & FD_H2_CONN_FLAGS_CONTINUATION ) ) |
     238           0 :                    ( !stream_id                                       ) ) ) {
     239           0 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     240           0 :     return 0;
     241           0 :   }
     242             : 
     243           0 :   if( FD_UNLIKELY( frame_flags & FD_H2_FLAG_END_HEADERS ) ) {
     244           0 :     conn->flags &= (uchar)~FD_H2_CONN_FLAGS_CONTINUATION;
     245           0 :   }
     246             : 
     247           0 :   fd_h2_stream_t * stream = cb->stream_query( conn, stream_id );
     248           0 :   if( FD_UNLIKELY( !stream ) ) {
     249           0 :     fd_h2_tx_rst_stream( rbuf_tx, stream_id, FD_H2_ERR_INTERNAL );
     250           0 :     return 1;
     251           0 :   }
     252             : 
     253           0 :   fd_h2_stream_rx_headers( stream, conn, frame_flags );
     254           0 :   if( FD_UNLIKELY( stream->state==FD_H2_STREAM_STATE_ILLEGAL ) ) {
     255           0 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     256           0 :     return 0;
     257           0 :   }
     258             : 
     259           0 :   cb->headers( conn, stream, payload, payload_sz, frame_flags );
     260             : 
     261           0 :   return 1;
     262           0 : }
     263             : 
     264             : static int
     265             : fd_h2_rx_rst_stream( fd_h2_conn_t *            conn,
     266             :                      uchar const *             payload,
     267             :                      ulong                     payload_sz,
     268             :                      fd_h2_callbacks_t const * cb,
     269           0 :                      uint                      stream_id ) {
     270           0 :   if( FD_UNLIKELY( payload_sz!=4UL ) ) {
     271           0 :     fd_h2_conn_error( conn, FD_H2_ERR_FRAME_SIZE );
     272           0 :     return 0;
     273           0 :   }
     274           0 :   if( FD_UNLIKELY( !stream_id ) ) {
     275           0 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     276           0 :     return 0;
     277           0 :   }
     278           0 :   if( FD_UNLIKELY( stream_id >= fd_ulong_max( conn->rx_stream_next, conn->tx_stream_next ) ) ) {
     279           0 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     280           0 :     return 0;
     281           0 :   }
     282           0 :   fd_h2_stream_t * stream = cb->stream_query( conn, stream_id );
     283           0 :   if( FD_LIKELY( stream ) ) {
     284           0 :     uint error_code = fd_uint_bswap( FD_LOAD( uint, payload ) );
     285           0 :     fd_h2_stream_reset( stream, conn );
     286           0 :     cb->rst_stream( conn, stream, error_code, 1 );
     287             :     /* stream points to freed memory at this point */
     288           0 :   }
     289           0 :   return 1;
     290           0 : }
     291             : 
     292             : static int
     293             : fd_h2_rx_settings( fd_h2_conn_t *            conn,
     294             :                    fd_h2_rbuf_t *            rbuf_tx,
     295             :                    uchar const *             payload,
     296             :                    ulong                     payload_sz,
     297             :                    fd_h2_callbacks_t const * cb,
     298             :                    uint                      frame_flags,
     299          12 :                    uint                      stream_id ) {
     300             : 
     301          12 :   if( FD_UNLIKELY( stream_id ) ) {
     302           0 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     303           0 :     return 0;
     304           0 :   }
     305             : 
     306          12 :   if( FD_UNLIKELY( conn->flags & FD_H2_CONN_FLAGS_SERVER_INITIAL ) ) {
     307             :     /* As a server, the first frame we should send is SETTINGS, not
     308             :        SETTINGS ACK as generated here */
     309           0 :     return 0;
     310           0 :   }
     311             : 
     312          12 :   if( frame_flags & FD_H2_FLAG_ACK ) {
     313           6 :     if( FD_UNLIKELY( payload_sz ) ) {
     314           0 :       fd_h2_conn_error( conn, FD_H2_ERR_FRAME_SIZE );
     315           0 :       return 0;
     316           0 :     }
     317           6 :     if( FD_UNLIKELY( !conn->setting_tx ) ) {
     318           0 :       fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     319           0 :       return 0;
     320           0 :     }
     321           6 :     if( conn->flags & FD_H2_CONN_FLAGS_WAIT_SETTINGS_ACK_0 ) {
     322           6 :       conn->flags &= (uchar)~FD_H2_CONN_FLAGS_WAIT_SETTINGS_ACK_0;
     323           6 :       if( !( conn->flags & FD_H2_CONN_FLAGS_HANDSHAKING ) ) {
     324           3 :         cb->conn_established( conn );
     325           3 :       }
     326           6 :     }
     327           6 :     conn->setting_tx--;
     328           6 :     return 1;
     329           6 :   }
     330             : 
     331           6 :   if( FD_UNLIKELY( payload_sz % 6 ) ) {
     332           0 :     fd_h2_conn_error( conn, FD_H2_ERR_FRAME_SIZE );
     333           0 :     return 0;
     334           0 :   }
     335             : 
     336          42 :   for( ulong off=0UL; off<payload_sz; off+=sizeof(fd_h2_setting_t) ) {
     337          36 :     fd_h2_setting_t setting = FD_LOAD( fd_h2_setting_t, payload+off );
     338          36 :     ushort id    = fd_ushort_bswap( setting.id );
     339          36 :     uint   value = fd_uint_bswap( setting.value );
     340             : 
     341          36 :     switch( id ) {
     342           6 :     case FD_H2_SETTINGS_ENABLE_PUSH:
     343           6 :       if( FD_UNLIKELY( value>1 ) ) {
     344           0 :         fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     345           0 :         return 0;
     346           0 :       }
     347           6 :       break;
     348           6 :     case FD_H2_SETTINGS_INITIAL_WINDOW_SIZE:
     349           6 :       if( FD_UNLIKELY( value>0x7fffffff ) ) {
     350           0 :         fd_h2_conn_error( conn, FD_H2_ERR_FLOW_CONTROL );
     351           0 :         return 0;
     352           0 :       }
     353           6 :       conn->peer_settings.initial_window_size = value;
     354             :       /* FIXME update window accordingly */
     355           6 :       break;
     356           6 :     case FD_H2_SETTINGS_MAX_FRAME_SIZE:
     357           6 :       if( FD_UNLIKELY( value<0x4000 || value>0xffffff ) ) {
     358           0 :         fd_h2_conn_error( conn, FD_H2_ERR_FLOW_CONTROL );
     359           0 :         return 0;
     360           0 :       }
     361           6 :       conn->peer_settings.max_frame_size = value;
     362             :       /* FIXME validate min */
     363           6 :       break;
     364           6 :     case FD_H2_SETTINGS_MAX_HEADER_LIST_SIZE:
     365           6 :       conn->peer_settings.max_header_list_size = value;
     366           6 :       break;
     367           6 :     case FD_H2_SETTINGS_MAX_CONCURRENT_STREAMS:
     368           6 :       conn->peer_settings.max_concurrent_streams = value;
     369           6 :       break;
     370          36 :     }
     371          36 :   }
     372             : 
     373           6 :   fd_h2_frame_hdr_t hdr = {
     374           6 :     .typlen = fd_h2_frame_typlen( FD_H2_FRAME_TYPE_SETTINGS, 0UL ),
     375           6 :     .flags  = FD_H2_FLAG_ACK
     376           6 :   };
     377           6 :   if( FD_UNLIKELY( fd_h2_rbuf_free_sz( rbuf_tx )<sizeof(fd_h2_frame_hdr_t) ) ) {
     378           0 :     fd_h2_conn_error( conn, FD_H2_ERR_INTERNAL );
     379           0 :     return 0;
     380           0 :   }
     381           6 :   fd_h2_rbuf_push( rbuf_tx, &hdr, sizeof(fd_h2_frame_hdr_t) );
     382             : 
     383           6 :   if( conn->flags & FD_H2_CONN_FLAGS_WAIT_SETTINGS_0 ) {
     384           6 :     conn->flags &= (uchar)~FD_H2_CONN_FLAGS_WAIT_SETTINGS_0;
     385           6 :     if( !( conn->flags & FD_H2_CONN_FLAGS_HANDSHAKING ) ) {
     386           3 :       cb->conn_established( conn );
     387           3 :     }
     388           6 :   }
     389             : 
     390           6 :   return 1;
     391           6 : }
     392             : 
     393             : static int
     394           0 : fd_h2_rx_push_promise( fd_h2_conn_t * conn ) {
     395           0 :   fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     396           0 :   return 0;
     397           0 : }
     398             : 
     399             : static int
     400             : fd_h2_rx_ping( fd_h2_conn_t *            conn,
     401             :                fd_h2_rbuf_t *            rbuf_tx,
     402             :                uchar const *             payload,
     403             :                ulong                     payload_sz,
     404             :                fd_h2_callbacks_t const * cb,
     405             :                uint                      frame_flags,
     406           6 :                uint                      stream_id ) {
     407           6 :   if( FD_UNLIKELY( payload_sz!=8UL ) ) {
     408           0 :     fd_h2_conn_error( conn, FD_H2_ERR_FRAME_SIZE );
     409           0 :     return 0;
     410           0 :   }
     411           6 :   if( FD_UNLIKELY( stream_id ) ) {
     412           0 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     413           0 :     return 0;
     414           0 :   }
     415             : 
     416           6 :   if( FD_UNLIKELY( frame_flags & FD_H2_FLAG_ACK ) ) {
     417             : 
     418             :     /* Received an acknowledgement for a PING frame. */
     419           6 :     if( FD_UNLIKELY( conn->ping_tx==0 ) ) {
     420             :       /* Unsolicited PING ACK ... Blindly ignore, since RFC 9113
     421             :          technically doesn't forbid those. */
     422           3 :       return 1;
     423           3 :     }
     424           3 :     cb->ping_ack( conn );
     425           3 :     conn->ping_tx = (uchar)( conn->ping_tx-1 );
     426             : 
     427           3 :   } else {
     428             : 
     429             :     /* Received a new PING frame.  Generate a PONG. */
     430             :     /* FIMXE rate limit */
     431           0 :     fd_h2_ping_t pong = {
     432           0 :       .hdr = {
     433           0 :         .typlen = fd_h2_frame_typlen( FD_H2_FRAME_TYPE_PING, 8UL ),
     434           0 :         .flags  = FD_H2_FLAG_ACK,
     435           0 :       },
     436           0 :       .payload = FD_LOAD( ulong, payload )
     437           0 :     };
     438           0 :     if( FD_UNLIKELY( fd_h2_rbuf_free_sz( rbuf_tx )<sizeof(fd_h2_ping_t) ) ) {
     439           0 :       fd_h2_conn_error( conn, FD_H2_ERR_INTERNAL );
     440           0 :       return 0;
     441           0 :     }
     442           0 :     fd_h2_rbuf_push( rbuf_tx, &pong, sizeof(fd_h2_ping_t) );
     443             : 
     444           0 :   }
     445             : 
     446           3 :   return 1;
     447           6 : }
     448             : 
     449             : int
     450             : fd_h2_tx_ping( fd_h2_conn_t * conn,
     451           9 :                fd_h2_rbuf_t * rbuf_tx ) {
     452           9 :   ulong ping_tx = conn->ping_tx;
     453           9 :   if( FD_UNLIKELY( ( fd_h2_rbuf_free_sz( rbuf_tx )<sizeof(fd_h2_ping_t) ) |
     454           9 :                    ( ping_tx>=UCHAR_MAX ) ) ) {
     455           6 :     return 0; /* blocked */
     456           6 :   }
     457             : 
     458           3 :   fd_h2_ping_t ping = {
     459           3 :     .hdr = {
     460           3 :       .typlen      = fd_h2_frame_typlen( FD_H2_FRAME_TYPE_PING, 8UL ),
     461           3 :       .flags       = 0U,
     462           3 :       .r_stream_id = 0UL
     463           3 :     },
     464           3 :     .payload = 0UL
     465           3 :   };
     466           3 :   fd_h2_rbuf_push( rbuf_tx, &ping, sizeof(fd_h2_ping_t) );
     467           3 :   conn->ping_tx = (uchar)( ping_tx+1 );
     468           3 :   return 1;
     469           9 : }
     470             : 
     471             : static int
     472             : fd_h2_rx_goaway( fd_h2_conn_t *            conn,
     473             :                  fd_h2_callbacks_t const * cb,
     474             :                  uchar const *             payload,
     475             :                  ulong                     payload_sz,
     476           0 :                  uint                      stream_id ) {
     477             : 
     478           0 :   if( FD_UNLIKELY( stream_id ) ) {
     479           0 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     480           0 :     return 0;
     481           0 :   }
     482           0 :   if( FD_UNLIKELY( payload_sz<8UL ) ) {
     483           0 :     fd_h2_conn_error( conn, FD_H2_ERR_FRAME_SIZE );
     484           0 :     return 0;
     485           0 :   }
     486             : 
     487           0 :   uint error_code = fd_uint_bswap( FD_LOAD( uint, payload+4UL ) );
     488           0 :   conn->flags = FD_H2_CONN_FLAGS_DEAD;
     489           0 :   cb->conn_final( conn, error_code, 1 /* peer */ );
     490             : 
     491           0 :   return 1;
     492           0 : }
     493             : 
     494             : static int
     495             : fd_h2_rx_window_update( fd_h2_conn_t *            conn,
     496             :                         fd_h2_rbuf_t *            rbuf_tx,
     497             :                         fd_h2_callbacks_t const * cb,
     498             :                         uchar const *             payload,
     499             :                         ulong                     payload_sz,
     500           0 :                         uint                      stream_id ) {
     501           0 :   if( FD_UNLIKELY( payload_sz!=4UL ) ) {
     502           0 :     fd_h2_conn_error( conn, FD_H2_ERR_FRAME_SIZE );
     503           0 :     return 0;
     504           0 :   }
     505           0 :   uint increment = fd_uint_bswap( FD_LOAD( uint, payload ) ) & 0x7fffffff;
     506             : 
     507           0 :   if( !stream_id ) {
     508             : 
     509             :     /* Connection-level window update */
     510           0 :     uint tx_wnd = conn->tx_wnd;
     511           0 :     if( FD_UNLIKELY( !increment ) ) {
     512           0 :       fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     513           0 :       return 0;
     514           0 :     }
     515           0 :     uint tx_wnd_new;
     516           0 :     if( FD_UNLIKELY( __builtin_uadd_overflow( tx_wnd, increment, &tx_wnd_new ) ) ) {
     517           0 :       fd_h2_conn_error( conn, FD_H2_ERR_FLOW_CONTROL );
     518           0 :       return 0;
     519           0 :     }
     520           0 :     conn->tx_wnd = tx_wnd_new;
     521           0 :     cb->window_update( conn, (uint)increment );
     522             : 
     523           0 :   } else {
     524             : 
     525           0 :     if( FD_UNLIKELY( stream_id >= fd_ulong_max( conn->rx_stream_next, conn->tx_stream_next ) ) ) {
     526           0 :       fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     527           0 :       return 0;
     528           0 :     }
     529             : 
     530           0 :     if( FD_UNLIKELY( !increment ) ) {
     531           0 :       fd_h2_tx_rst_stream( rbuf_tx, stream_id, FD_H2_ERR_PROTOCOL );
     532           0 :       return 1;
     533           0 :     }
     534             : 
     535           0 :     fd_h2_stream_t * stream = cb->stream_query( conn, stream_id );
     536           0 :     if( FD_UNLIKELY( !stream ) ) {
     537           0 :       fd_h2_tx_rst_stream( rbuf_tx, stream_id, FD_H2_ERR_STREAM_CLOSED );
     538           0 :       return 1;
     539           0 :     }
     540             : 
     541             :     /* Stream-level window update */
     542           0 :     uint tx_wnd_new;
     543           0 :     if( FD_UNLIKELY( __builtin_uadd_overflow( stream->tx_wnd, increment, &tx_wnd_new ) ) ) {
     544           0 :       fd_h2_stream_error( stream, rbuf_tx, FD_H2_ERR_FLOW_CONTROL );
     545           0 :       cb->rst_stream( conn, stream, FD_H2_ERR_FLOW_CONTROL, 0 );
     546             :       /* stream points to freed memory at this point */
     547           0 :       return 1;
     548           0 :     }
     549           0 :     stream->tx_wnd = tx_wnd_new;
     550           0 :     cb->stream_window_update( conn, stream, (uint)increment );
     551             : 
     552           0 :   }
     553             : 
     554           0 :   return 1;
     555           0 : }
     556             : 
     557             : /* fd_h2_rx_frame handles a complete frame.  Returns 1 on success, and
     558             :    0 on connection error. */
     559             : 
     560             : static int
     561             : fd_h2_rx_frame( fd_h2_conn_t *            conn,
     562             :                 fd_h2_rbuf_t *            rbuf_tx,
     563             :                 uchar *                   payload,
     564             :                 ulong                     payload_sz,
     565             :                 fd_h2_callbacks_t const * cb,
     566             :                 uint                      frame_type,
     567             :                 uint                      frame_flags,
     568          18 :                 uint                      stream_id ) {
     569          18 :   switch( frame_type ) {
     570           0 :   case FD_H2_FRAME_TYPE_HEADERS:
     571           0 :     return fd_h2_rx_headers( conn, rbuf_tx, payload, payload_sz, cb, frame_flags, stream_id );
     572           0 :   case FD_H2_FRAME_TYPE_PRIORITY:
     573           0 :     return fd_h2_rx_priority( conn, payload_sz, stream_id );
     574           0 :   case FD_H2_FRAME_TYPE_RST_STREAM:
     575           0 :     return fd_h2_rx_rst_stream( conn, payload, payload_sz, cb, stream_id );
     576          12 :   case FD_H2_FRAME_TYPE_SETTINGS:
     577          12 :     return fd_h2_rx_settings( conn, rbuf_tx, payload, payload_sz, cb, frame_flags, stream_id );
     578           0 :   case FD_H2_FRAME_TYPE_PUSH_PROMISE:
     579           0 :     return fd_h2_rx_push_promise( conn );
     580           0 :   case FD_H2_FRAME_TYPE_CONTINUATION:
     581           0 :     return fd_h2_rx_continuation( conn, rbuf_tx, payload, payload_sz, cb, frame_flags, stream_id );
     582           6 :   case FD_H2_FRAME_TYPE_PING:
     583           6 :     return fd_h2_rx_ping( conn, rbuf_tx, payload, payload_sz, cb, frame_flags, stream_id );
     584           0 :   case FD_H2_FRAME_TYPE_GOAWAY:
     585           0 :     return fd_h2_rx_goaway( conn, cb, payload, payload_sz, stream_id );
     586           0 :   case FD_H2_FRAME_TYPE_WINDOW_UPDATE:
     587           0 :     return fd_h2_rx_window_update( conn, rbuf_tx, cb, payload, payload_sz, stream_id );
     588           0 :   default:
     589           0 :     return 1;
     590          18 :   }
     591          18 : }
     592             : 
     593             : /* fd_h2_rx1 handles one frame. */
     594             : 
     595             : static void
     596             : fd_h2_rx1( fd_h2_conn_t *            conn,
     597             :            fd_h2_rbuf_t *            rbuf_rx,
     598             :            fd_h2_rbuf_t *            rbuf_tx,
     599             :            uchar *                   scratch,
     600             :            ulong                     scratch_sz,
     601          18 :            fd_h2_callbacks_t const * cb ) {
     602             :   /* All frames except DATA are fully buffered, thus assume that current
     603             :      frame is a DATA frame if rx_frame_rem != 0. */
     604          18 :   if( conn->rx_frame_rem ) {
     605           0 :     fd_h2_rx_data( conn, rbuf_rx, rbuf_tx, cb );
     606           0 :     return;
     607           0 :   }
     608          18 :   if( FD_UNLIKELY( conn->rx_pad_rem ) ) {
     609           0 :     ulong pad_rem    = conn->rx_pad_rem;
     610           0 :     ulong rbuf_avail = fd_h2_rbuf_used_sz( rbuf_rx );
     611           0 :     uint  chunk_sz   = (uint)fd_ulong_min( pad_rem, rbuf_avail );
     612           0 :     fd_h2_rbuf_skip( rbuf_rx, chunk_sz );
     613           0 :     conn->rx_pad_rem = (uchar)( conn->rx_pad_rem - chunk_sz );
     614           0 :     return;
     615           0 :   }
     616             : 
     617             :   /* A new frame starts.  Peek the header. */
     618          18 :   if( FD_UNLIKELY( fd_h2_rbuf_used_sz( rbuf_rx )<sizeof(fd_h2_frame_hdr_t) ) ) {
     619           0 :     conn->rx_suppress = rbuf_rx->lo_off + sizeof(fd_h2_frame_hdr_t);
     620           0 :     return;
     621           0 :   }
     622          18 :   fd_h2_rbuf_t rx_peek = *rbuf_rx;
     623          18 :   fd_h2_frame_hdr_t hdr;
     624          18 :   fd_h2_rbuf_pop_copy( &rx_peek, &hdr, sizeof(fd_h2_frame_hdr_t) );
     625          18 :   uint const frame_type = fd_h2_frame_type  ( hdr.typlen );
     626          18 :   uint const frame_sz   = fd_h2_frame_length( hdr.typlen );
     627             : 
     628          18 :   if( FD_UNLIKELY( frame_sz > conn->self_settings.max_frame_size ) ) {
     629           0 :     fd_h2_conn_error( conn, FD_H2_ERR_FRAME_SIZE );
     630           0 :     return;
     631           0 :   }
     632          18 :   if( FD_UNLIKELY( (!!( conn->flags & FD_H2_CONN_FLAGS_CONTINUATION ) ) &
     633          18 :                    (    frame_type!=FD_H2_FRAME_TYPE_CONTINUATION     ) ) ) {
     634           0 :     fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     635           0 :     return;
     636           0 :   }
     637             : 
     638             :   /* Peek padding */
     639          18 :   uint pad_sz = 0U;
     640          18 :   uint rem_sz = frame_sz;
     641          18 :   if( ( frame_type==FD_H2_FRAME_TYPE_DATA    ||
     642          18 :         frame_type==FD_H2_FRAME_TYPE_HEADERS ||
     643          18 :         frame_type==FD_H2_FRAME_TYPE_PUSH_PROMISE ) &&
     644          18 :       !!( hdr.flags & FD_H2_FLAG_PADDED ) ) {
     645           0 :     if( FD_UNLIKELY( fd_h2_rbuf_used_sz( &rx_peek )<1UL ) ) return;
     646           0 :     pad_sz = rx_peek.lo[0];
     647           0 :     rem_sz--;
     648           0 :     if( FD_UNLIKELY( pad_sz>=rem_sz ) ) {
     649           0 :       fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     650           0 :       return;
     651           0 :     }
     652           0 :     fd_h2_rbuf_skip( &rx_peek, 1UL );
     653           0 :   }
     654             : 
     655             :   /* Special case: Process data incrementally */
     656          18 :   if( frame_type==FD_H2_FRAME_TYPE_DATA ) {
     657           0 :     conn->rx_frame_rem   = rem_sz;
     658           0 :     conn->rx_frame_flags = hdr.flags;
     659           0 :     conn->rx_stream_id   = fd_h2_frame_stream_id( hdr.r_stream_id );
     660           0 :     conn->rx_pad_rem     = (uchar)pad_sz;
     661           0 :     *rbuf_rx = rx_peek;
     662           0 :     if( FD_UNLIKELY( !conn->rx_stream_id ) ) {
     663           0 :       fd_h2_conn_error( conn, FD_H2_ERR_PROTOCOL );
     664           0 :       return;
     665           0 :     }
     666           0 :     fd_h2_rx_data( conn, rbuf_rx, rbuf_tx, cb );
     667           0 :     return;
     668           0 :   }
     669             : 
     670             :   /* Consume all or nothing */
     671          18 :   ulong const tot_sz = sizeof(fd_h2_frame_hdr_t) + frame_sz;
     672          18 :   if( FD_UNLIKELY( tot_sz>fd_h2_rbuf_used_sz( rbuf_rx ) ) ) {
     673           0 :     conn->rx_suppress = rbuf_rx->lo_off + tot_sz;
     674           0 :     return;
     675           0 :   }
     676             : 
     677          18 :   uint payload_sz = rem_sz-pad_sz;
     678          18 :   if( FD_UNLIKELY( scratch_sz < payload_sz ) ) {
     679           0 :     if( FD_UNLIKELY( scratch_sz < conn->self_settings.max_frame_size ) ) {
     680           0 :       FD_LOG_WARNING(( "scratch buffer too small: scratch_sz=%lu max_frame_size=%u)",
     681           0 :                        scratch_sz, conn->self_settings.max_frame_size ));
     682           0 :       fd_h2_conn_error( conn, FD_H2_ERR_INTERNAL );
     683           0 :       return;
     684           0 :     }
     685           0 :     fd_h2_conn_error( conn, FD_H2_ERR_FRAME_SIZE );
     686           0 :     return;
     687           0 :   }
     688             : 
     689          18 :   *rbuf_rx = rx_peek;
     690          18 :   uchar * frame = fd_h2_rbuf_pop( rbuf_rx, scratch, payload_sz );
     691          18 :   int ok =
     692          18 :     fd_h2_rx_frame( conn, rbuf_tx, frame, payload_sz, cb,
     693          18 :                     frame_type,
     694          18 :                     hdr.flags,
     695          18 :                     fd_h2_frame_stream_id( hdr.r_stream_id ) );
     696          18 :   (void)ok; /* FIXME */
     697          18 :   fd_h2_rbuf_skip( rbuf_rx, pad_sz );
     698          18 : }
     699             : 
     700             : void
     701             : fd_h2_rx( fd_h2_conn_t *            conn,
     702             :           fd_h2_rbuf_t *            rbuf_rx,
     703             :           fd_h2_rbuf_t *            rbuf_tx,
     704             :           uchar *                   scratch,
     705             :           ulong                     scratch_sz,
     706          18 :           fd_h2_callbacks_t const * cb ) {
     707             :   /* Pre-receive TX work */
     708             : 
     709             :   /* Stop handling frames on conn error. */
     710          18 :   if( FD_UNLIKELY( conn->flags & FD_H2_CONN_FLAGS_DEAD ) ) return;
     711             : 
     712             :   /* All other logic below can only proceed if new data arrived. */
     713          18 :   if( FD_UNLIKELY( !fd_h2_rbuf_used_sz( rbuf_rx ) ) ) return;
     714             : 
     715             :   /* Slowloris defense: Guess how much bytes are required to progress
     716             :      ahead of time based on the frame's type and size. */
     717          18 :   if( FD_UNLIKELY( rbuf_rx->hi_off < conn->rx_suppress ) ) return;
     718             : 
     719             :   /* Handle frames */
     720          18 :   for(;;) {
     721          18 :     ulong lo0 = rbuf_rx->lo_off;
     722          18 :     fd_h2_rx1( conn, rbuf_rx, rbuf_tx, scratch, scratch_sz, cb );
     723          18 :     ulong lo1 = rbuf_rx->lo_off;
     724             : 
     725             :     /* Terminate when no more bytes are available to read */
     726          18 :     if( !fd_h2_rbuf_used_sz( rbuf_rx ) ) break;
     727             : 
     728             :     /* Terminate when the frame handler didn't make progress (e.g. due
     729             :        to rbuf_tx full, or due to incomplete read from rbuf_tx)*/
     730           0 :     if( FD_UNLIKELY( lo0==lo1 ) ) break;
     731             : 
     732             :     /* Terminate if the conn died */
     733           0 :     if( FD_UNLIKELY( conn->flags & (FD_H2_CONN_FLAGS_SEND_GOAWAY|FD_H2_CONN_FLAGS_DEAD) ) ) break;
     734           0 :   }
     735          18 : }
     736             : 
     737             : void
     738             : fd_h2_tx_control( fd_h2_conn_t *            conn,
     739             :                   fd_h2_rbuf_t *            rbuf_tx,
     740          15 :                   fd_h2_callbacks_t const * cb ) {
     741             : 
     742          15 :   if( FD_UNLIKELY( fd_h2_rbuf_free_sz( rbuf_tx )<128 ) ) return;
     743             : 
     744          15 :   switch( fd_uint_find_lsb( (uint)conn->flags | 0x10000u ) ) {
     745             : 
     746           3 :   case FD_H2_CONN_FLAGS_LG_CLIENT_INITIAL:
     747           3 :     fd_h2_rbuf_push( rbuf_tx, fd_h2_client_preface, sizeof(fd_h2_client_preface) );
     748           3 :     __attribute__((fallthrough));
     749             : 
     750           3 :   case FD_H2_CONN_FLAGS_LG_SERVER_INITIAL: {
     751           3 :     uchar buf[ FD_H2_OUR_SETTINGS_ENCODED_SZ ];
     752           3 :     fd_h2_gen_settings( &conn->self_settings, buf );
     753           3 :     fd_h2_rbuf_push( rbuf_tx, buf, sizeof(buf) );
     754           3 :     conn->setting_tx++;
     755           3 :     conn->flags = FD_H2_CONN_FLAGS_WAIT_SETTINGS_0 | FD_H2_CONN_FLAGS_WAIT_SETTINGS_ACK_0;
     756           3 :     break;
     757           3 :   }
     758             : 
     759           0 : goaway:
     760           0 :   case FD_H2_CONN_FLAGS_LG_SEND_GOAWAY: {
     761           0 :     fd_h2_goaway_t goaway = {
     762           0 :       .hdr = {
     763           0 :         .typlen = fd_h2_frame_typlen( FD_H2_FRAME_TYPE_GOAWAY, 8UL )
     764           0 :       },
     765           0 :       .last_stream_id = 0, /* FIXME */
     766           0 :       .error_code     = fd_uint_bswap( (uint)conn->conn_error )
     767           0 :     };
     768           0 :     conn->flags = FD_H2_CONN_FLAGS_DEAD;
     769           0 :     fd_h2_rbuf_push( rbuf_tx, &goaway, sizeof(fd_h2_goaway_t) );
     770           0 :     cb->conn_final( conn, conn->conn_error, 0 /* local */ );
     771           0 :     break;
     772           0 :   }
     773             : 
     774           0 :   case FD_H2_CONN_FLAGS_LG_WINDOW_UPDATE: {
     775           0 :     uint increment = conn->rx_wnd_max - conn->rx_wnd;
     776           0 :     if( FD_UNLIKELY( increment>0x7fffffff ) ) {
     777           0 :       fd_h2_conn_error( conn, FD_H2_ERR_INTERNAL );
     778           0 :       goto goaway;
     779           0 :     }
     780           0 :     if( FD_UNLIKELY( increment==0 ) ) break;
     781           0 :     fd_h2_window_update_t window_update = {
     782           0 :       .hdr = {
     783           0 :         .typlen = fd_h2_frame_typlen( FD_H2_FRAME_TYPE_WINDOW_UPDATE, 4UL )
     784           0 :       },
     785           0 :       .increment = fd_uint_bswap( increment )
     786           0 :     };
     787           0 :     fd_h2_rbuf_push( rbuf_tx, &window_update, sizeof(fd_h2_window_update_t) );
     788           0 :     conn->rx_wnd = conn->rx_wnd_max;
     789           0 :     conn->flags = (ushort)( (conn->flags) & (~FD_H2_CONN_FLAGS_WINDOW_UPDATE) );
     790           0 :     break;
     791           0 :   }
     792             : 
     793          12 :   default:
     794          12 :     break;
     795             : 
     796          15 :   }
     797             : 
     798          15 : }

Generated by: LCOV version 1.14