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