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 */
|