Line data Source code
1 : #ifndef HEADER_fd_src_waltz_h2_fd_h2_stream_h 2 : #define HEADER_fd_src_waltz_h2_fd_h2_stream_h 3 : 4 : /* fd_h2_stream.h provides the HTTP/2 stream state machine. */ 5 : 6 : #include "fd_h2_base.h" 7 : #include "fd_h2_proto.h" 8 : #include "fd_h2_conn.h" 9 : 10 : /* The fd_h2_stream_t object holds the stream state machine. */ 11 : 12 : struct fd_h2_stream { 13 : uint stream_id; 14 : uint tx_wnd; /* transmit quota available */ 15 : uint rx_wnd; /* receive window bytes remaining */ 16 : 17 : uchar state; 18 : uchar hdrs_seq; 19 : uchar rx_aborted; /* explicit reset/error stops pending DATA delivery */ 20 : }; 21 : 22 0 : #define FD_H2_STREAM_STATE_IDLE 0 23 0 : #define FD_H2_STREAM_STATE_OPEN 1 24 0 : #define FD_H2_STREAM_STATE_CLOSING_TX 2 /* half-closed (local) */ 25 0 : #define FD_H2_STREAM_STATE_CLOSING_RX 3 /* half-closed (remote) */ 26 0 : #define FD_H2_STREAM_STATE_CLOSED 4 27 0 : #define FD_H2_STREAM_STATE_ILLEGAL 5 28 : 29 : FD_PROTOTYPES_BEGIN 30 : 31 : /* fd_h2_stream_init initializes a stream object. On return, the stream 32 : is in 'IDLE' state and does not have an assigned stream ID. */ 33 : 34 : static inline fd_h2_stream_t * 35 0 : fd_h2_stream_init( fd_h2_stream_t * stream ) { 36 0 : *stream = (fd_h2_stream_t){0}; 37 0 : return stream; 38 0 : } 39 : 40 : /* fd_h2_stream_is_tx returns 1 if the stream is a "TX stream" (i.e. a 41 : stream initiated by this endpoint), or 0 if the stream is a "RX 42 : stream" (i.e. a stream initiated by the peer). */ 43 : static inline int 44 : fd_h2_stream_is_tx( fd_h2_stream_t * stream, 45 0 : fd_h2_conn_t * conn ) { 46 : /* Streams initiated by a client MUST use odd-numbered stream identifiers; */ 47 0 : uint is_client_stream = stream->stream_id & 1; 48 : /* Are we a client or a server? This is determined by the parity of the first stream ID we 49 : send which stored in the parity of conn->tx_stream_next. */ 50 0 : uint is_client_self = conn->tx_stream_next & 1; 51 0 : return is_client_stream == is_client_self; 52 0 : } 53 : 54 : /* fd_h2_stream_open transitions a stream from 'IDLE' to 'OPEN'. 55 : In fd_h2, this happens when the local or peer side sends a HEADERS 56 : frame. The TX side of the stream assumes the peer's default send 57 : window. */ 58 : 59 : static inline fd_h2_stream_t * 60 : fd_h2_stream_open( fd_h2_stream_t * stream, 61 : fd_h2_conn_t * conn, 62 0 : uint stream_id ) { 63 0 : *stream = (fd_h2_stream_t) { 64 0 : .stream_id = stream_id, 65 0 : .state = FD_H2_STREAM_STATE_OPEN, 66 0 : .tx_wnd = conn->peer_settings.initial_window_size, 67 0 : .rx_wnd = conn->self_settings.initial_window_size, 68 0 : .hdrs_seq = 0U 69 0 : }; 70 0 : conn->stream_active_cnt[ fd_h2_stream_is_tx( stream, conn ) ]++; 71 0 : return stream; 72 0 : } 73 : 74 : static inline void 75 : fd_h2_stream_private_deactivate( fd_h2_stream_t * stream, 76 0 : fd_h2_conn_t * conn ) { 77 0 : int is_tx_stream = fd_h2_stream_is_tx( stream, conn ); 78 0 : FD_CHECK_CRIT( conn->stream_active_cnt[ is_tx_stream ]>0U, "corrupt state" ); 79 0 : conn->stream_active_cnt[ is_tx_stream ]--; 80 0 : } 81 : 82 : static inline void 83 : fd_h2_stream_error( fd_h2_stream_t * stream, 84 : fd_h2_conn_t * conn, 85 : fd_h2_rbuf_t * rbuf_tx, 86 0 : uint h2_err ) { 87 : /* An explicit abort stops delivery of a pending field block, even 88 : when the caller retains the closed stream in its map. */ 89 0 : if( FD_UNLIKELY( (conn->flags & FD_H2_CONN_FLAGS_CONTINUATION) && conn->rx_stream_id==stream->stream_id ) ) conn->rx_hdrs_discard = 1U; 90 0 : switch( stream->state ) { 91 0 : case FD_H2_STREAM_STATE_OPEN: 92 0 : case FD_H2_STREAM_STATE_CLOSING_TX: 93 0 : case FD_H2_STREAM_STATE_CLOSING_RX: 94 0 : fd_h2_tx_rst_stream( rbuf_tx, stream->stream_id, h2_err ); 95 : /* NO_ERROR after the peer's END_STREAM still delivers received data */ 96 0 : stream->rx_aborted = (uchar)( h2_err!=FD_H2_SUCCESS || stream->state!=FD_H2_STREAM_STATE_CLOSING_RX ); 97 0 : stream->state = FD_H2_STREAM_STATE_CLOSED; 98 0 : fd_h2_stream_private_deactivate( stream, conn ); 99 0 : break; 100 0 : case FD_H2_STREAM_STATE_CLOSED: 101 0 : if( h2_err!=FD_H2_SUCCESS ) stream->rx_aborted = 1U; 102 0 : break; 103 0 : default: 104 0 : stream->state = FD_H2_STREAM_STATE_ILLEGAL; 105 0 : break; 106 0 : } 107 0 : } 108 : 109 : /* fd_h2_rst_stream generates a RST_STREAM frame on the given stream. 110 : On return, the stream is in CLOSED state, and the underlying stream 111 : object and map entry can be discarded. conn->active_stream_cnt is 112 : decremented accordingly. */ 113 : 114 : void 115 : fd_h2_rst_stream( fd_h2_conn_t * conn, 116 : fd_h2_rbuf_t * rbuf_tx, 117 : fd_h2_stream_t * stream ); 118 : 119 : static inline void 120 : fd_h2_stream_close_rx( fd_h2_stream_t * stream, 121 0 : fd_h2_conn_t * conn ) { 122 0 : switch( stream->state ) { 123 0 : case FD_H2_STREAM_STATE_OPEN: 124 0 : stream->state = FD_H2_STREAM_STATE_CLOSING_RX; 125 0 : break; 126 0 : case FD_H2_STREAM_STATE_CLOSING_TX: 127 0 : stream->state = FD_H2_STREAM_STATE_CLOSED; 128 0 : fd_h2_stream_private_deactivate( stream, conn ); 129 0 : break; 130 0 : default: 131 0 : stream->state = FD_H2_STREAM_STATE_ILLEGAL; 132 0 : break; 133 0 : } 134 0 : } 135 : 136 : static inline void 137 : fd_h2_stream_close_tx( fd_h2_stream_t * stream, 138 0 : fd_h2_conn_t * conn ) { 139 0 : switch( stream->state ) { 140 0 : case FD_H2_STREAM_STATE_OPEN: 141 0 : stream->state = FD_H2_STREAM_STATE_CLOSING_TX; 142 0 : break; 143 0 : case FD_H2_STREAM_STATE_CLOSING_RX: 144 0 : stream->state = FD_H2_STREAM_STATE_CLOSED; 145 0 : fd_h2_stream_private_deactivate( stream, conn ); 146 0 : break; 147 0 : default: 148 0 : stream->state = FD_H2_STREAM_STATE_ILLEGAL; 149 0 : break; 150 0 : } 151 0 : } 152 : 153 : static inline void 154 : fd_h2_stream_reset( fd_h2_stream_t * stream, 155 0 : fd_h2_conn_t * conn ) { 156 0 : if( FD_UNLIKELY( (conn->flags & FD_H2_CONN_FLAGS_CONTINUATION) && conn->rx_stream_id==stream->stream_id ) ) conn->rx_hdrs_discard = 1U; 157 0 : stream->rx_aborted = 1U; 158 0 : switch( stream->state ) { 159 0 : case FD_H2_STREAM_STATE_OPEN: 160 0 : case FD_H2_STREAM_STATE_CLOSING_TX: 161 0 : case FD_H2_STREAM_STATE_CLOSING_RX: 162 0 : stream->state = FD_H2_STREAM_STATE_CLOSED; 163 0 : fd_h2_stream_private_deactivate( stream, conn ); 164 0 : break; 165 0 : default: 166 0 : stream->state = FD_H2_STREAM_STATE_ILLEGAL; 167 0 : break; 168 0 : } 169 0 : } 170 : 171 : static inline void 172 : fd_h2_stream_rx_headers( fd_h2_stream_t * stream, 173 : fd_h2_conn_t * conn, 174 0 : ulong flags ) { 175 0 : if( stream->state == FD_H2_STREAM_STATE_IDLE ) { 176 : /* FIXME This is probably redundant */ 177 0 : stream->state = FD_H2_STREAM_STATE_OPEN; 178 0 : } 179 0 : if( flags & FD_H2_FLAG_END_STREAM ) { 180 0 : fd_h2_stream_close_rx( stream, conn ); 181 0 : } 182 0 : if( flags & FD_H2_FLAG_END_HEADERS ) { 183 0 : stream->hdrs_seq = (uchar)( stream->hdrs_seq + 1 ); 184 0 : } 185 0 : } 186 : 187 : static inline void 188 : fd_h2_stream_rx_data( fd_h2_stream_t * stream, 189 : fd_h2_conn_t * conn, 190 0 : ulong flags ) { 191 0 : if( flags & FD_H2_FLAG_END_STREAM ) { 192 0 : fd_h2_stream_close_rx( stream, conn ); 193 0 : } 194 0 : } 195 : 196 : FD_PROTOTYPES_END 197 : 198 : #endif /* HEADER_fd_src_waltz_h2_fd_h2_stream_h */