| 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. |
| 4 | module 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). |
| 11 | pub const h2_frame_data = u8(0x0) |
| 12 | pub const h2_frame_headers = u8(0x1) |
| 13 | pub const h2_frame_priority = u8(0x2) |
| 14 | pub const h2_frame_rst_stream = u8(0x3) |
| 15 | pub const h2_frame_settings = u8(0x4) |
| 16 | pub const h2_frame_push_promise = u8(0x5) |
| 17 | pub const h2_frame_ping = u8(0x6) |
| 18 | pub const h2_frame_goaway = u8(0x7) |
| 19 | pub const h2_frame_window_update = u8(0x8) |
| 20 | pub 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. |
| 24 | pub const h2_flag_end_stream = u8(0x1) |
| 25 | pub const h2_flag_ack = u8(0x1) |
| 26 | pub const h2_flag_end_headers = u8(0x4) |
| 27 | pub const h2_flag_padded = u8(0x8) |
| 28 | pub const h2_flag_priority = u8(0x20) |
| 29 | |
| 30 | // h2_frame_header_len is the fixed size of a frame header in bytes. |
| 31 | pub 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. |
| 35 | pub 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. |
| 39 | pub const h2_max_max_frame_size = u32(16777215) |
| 40 | |
| 41 | // HTTP/2 setting identifiers (RFC 7540 Section 6.5.2). |
| 42 | pub const h2_settings_header_table_size = u16(0x1) |
| 43 | pub const h2_settings_enable_push = u16(0x2) |
| 44 | pub const h2_settings_max_concurrent_streams = u16(0x3) |
| 45 | pub const h2_settings_initial_window_size = u16(0x4) |
| 46 | pub const h2_settings_max_frame_size = u16(0x5) |
| 47 | pub const h2_settings_max_header_list_size = u16(0x6) |
| 48 | |
| 49 | // H2FrameHeader is the 9-byte header that precedes every HTTP/2 frame. |
| 50 | pub struct H2FrameHeader { |
| 51 | pub: |
| 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 | |
| 58 | pub struct H2DataFrame { |
| 59 | pub: |
| 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 | |
| 69 | pub struct H2HeadersFrame { |
| 70 | pub: |
| 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 | |
| 82 | pub struct H2PriorityFrame { |
| 83 | pub: |
| 84 | stream_id u32 |
| 85 | exclusive bool |
| 86 | stream_dep u32 |
| 87 | weight u8 |
| 88 | } |
| 89 | |
| 90 | pub struct H2RstStreamFrame { |
| 91 | pub: |
| 92 | stream_id u32 |
| 93 | error_code u32 |
| 94 | } |
| 95 | |
| 96 | pub struct H2Setting { |
| 97 | pub: |
| 98 | id u16 |
| 99 | value u32 |
| 100 | } |
| 101 | |
| 102 | pub struct H2SettingsFrame { |
| 103 | pub: |
| 104 | ack bool |
| 105 | settings []H2Setting |
| 106 | } |
| 107 | |
| 108 | pub struct H2PushPromiseFrame { |
| 109 | pub: |
| 110 | stream_id u32 |
| 111 | promised_stream_id u32 |
| 112 | fragment []u8 |
| 113 | end_headers bool |
| 114 | } |
| 115 | |
| 116 | pub struct H2PingFrame { |
| 117 | pub: |
| 118 | ack bool |
| 119 | data []u8 // 8 opaque bytes |
| 120 | } |
| 121 | |
| 122 | pub struct H2GoawayFrame { |
| 123 | pub: |
| 124 | last_stream_id u32 |
| 125 | error_code u32 |
| 126 | debug_data []u8 |
| 127 | } |
| 128 | |
| 129 | pub struct H2WindowUpdateFrame { |
| 130 | pub: |
| 131 | stream_id u32 |
| 132 | window_size_increment u32 |
| 133 | } |
| 134 | |
| 135 | pub struct H2ContinuationFrame { |
| 136 | pub: |
| 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. |
| 144 | pub struct H2UnknownFrame { |
| 145 | pub: |
| 146 | header H2FrameHeader |
| 147 | payload []u8 |
| 148 | } |
| 149 | |
| 150 | // H2Frame is any HTTP/2 frame. |
| 151 | pub 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 | |
| 165 | fn h2_be_u16(b []u8, o int) u16 { |
| 166 | return (u16(b[o]) << 8) | u16(b[o + 1]) |
| 167 | } |
| 168 | |
| 169 | fn 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 | |
| 173 | fn 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 | |
| 177 | fn h2_put_u16(mut b []u8, v u16) { |
| 178 | b << u8(v >> 8) |
| 179 | b << u8(v) |
| 180 | } |
| 181 | |
| 182 | fn h2_put_u24(mut b []u8, v u32) { |
| 183 | b << u8(v >> 16) |
| 184 | b << u8(v >> 8) |
| 185 | b << u8(v) |
| 186 | } |
| 187 | |
| 188 | fn 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`. |
| 198 | pub 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. |
| 215 | pub 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). |
| 228 | fn 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. |
| 243 | pub 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. |
| 426 | fn 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. |
| 438 | pub 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 | |