LCOV - code coverage report
Current view: top level - waltz/h2 - fd_h2_conn.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 19 83 22.9 %
Date: 2025-07-01 05:00:49 Functions: 1 60 1.7 %

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

Generated by: LCOV version 1.14