| 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 | // HTTP/2 setting identifiers (RFC 7540 Section 6.5.2). |
| 38 | pub const h2_settings_header_table_size = u16(0x1) |
| 39 | pub const h2_settings_enable_push = u16(0x2) |
| 40 | pub const h2_settings_max_concurrent_streams = u16(0x3) |
| 41 | pub const h2_settings_initial_window_size = u16(0x4) |
| 42 | pub const h2_settings_max_frame_size = u16(0x5) |
| 43 | pub const h2_settings_max_header_list_size = u16(0x6) |
| 44 | |
| 45 | // H2FrameHeader is the 9-byte header that precedes every HTTP/2 frame. |
| 46 | pub struct H2FrameHeader { |
| 47 | pub: |
| 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 | |
| 54 | pub struct H2DataFrame { |
| 55 | pub: |
| 56 | stream_id u32 |
| 57 | data []u8 |
| 58 | end_stream bool |
| 59 | } |
| 60 | |
| 61 | pub struct H2HeadersFrame { |
| 62 | pub: |
| 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 | |
| 74 | pub struct H2PriorityFrame { |
| 75 | pub: |
| 76 | stream_id u32 |
| 77 | exclusive bool |
| 78 | stream_dep u32 |
| 79 | weight u8 |
| 80 | } |
| 81 | |
| 82 | pub struct H2RstStreamFrame { |
| 83 | pub: |
| 84 | stream_id u32 |
| 85 | error_code u32 |
| 86 | } |
| 87 | |
| 88 | pub struct H2Setting { |
| 89 | pub: |
| 90 | id u16 |
| 91 | value u32 |
| 92 | } |
| 93 | |
| 94 | pub struct H2SettingsFrame { |
| 95 | pub: |
| 96 | ack bool |
| 97 | settings []H2Setting |
| 98 | } |
| 99 | |
| 100 | pub struct H2PushPromiseFrame { |
| 101 | pub: |
| 102 | stream_id u32 |
| 103 | promised_stream_id u32 |
| 104 | fragment []u8 |
| 105 | end_headers bool |
| 106 | } |
| 107 | |
| 108 | pub struct H2PingFrame { |
| 109 | pub: |
| 110 | ack bool |
| 111 | data []u8 // 8 opaque bytes |
| 112 | } |
| 113 | |
| 114 | pub struct H2GoawayFrame { |
| 115 | pub: |
| 116 | last_stream_id u32 |
| 117 | error_code u32 |
| 118 | debug_data []u8 |
| 119 | } |
| 120 | |
| 121 | pub struct H2WindowUpdateFrame { |
| 122 | pub: |
| 123 | stream_id u32 |
| 124 | window_size_increment u32 |
| 125 | } |
| 126 | |
| 127 | pub struct H2ContinuationFrame { |
| 128 | pub: |
| 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. |
| 136 | pub struct H2UnknownFrame { |
| 137 | pub: |
| 138 | header H2FrameHeader |
| 139 | payload []u8 |
| 140 | } |
| 141 | |
| 142 | // H2Frame is any HTTP/2 frame. |
| 143 | pub 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 | |
| 157 | fn h2_be_u16(b []u8, o int) u16 { |
| 158 | return (u16(b[o]) << 8) | u16(b[o + 1]) |
| 159 | } |
| 160 | |
| 161 | fn 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 | |
| 165 | fn 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 | |
| 169 | fn h2_put_u16(mut b []u8, v u16) { |
| 170 | b << u8(v >> 8) |
| 171 | b << u8(v) |
| 172 | } |
| 173 | |
| 174 | fn h2_put_u24(mut b []u8, v u32) { |
| 175 | b << u8(v >> 16) |
| 176 | b << u8(v >> 8) |
| 177 | b << u8(v) |
| 178 | } |
| 179 | |
| 180 | fn 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`. |
| 190 | pub 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. |
| 207 | pub 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). |
| 220 | fn 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. |
| 235 | pub 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. |
| 417 | fn 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. |
| 429 | pub 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 | |