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

Generated by: LCOV version 1.14