LCOV - code coverage report
Current view: top level - waltz/h2 - fd_h2_conn.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 55 83 66.3 %
Date: 2025-08-05 05:04:49 Functions: 5 66 7.6 %

          Line data    Source code
       1             : #ifndef HEADER_fd_src_waltz_h2_fd_h2_conn_h
       2             : #define HEADER_fd_src_waltz_h2_fd_h2_conn_h
       3             : 
       4             : /* fd_h2_conn.h provides the HTTP/2 connection state machine and utils
       5             :    for multiplexing frames. */
       6             : 
       7             : #include "fd_h2_rbuf.h"
       8             : #include "fd_h2_proto.h"
       9             : 
      10             : /* fd_h2_settings_t contains HTTP/2 settings that fd_h2 understands. */
      11             : 
      12             : struct fd_h2_settings {
      13             :   uint initial_window_size;
      14             :   uint max_frame_size;
      15             :   uint max_header_list_size;
      16             :   uint max_concurrent_streams;
      17             : };
      18             : 
      19             : typedef struct fd_h2_settings fd_h2_settings_t;
      20             : 
      21             : /* FD_H2_MAX_PENDING_SETTINGS limits the number of SETTINGS frames that
      22             :    fd_h2 can burst without an acknowledgement.  Aborts the conn with a
      23             :    TCP RST if fd_h2 or the peer pile on too many unacknowledged SETTINGS
      24             :    frames. */
      25             : 
      26             : #define FD_H2_MAX_PENDING_SETTINGS 64
      27             : 
      28             : /* fd_h2_conn is a framing-layer HTTP/2 connection handle.
      29             :    It implements RFC 9113 mandatory behavior, such as negotiating conn
      30             :    settings with a peer. */
      31             : 
      32             : struct fd_h2_conn {
      33             :   union { /* arbitrary value for use by caller */
      34             :     void * ctx;
      35             :     ulong  memo;
      36             :   };
      37             : 
      38             :   fd_h2_settings_t self_settings;
      39             :   fd_h2_settings_t peer_settings;
      40             : 
      41             :   uchar * tx_frame_p;     /* in-progress frame: first byte in rbuf */
      42             :   ulong   tx_payload_off; /* in-progress frame: cum sent byte cnt before payload */
      43             :   ulong   rx_suppress;    /* skip frame handlers until this RX offset */
      44             : 
      45             :   uint  rx_frame_rem;    /* current RX frame: payload bytes remaining */
      46             :   uint  rx_stream_id;    /* current RX frame: stream ID */
      47             :   uint  rx_stream_next;  /* next unused RX stream ID */
      48             : 
      49             :   uint  rx_wnd_wmark;    /* receive window refill threshold */
      50             :   uint  rx_wnd_max;      /* receive window max size */
      51             :   uint  rx_wnd;          /* receive window bytes remaining */
      52             : 
      53             :   uint  tx_stream_next;  /* next unused TX stream ID */
      54             :   uint  tx_wnd;          /* transmit quota available */
      55             : 
      56             :   uint  stream_active_cnt[2]; /* currently active {rx,tx} streams  */
      57             : 
      58             :   ushort flags;           /* bit set of FD_H2_CONN_FLAGS_* */
      59             :   uchar  conn_error;
      60             :   uchar  setting_tx;      /* no of sent SETTINGS frames pending their ACK */
      61             :   uchar  rx_frame_flags;  /* current RX frame: flags */
      62             :   uchar  rx_pad_rem;      /* current RX frame: pad bytes remaining */
      63             :   uchar  ping_tx;         /* no of sent PING frames pending their ACK */
      64             : };
      65             : 
      66             : /* FD_H2_CONN_FLAGS_* give flags related to conn lifecycle */
      67             : 
      68           3 : #define FD_H2_CONN_FLAGS_LG_DEAD                (0) /* conn has passed */
      69           3 : #define FD_H2_CONN_FLAGS_LG_SEND_GOAWAY         (1) /* send GOAWAY */
      70           0 : #define FD_H2_CONN_FLAGS_LG_CONTINUATION        (2) /* next frame must be CONTINUATION */
      71          78 : #define FD_H2_CONN_FLAGS_LG_CLIENT_INITIAL      (3) /* send preface, SETTINGS */
      72          21 : #define FD_H2_CONN_FLAGS_LG_WAIT_SETTINGS_ACK_0 (4) /* wait for initial ACK of initial SETTINGS */
      73          21 : #define FD_H2_CONN_FLAGS_LG_WAIT_SETTINGS_0     (5) /* wait for peer's initial SETTINGS */
      74           6 : #define FD_H2_CONN_FLAGS_LG_SERVER_INITIAL      (6) /* wait for client preface, then send settings */
      75           0 : #define FD_H2_CONN_FLAGS_LG_WINDOW_UPDATE       (7) /* send WINDOW_UPDATE */
      76             : 
      77           3 : #define FD_H2_CONN_FLAGS_DEAD                ((uchar)( 1U<<FD_H2_CONN_FLAGS_LG_DEAD                ))
      78           3 : #define FD_H2_CONN_FLAGS_SEND_GOAWAY         ((uchar)( 1U<<FD_H2_CONN_FLAGS_LG_SEND_GOAWAY         ))
      79           0 : #define FD_H2_CONN_FLAGS_CONTINUATION        ((uchar)( 1U<<FD_H2_CONN_FLAGS_LG_CONTINUATION        ))
      80          75 : #define FD_H2_CONN_FLAGS_CLIENT_INITIAL      ((uchar)( 1U<<FD_H2_CONN_FLAGS_LG_CLIENT_INITIAL      ))
      81          21 : #define FD_H2_CONN_FLAGS_WAIT_SETTINGS_ACK_0 ((uchar)( 1U<<FD_H2_CONN_FLAGS_LG_WAIT_SETTINGS_ACK_0 ))
      82          21 : #define FD_H2_CONN_FLAGS_WAIT_SETTINGS_0     ((uchar)( 1U<<FD_H2_CONN_FLAGS_LG_WAIT_SETTINGS_0     ))
      83           3 : #define FD_H2_CONN_FLAGS_SERVER_INITIAL      ((uchar)( 1U<<FD_H2_CONN_FLAGS_LG_SERVER_INITIAL      ))
      84           0 : #define FD_H2_CONN_FLAGS_WINDOW_UPDATE       ((uchar)( 1U<<FD_H2_CONN_FLAGS_LG_WINDOW_UPDATE       ))
      85             : 
      86             : /* A connection is established when no more handshake-related flags are
      87             :    sent.  Specifically: The connection preface was sent, the peer's
      88             :    preface was received, a SETTINGS frame was sent, a SETTINGS frame was
      89             :    received, a SETTINGS ACK was sent, and a SETTINGS ACK was received. */
      90             : 
      91          12 : #define FD_H2_CONN_FLAGS_HANDSHAKING (0xf0)
      92             : 
      93             : FD_PROTOTYPES_BEGIN
      94             : 
      95             : /* fd_h2_conn_init_{client,server} bootstraps a conn object for use as a
      96             :    {client,server}-side HTTP/2 connection.  Returns conn on success, or
      97             :    NULL on failure (logs warning).  This call is currently infallible so
      98             :    there are no failure conditions.  The caller should check for failure
      99             :    regardless (future proofing).
     100             : 
     101             :    fd_h2_conn_init_server assumes that the client preface was already
     102             :    received from the incoming stream.  The preface is the 24 byte
     103             :    constant string fd_h2_client_preface. */
     104             : 
     105             : fd_h2_conn_t *
     106             : fd_h2_conn_init_client( fd_h2_conn_t * conn );
     107             : 
     108             : fd_h2_conn_t *
     109             : fd_h2_conn_init_server( fd_h2_conn_t * conn );
     110             : 
     111             : extern char const fd_h2_client_preface[24];
     112             : 
     113             : /* fd_h2_conn_fini destroys a h2_conn object.  Since h2_conn has no
     114             :    references or ownership over external objects, this is a no-op. */
     115             : 
     116             : void *
     117             : fd_h2_conn_fini( fd_h2_conn_t * conn );
     118             : 
     119             : /* fd_h2_rx consumes as much incoming bytes from rbuf_rx as possible.
     120             :    May write control messages to rbuf_out.  Stops when no more data in
     121             :    rbuf_tx is full, rbuf_rx is available, or a connection error
     122             :    occurred.  scratch points to a scratch buffer used for reassembly.
     123             :    Assumes scratch_sz >= conn.self_settings.max_frame_size. */
     124             : 
     125             : void
     126             : fd_h2_rx( fd_h2_conn_t *            conn,
     127             :           fd_h2_rbuf_t *            rbuf_rx,
     128             :           fd_h2_rbuf_t *            rbuf_tx,
     129             :           uchar *                   scratch,
     130             :           ulong                     scratch_sz,
     131             :           fd_h2_callbacks_t const * cb );
     132             : 
     133             : /* fd_h2_tx_control writes out control messages to rbuf_out.  Should be
     134             :    called immediately when creating a conn or whenever a timer
     135             :    expires. */
     136             : 
     137             : void
     138             : fd_h2_tx_control( fd_h2_conn_t *            conn,
     139             :                   fd_h2_rbuf_t *            rbuf_tx,
     140             :                   fd_h2_callbacks_t const * cb );
     141             : 
     142             : /* fd_h2_tx_check_sz checks whether rbuf_tx has enough space to buffer
     143             :    a frame for sending.  frame_max is the max frame size to check for.
     144             :    Returns 1 if the send buffer has enough space to hold a frame, 0
     145             :    otherwise.
     146             : 
     147             :    Note that the caller should separately do the following checks:
     148             :    - TCP send window space (optional, if fast delivery is desired)
     149             :    - HTTP/2 connection send window space
     150             :    - HTTP/2 stream limit check
     151             :    - HTTP/2 stream send window space */
     152             : 
     153             : static inline int
     154             : fd_h2_tx_check_sz( fd_h2_rbuf_t const * rbuf_tx,
     155           0 :                    ulong                frame_max ) {
     156           0 :   /* Calculate the wire size of a frame */
     157           0 :   ulong tot_sz = 9UL + frame_max;
     158           0 :   /* Leave some room in rbuf_tx for control frames */
     159           0 :   ulong req_sz = tot_sz + 64UL;
     160           0 :   return fd_h2_rbuf_free_sz( rbuf_tx ) >= req_sz;
     161           0 : }
     162             : 
     163             : /* fd_h2_tx_prepare appends a partial frame header to rbuf_tx.  Assumes
     164             :    that rbuf_tx has enough space to write a frame header.  On return,
     165             :    the caller can start appending the actual payload to rbuf_tx. */
     166             : 
     167             : static inline void
     168             : fd_h2_tx_prepare( fd_h2_conn_t * conn,
     169             :                   fd_h2_rbuf_t * rbuf_tx,
     170             :                   uint           frame_type,
     171             :                   uint           flags,
     172          18 :                   uint           stream_id ) {
     173          18 :   if( FD_UNLIKELY( conn->tx_frame_p ) ) {
     174           0 :     FD_LOG_CRIT(( "Mismatched fd_h2_tx_prepare" ));
     175           0 :   }
     176             : 
     177          18 :   conn->tx_frame_p     = rbuf_tx->hi;
     178          18 :   conn->tx_payload_off = rbuf_tx->hi_off + 9;
     179             : 
     180          18 :   fd_h2_frame_hdr_t hdr = {
     181          18 :     .typlen      = fd_h2_frame_typlen( frame_type, 0 ),
     182          18 :     .flags       = (uchar)flags,
     183          18 :     .r_stream_id = fd_uint_bswap( stream_id )
     184          18 :   };
     185          18 :   fd_h2_rbuf_push( rbuf_tx, &hdr, sizeof(fd_h2_frame_hdr_t) );
     186          18 : }
     187             : 
     188             : /* fd_h2_tx_commit finishes up a HTTP/2 frame. */
     189             : 
     190             : static inline void
     191             : fd_h2_tx_commit( fd_h2_conn_t *       conn,
     192          18 :                  fd_h2_rbuf_t const * rbuf_tx ) {
     193          18 :   ulong   off0  = conn->tx_payload_off;
     194          18 :   ulong   off1  = rbuf_tx->hi_off;
     195          18 :   uchar * buf0  = rbuf_tx->buf0;
     196          18 :   uchar * buf1  = rbuf_tx->buf1;
     197          18 :   ulong   bufsz = rbuf_tx->bufsz;
     198          18 :   uchar * frame = conn->tx_frame_p;
     199          18 :   uchar * sz0   = frame;
     200          18 :   uchar * sz1   = frame+1;
     201          18 :   uchar * sz2   = frame+2;
     202          18 :   sz1 = sz1>=buf1 ? sz1-bufsz : sz1;
     203          18 :   sz2 = sz2>=buf1 ? sz2-bufsz : sz2;
     204             : 
     205          18 :   if( FD_UNLIKELY( frame<buf0 || frame>=buf1 ) ) {
     206           0 :     FD_LOG_CRIT(( "Can't finish frame: rbuf_tx doesn't match" ));
     207           0 :   }
     208             : 
     209          18 :   ulong write_sz = (ulong)( off1-off0 );
     210             :   /* FIXME check write_sz? */
     211          18 :   *sz0 = (uchar)( write_sz>>16 );
     212          18 :   *sz1 = (uchar)( write_sz>> 8 );
     213          18 :   *sz2 = (uchar)( write_sz     );
     214             : 
     215          18 :   conn->tx_frame_p     = NULL;
     216          18 :   conn->tx_payload_off = 0UL;
     217          18 : }
     218             : 
     219             : /* fd_h2_tx is a slow streamlined variant of fd_h2_tx_{prepare,commit}.
     220             :    This variant assumes that the frame paylaod is already available in
     221             :    a separate buffer. */
     222             : 
     223             : static inline void
     224             : fd_h2_tx( fd_h2_rbuf_t * rbuf_tx,
     225             :           uchar const *  payload,
     226             :           ulong          payload_sz,
     227             :           uint           frame_type,
     228             :           uint           flags,
     229           0 :           uint           stream_id ) {
     230           0 :   fd_h2_frame_hdr_t hdr = {
     231           0 :     .typlen      = fd_h2_frame_typlen( frame_type, payload_sz ),
     232           0 :     .flags       = (uchar)flags,
     233           0 :     .r_stream_id = fd_uint_bswap( stream_id )
     234           0 :   };
     235           0 :   fd_h2_rbuf_push( rbuf_tx, &hdr, sizeof(fd_h2_frame_hdr_t) );
     236           0 :   fd_h2_rbuf_push( rbuf_tx, payload, payload_sz );
     237           0 : }
     238             : 
     239             : /* fd_h2_tx_ping attempts to enqueue a PING frame for sending
     240             :    (in fd_h2_tx_control). */
     241             : 
     242             : int
     243             : fd_h2_tx_ping( fd_h2_conn_t * conn,
     244             :                fd_h2_rbuf_t * rbuf_tx );
     245             : 
     246             : /* fd_h2_conn_error enqueues a GOAWAY frame for sending
     247             :    (in fd_h2_tx_control). */
     248             : 
     249             : static inline void
     250             : fd_h2_conn_error( fd_h2_conn_t * conn,
     251           0 :                   uint           err_code ) {
     252             :   /* Clear all other flags */
     253           0 :   conn->flags = FD_H2_CONN_FLAGS_SEND_GOAWAY;
     254           0 :   conn->conn_error = (uchar)err_code;
     255           0 : }
     256             : 
     257             : /* fd_h2_tx_rst_stream writes a RST_STREAM frame for sending.  rbuf_tx
     258             :    must have at least sizeof(fd_h2_rst_stream_t) free space.  (This is
     259             :    a low-level API) */
     260             : 
     261             : static inline void
     262             : fd_h2_tx_rst_stream( fd_h2_rbuf_t * rbuf_tx,
     263             :                      uint           stream_id,
     264          18 :                      uint           h2_err ) {
     265          18 :   fd_h2_rst_stream_t rst_stream = {
     266          18 :     .hdr = {
     267          18 :       .typlen      = fd_h2_frame_typlen( FD_H2_FRAME_TYPE_RST_STREAM, 4UL ),
     268          18 :       .flags       = 0U,
     269          18 :       .r_stream_id = fd_uint_bswap( stream_id )
     270          18 :     },
     271          18 :     .error_code = fd_uint_bswap( h2_err )
     272          18 :   };
     273          18 :   fd_h2_rbuf_push( rbuf_tx, &rst_stream, sizeof(fd_h2_rst_stream_t) );
     274          18 : }
     275             : 
     276             : FD_PROTOTYPES_END
     277             : 
     278             : #endif /* HEADER_fd_src_waltz_h2_fd_h2_conn_h */

Generated by: LCOV version 1.14