vxx2 / vlib / net / http / h2_frame.v
529 lines · 492 sloc · 14.4 KB · 89607c731290ee2c148c11541f73c40b23b00481
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module http
5
6// This file implements the HTTP/2 binary framing layer (RFC 7540 Sections 4
7// and 6). It is self-contained and does not touch the rest of net.http; the
8// connection layer (added separately) drives it.
9
10// HTTP/2 frame types (RFC 7540 Section 6).
11pub const h2_frame_data = u8(0x0)
12pub const h2_frame_headers = u8(0x1)
13pub const h2_frame_priority = u8(0x2)
14pub const h2_frame_rst_stream = u8(0x3)
15pub const h2_frame_settings = u8(0x4)
16pub const h2_frame_push_promise = u8(0x5)
17pub const h2_frame_ping = u8(0x6)
18pub const h2_frame_goaway = u8(0x7)
19pub const h2_frame_window_update = u8(0x8)
20pub const h2_frame_continuation = u8(0x9)
21
22// HTTP/2 frame flags (RFC 7540 Section 6). The same bit is reused across
23// frame types with different meanings.
24pub const h2_flag_end_stream = u8(0x1)
25pub const h2_flag_ack = u8(0x1)
26pub const h2_flag_end_headers = u8(0x4)
27pub const h2_flag_padded = u8(0x8)
28pub const h2_flag_priority = u8(0x20)
29
30// h2_frame_header_len is the fixed size of a frame header in bytes.
31pub const h2_frame_header_len = 9
32
33// h2_default_max_frame_size is the initial SETTINGS_MAX_FRAME_SIZE
34// (RFC 7540 Section 6.5.2), and the smallest value a peer may set it to.
35pub const h2_default_max_frame_size = u32(16384)
36
37// h2_max_max_frame_size is the largest permitted SETTINGS_MAX_FRAME_SIZE
38// (RFC 7540 Section 6.5.2): 2^24-1 bytes.
39pub const h2_max_max_frame_size = u32(16777215)
40
41// HTTP/2 setting identifiers (RFC 7540 Section 6.5.2).
42pub const h2_settings_header_table_size = u16(0x1)
43pub const h2_settings_enable_push = u16(0x2)
44pub const h2_settings_max_concurrent_streams = u16(0x3)
45pub const h2_settings_initial_window_size = u16(0x4)
46pub const h2_settings_max_frame_size = u16(0x5)
47pub const h2_settings_max_header_list_size = u16(0x6)
48
49// H2FrameHeader is the 9-byte header that precedes every HTTP/2 frame.
50pub struct H2FrameHeader {
51pub:
52 length u32 // 24-bit payload length
53 typ u8
54 flags u8
55 stream_id u32 // 31-bit; the reserved bit is ignored on read
56}
57
58pub struct H2DataFrame {
59pub:
60 stream_id u32
61 data []u8 // payload with any padding stripped
62 end_stream bool
63 // flow_size is the full received payload length (pad-length byte + data +
64 // padding) that counts toward flow control (RFC 7540 6.9.1). For outgoing
65 // frames it is left 0 and unused; the parser sets it for received frames.
66 flow_size int
67}
68
69pub struct H2HeadersFrame {
70pub:
71 stream_id u32
72 fragment []u8 // HPACK header block fragment
73 end_stream bool
74 end_headers bool
75 // Priority information, present only when the PRIORITY flag is set.
76 has_priority bool
77 exclusive bool
78 stream_dep u32
79 weight u8
80}
81
82pub struct H2PriorityFrame {
83pub:
84 stream_id u32
85 exclusive bool
86 stream_dep u32
87 weight u8
88}
89
90pub struct H2RstStreamFrame {
91pub:
92 stream_id u32
93 error_code u32
94}
95
96pub struct H2Setting {
97pub:
98 id u16
99 value u32
100}
101
102pub struct H2SettingsFrame {
103pub:
104 ack bool
105 settings []H2Setting
106}
107
108pub struct H2PushPromiseFrame {
109pub:
110 stream_id u32
111 promised_stream_id u32
112 fragment []u8
113 end_headers bool
114}
115
116pub struct H2PingFrame {
117pub:
118 ack bool
119 data []u8 // 8 opaque bytes
120}
121
122pub struct H2GoawayFrame {
123pub:
124 last_stream_id u32
125 error_code u32
126 debug_data []u8
127}
128
129pub struct H2WindowUpdateFrame {
130pub:
131 stream_id u32
132 window_size_increment u32
133}
134
135pub struct H2ContinuationFrame {
136pub:
137 stream_id u32
138 fragment []u8
139 end_headers bool
140}
141
142// H2UnknownFrame preserves a frame of an unrecognised type, which receivers
143// must ignore (RFC 7540 Section 4.1) but may want to inspect or forward.
144pub struct H2UnknownFrame {
145pub:
146 header H2FrameHeader
147 payload []u8
148}
149
150// H2Frame is any HTTP/2 frame.
151pub type H2Frame = H2ContinuationFrame
152 | H2DataFrame
153 | H2GoawayFrame
154 | H2HeadersFrame
155 | H2PingFrame
156 | H2PriorityFrame
157 | H2PushPromiseFrame
158 | H2RstStreamFrame
159 | H2SettingsFrame
160 | H2UnknownFrame
161 | H2WindowUpdateFrame
162
163// --- Big-endian helpers ---
164
165fn h2_be_u16(b []u8, o int) u16 {
166 return (u16(b[o]) << 8) | u16(b[o + 1])
167}
168
169fn h2_be_u24(b []u8, o int) u32 {
170 return (u32(b[o]) << 16) | (u32(b[o + 1]) << 8) | u32(b[o + 2])
171}
172
173fn h2_be_u32(b []u8, o int) u32 {
174 return (u32(b[o]) << 24) | (u32(b[o + 1]) << 16) | (u32(b[o + 2]) << 8) | u32(b[o + 3])
175}
176
177fn h2_put_u16(mut b []u8, v u16) {
178 b << u8(v >> 8)
179 b << u8(v)
180}
181
182fn h2_put_u24(mut b []u8, v u32) {
183 b << u8(v >> 16)
184 b << u8(v >> 8)
185 b << u8(v)
186}
187
188fn h2_put_u32(mut b []u8, v u32) {
189 b << u8(v >> 24)
190 b << u8(v >> 16)
191 b << u8(v >> 8)
192 b << u8(v)
193}
194
195// --- Frame header ---
196
197// h2_parse_frame_header parses the 9-byte frame header at the start of `buf`.
198pub fn h2_parse_frame_header(buf []u8) !H2FrameHeader {
199 if buf.len < h2_frame_header_len {
200 return error('h2: frame header truncated')
201 }
202 return H2FrameHeader{
203 length: h2_be_u24(buf, 0)
204 typ: buf[3]
205 flags: buf[4]
206 stream_id: h2_be_u32(buf, 5) & 0x7fff_ffff
207 }
208}
209
210// --- Decoding ---
211
212// h2_read_frame parses one frame (header + payload) from the start of `buf`,
213// returning the frame and the number of bytes consumed. The caller is
214// responsible for enforcing the negotiated SETTINGS_MAX_FRAME_SIZE.
215pub fn h2_read_frame(buf []u8) !(H2Frame, int) {
216 header := h2_parse_frame_header(buf)!
217 total := h2_frame_header_len + int(header.length)
218 if buf.len < total {
219 return error('h2: frame payload truncated (need ${total}, have ${buf.len})')
220 }
221 payload := buf[h2_frame_header_len..total]
222 frame := h2_parse_frame(header, payload)!
223 return frame, total
224}
225
226// h2_strip_padding removes the optional pad-length prefix byte and the
227// trailing padding from a frame payload (RFC 7540 Section 6.1).
228fn h2_strip_padding(payload []u8, padded bool) ![]u8 {
229 if !padded {
230 return payload
231 }
232 if payload.len < 1 {
233 return error('h2: padded frame missing pad length')
234 }
235 pad_len := int(payload[0])
236 if 1 + pad_len > payload.len {
237 return error('h2: pad length exceeds frame payload')
238 }
239 return payload[1..payload.len - pad_len]
240}
241
242// h2_parse_frame decodes a frame from an already-parsed header and its payload.
243pub fn h2_parse_frame(header H2FrameHeader, payload []u8) !H2Frame {
244 match header.typ {
245 h2_frame_data {
246 if header.stream_id == 0 {
247 return error('h2: DATA frame on stream 0')
248 }
249 body := h2_strip_padding(payload, header.flags & h2_flag_padded != 0)!
250 return H2DataFrame{
251 stream_id: header.stream_id
252 data: body.clone()
253 end_stream: header.flags & h2_flag_end_stream != 0
254 flow_size: payload.len
255 }
256 }
257 h2_frame_headers {
258 if header.stream_id == 0 {
259 return error('h2: HEADERS frame on stream 0')
260 }
261 mut body := h2_strip_padding(payload, header.flags & h2_flag_padded != 0)!
262 mut has_priority := false
263 mut exclusive := false
264 mut stream_dep := u32(0)
265 mut weight := u8(0)
266 if header.flags & h2_flag_priority != 0 {
267 if body.len < 5 {
268 return error('h2: HEADERS priority section truncated')
269 }
270 dep := h2_be_u32(body, 0)
271 has_priority = true
272 exclusive = dep & 0x8000_0000 != 0
273 stream_dep = dep & 0x7fff_ffff
274 weight = body[4]
275 body = unsafe { body[5..] }
276 }
277 return H2HeadersFrame{
278 stream_id: header.stream_id
279 fragment: body.clone()
280 end_stream: header.flags & h2_flag_end_stream != 0
281 end_headers: header.flags & h2_flag_end_headers != 0
282 has_priority: has_priority
283 exclusive: exclusive
284 stream_dep: stream_dep
285 weight: weight
286 }
287 }
288 h2_frame_priority {
289 if header.stream_id == 0 {
290 return error('h2: PRIORITY frame on stream 0')
291 }
292 if payload.len != 5 {
293 return error('h2: PRIORITY frame must be 5 bytes')
294 }
295 // Note: a stream depending on itself (stream_dep == stream_id) is a
296 // stream error (RFC 7540 Section 5.3.1), and a zero stream
297 // dependency is otherwise valid. These are semantic checks left to
298 // the connection layer, which must respond with RST_STREAM on the
299 // affected stream rather than tearing down the whole connection.
300 dep := h2_be_u32(payload, 0)
301 return H2PriorityFrame{
302 stream_id: header.stream_id
303 exclusive: dep & 0x8000_0000 != 0
304 stream_dep: dep & 0x7fff_ffff
305 weight: payload[4]
306 }
307 }
308 h2_frame_rst_stream {
309 if header.stream_id == 0 {
310 return error('h2: RST_STREAM frame on stream 0')
311 }
312 if payload.len != 4 {
313 return error('h2: RST_STREAM frame must be 4 bytes')
314 }
315 return H2RstStreamFrame{
316 stream_id: header.stream_id
317 error_code: h2_be_u32(payload, 0)
318 }
319 }
320 h2_frame_settings {
321 if header.stream_id != 0 {
322 return error('h2: SETTINGS frame on non-zero stream')
323 }
324 ack := header.flags & h2_flag_ack != 0
325 if ack {
326 if payload.len != 0 {
327 return error('h2: SETTINGS ACK must have empty payload')
328 }
329 return H2SettingsFrame{
330 ack: true
331 }
332 }
333 if payload.len % 6 != 0 {
334 return error('h2: SETTINGS payload not a multiple of 6')
335 }
336 mut settings := []H2Setting{cap: payload.len / 6}
337 for i := 0; i < payload.len; i += 6 {
338 settings << H2Setting{
339 id: h2_be_u16(payload, i)
340 value: h2_be_u32(payload, i + 2)
341 }
342 }
343 return H2SettingsFrame{
344 ack: false
345 settings: settings
346 }
347 }
348 h2_frame_push_promise {
349 if header.stream_id == 0 {
350 return error('h2: PUSH_PROMISE frame on stream 0')
351 }
352 mut body := h2_strip_padding(payload, header.flags & h2_flag_padded != 0)!
353 if body.len < 4 {
354 return error('h2: PUSH_PROMISE missing promised stream id')
355 }
356 promised := h2_be_u32(body, 0) & 0x7fff_ffff
357 return H2PushPromiseFrame{
358 stream_id: header.stream_id
359 promised_stream_id: promised
360 fragment: body[4..].clone()
361 end_headers: header.flags & h2_flag_end_headers != 0
362 }
363 }
364 h2_frame_ping {
365 if header.stream_id != 0 {
366 return error('h2: PING frame on non-zero stream')
367 }
368 if payload.len != 8 {
369 return error('h2: PING frame must be 8 bytes')
370 }
371 return H2PingFrame{
372 ack: header.flags & h2_flag_ack != 0
373 data: payload.clone()
374 }
375 }
376 h2_frame_goaway {
377 if header.stream_id != 0 {
378 return error('h2: GOAWAY frame on non-zero stream')
379 }
380 if payload.len < 8 {
381 return error('h2: GOAWAY frame too short')
382 }
383 return H2GoawayFrame{
384 last_stream_id: h2_be_u32(payload, 0) & 0x7fff_ffff
385 error_code: h2_be_u32(payload, 4)
386 debug_data: payload[8..].clone()
387 }
388 }
389 h2_frame_window_update {
390 if payload.len != 4 {
391 return error('h2: WINDOW_UPDATE frame must be 4 bytes')
392 }
393 // Note: a zero increment is an error (RFC 7540 Section 6.9) — a
394 // stream error on a stream, but a connection error on stream 0.
395 // That stream-vs-connection distinction is the connection layer's
396 // responsibility, so it is not rejected here.
397 return H2WindowUpdateFrame{
398 stream_id: header.stream_id
399 window_size_increment: h2_be_u32(payload, 0) & 0x7fff_ffff
400 }
401 }
402 h2_frame_continuation {
403 if header.stream_id == 0 {
404 return error('h2: CONTINUATION frame on stream 0')
405 }
406 return H2ContinuationFrame{
407 stream_id: header.stream_id
408 fragment: payload.clone()
409 end_headers: header.flags & h2_flag_end_headers != 0
410 }
411 }
412 else {
413 // Unknown frame types must be ignored (RFC 7540 Section 4.1);
414 // preserve them so the caller can decide.
415 return H2UnknownFrame{
416 header: header
417 payload: payload.clone()
418 }
419 }
420 }
421}
422
423// --- Encoding ---
424
425// h2_frame_bytes builds a complete frame from its parts.
426fn h2_frame_bytes(typ u8, flags u8, stream_id u32, payload []u8) []u8 {
427 mut b := []u8{cap: h2_frame_header_len + payload.len}
428 h2_put_u24(mut b, u32(payload.len))
429 b << typ
430 b << flags
431 h2_put_u32(mut b, stream_id & 0x7fff_ffff)
432 b << payload
433 return b
434}
435
436// encode serialises a frame to its on-the-wire bytes. The encoder never emits
437// padding.
438pub fn (f H2Frame) encode() []u8 {
439 match f {
440 H2DataFrame {
441 flags := if f.end_stream { h2_flag_end_stream } else { u8(0) }
442 return h2_frame_bytes(h2_frame_data, flags, f.stream_id, f.data)
443 }
444 H2HeadersFrame {
445 mut flags := u8(0)
446 if f.end_stream {
447 flags |= h2_flag_end_stream
448 }
449 if f.end_headers {
450 flags |= h2_flag_end_headers
451 }
452 mut payload := []u8{}
453 if f.has_priority {
454 flags |= h2_flag_priority
455 mut dep := f.stream_dep & 0x7fff_ffff
456 if f.exclusive {
457 dep |= 0x8000_0000
458 }
459 h2_put_u32(mut payload, dep)
460 payload << f.weight
461 }
462 payload << f.fragment
463 return h2_frame_bytes(h2_frame_headers, flags, f.stream_id, payload)
464 }
465 H2PriorityFrame {
466 mut payload := []u8{cap: 5}
467 mut dep := f.stream_dep & 0x7fff_ffff
468 if f.exclusive {
469 dep |= 0x8000_0000
470 }
471 h2_put_u32(mut payload, dep)
472 payload << f.weight
473 return h2_frame_bytes(h2_frame_priority, 0, f.stream_id, payload)
474 }
475 H2RstStreamFrame {
476 mut payload := []u8{cap: 4}
477 h2_put_u32(mut payload, f.error_code)
478 return h2_frame_bytes(h2_frame_rst_stream, 0, f.stream_id, payload)
479 }
480 H2SettingsFrame {
481 if f.ack {
482 return h2_frame_bytes(h2_frame_settings, h2_flag_ack, 0, [])
483 }
484 mut payload := []u8{cap: f.settings.len * 6}
485 for s in f.settings {
486 h2_put_u16(mut payload, s.id)
487 h2_put_u32(mut payload, s.value)
488 }
489 return h2_frame_bytes(h2_frame_settings, 0, 0, payload)
490 }
491 H2PushPromiseFrame {
492 mut flags := u8(0)
493 if f.end_headers {
494 flags |= h2_flag_end_headers
495 }
496 mut payload := []u8{cap: 4 + f.fragment.len}
497 h2_put_u32(mut payload, f.promised_stream_id & 0x7fff_ffff)
498 payload << f.fragment
499 return h2_frame_bytes(h2_frame_push_promise, flags, f.stream_id, payload)
500 }
501 H2PingFrame {
502 flags := if f.ack { h2_flag_ack } else { u8(0) }
503 mut payload := []u8{len: 8}
504 for i in 0 .. 8 {
505 payload[i] = if i < f.data.len { f.data[i] } else { u8(0) }
506 }
507 return h2_frame_bytes(h2_frame_ping, flags, 0, payload)
508 }
509 H2GoawayFrame {
510 mut payload := []u8{cap: 8 + f.debug_data.len}
511 h2_put_u32(mut payload, f.last_stream_id & 0x7fff_ffff)
512 h2_put_u32(mut payload, f.error_code)
513 payload << f.debug_data
514 return h2_frame_bytes(h2_frame_goaway, 0, 0, payload)
515 }
516 H2WindowUpdateFrame {
517 mut payload := []u8{cap: 4}
518 h2_put_u32(mut payload, f.window_size_increment & 0x7fff_ffff)
519 return h2_frame_bytes(h2_frame_window_update, 0, f.stream_id, payload)
520 }
521 H2ContinuationFrame {
522 flags := if f.end_headers { h2_flag_end_headers } else { u8(0) }
523 return h2_frame_bytes(h2_frame_continuation, flags, f.stream_id, f.fragment)
524 }
525 H2UnknownFrame {
526 return h2_frame_bytes(f.header.typ, f.header.flags, f.header.stream_id, f.payload)
527 }
528 }
529}
530