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