we (web engine): Experimental web browser project to understand the limits of Claude
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement HPACK header compression for HTTP/2 (RFC 7541)

Add complete HPACK encoder/decoder at crates/net/src/http2/hpack.rs:
- Integer encoding/decoding with prefix bits (§5.1)
- Huffman coding via canonical code generation from RFC code lengths (§5.2, Appendix B)
- Static table with 61 predefined headers (Appendix A)
- Dynamic table with FIFO eviction and configurable max size (§2.3)
- All header field representations: indexed, literal with/without indexing, never indexed (§6)
- Dynamic table size updates (§6.3)
- 41 tests including RFC 7541 Appendix C examples (C.2-C.6)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+1678
+1674
crates/net/src/http2/hpack.rs
··· 1 + //! HPACK: Header Compression for HTTP/2 (RFC 7541). 2 + //! 3 + //! Implements the full HPACK encoding and decoding format including: 4 + //! - Integer representation with prefix bits (§5.1) 5 + //! - Huffman coding for string literals (§5.2, Appendix B) 6 + //! - Static table of 61 predefined headers (Appendix A) 7 + //! - Dynamic table with configurable max size (§2.3) 8 + //! - All header field representations (§6) 9 + 10 + use std::fmt; 11 + 12 + // --------------------------------------------------------------------------- 13 + // Errors 14 + // --------------------------------------------------------------------------- 15 + 16 + /// Errors that can occur during HPACK encoding or decoding. 17 + #[derive(Debug, Clone, PartialEq, Eq)] 18 + pub enum HpackError { 19 + /// The encoded integer is incomplete (truncated input). 20 + IntegerOverflow, 21 + /// Unexpected end of input. 22 + UnexpectedEnd, 23 + /// An index into the header table is out of bounds. 24 + InvalidIndex(usize), 25 + /// The Huffman-encoded string is malformed. 26 + InvalidHuffman, 27 + /// Dynamic table size update exceeds the protocol limit. 28 + InvalidTableSize(usize), 29 + } 30 + 31 + impl fmt::Display for HpackError { 32 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 33 + match self { 34 + HpackError::IntegerOverflow => write!(f, "HPACK integer overflow"), 35 + HpackError::UnexpectedEnd => write!(f, "unexpected end of HPACK data"), 36 + HpackError::InvalidIndex(i) => write!(f, "invalid HPACK table index {i}"), 37 + HpackError::InvalidHuffman => write!(f, "invalid Huffman-encoded string"), 38 + HpackError::InvalidTableSize(s) => { 39 + write!(f, "dynamic table size update {s} exceeds limit") 40 + } 41 + } 42 + } 43 + } 44 + 45 + // --------------------------------------------------------------------------- 46 + // Integer encoding / decoding (RFC 7541 §5.1) 47 + // --------------------------------------------------------------------------- 48 + 49 + /// Encode an integer using the HPACK variable-length integer format. 50 + /// 51 + /// `prefix_bits` is the number of bits available in the first octet (1..=8). 52 + /// `prefix_value` is the value already present in the high bits of the first byte. 53 + pub fn encode_integer(buf: &mut Vec<u8>, mut value: usize, prefix_bits: u8, prefix_value: u8) { 54 + debug_assert!((1..=8).contains(&prefix_bits)); 55 + let max_prefix = (1usize << prefix_bits) - 1; 56 + 57 + if value < max_prefix { 58 + buf.push(prefix_value | value as u8); 59 + } else { 60 + buf.push(prefix_value | max_prefix as u8); 61 + value -= max_prefix; 62 + while value >= 128 { 63 + buf.push((value % 128) as u8 | 0x80); 64 + value /= 128; 65 + } 66 + buf.push(value as u8); 67 + } 68 + } 69 + 70 + /// Decode an integer from the HPACK variable-length integer format. 71 + /// 72 + /// Returns `(value, bytes_consumed)`. 73 + pub fn decode_integer(data: &[u8], prefix_bits: u8) -> Result<(usize, usize), HpackError> { 74 + debug_assert!((1..=8).contains(&prefix_bits)); 75 + if data.is_empty() { 76 + return Err(HpackError::UnexpectedEnd); 77 + } 78 + 79 + let max_prefix = (1usize << prefix_bits) - 1; 80 + let value = (data[0] as usize) & max_prefix; 81 + 82 + if value < max_prefix { 83 + return Ok((value, 1)); 84 + } 85 + 86 + let mut value = max_prefix; 87 + let mut m = 0u32; 88 + let mut i = 1; 89 + 90 + loop { 91 + if i >= data.len() { 92 + return Err(HpackError::UnexpectedEnd); 93 + } 94 + let b = data[i] as usize; 95 + i += 1; 96 + 97 + // Guard against overflow: each continuation contributes 7 bits. 98 + // With m reaching 56+ on a 64-bit system we'd overflow. 99 + if m >= 56 { 100 + return Err(HpackError::IntegerOverflow); 101 + } 102 + 103 + value = value 104 + .checked_add((b & 0x7F) << m) 105 + .ok_or(HpackError::IntegerOverflow)?; 106 + m += 7; 107 + 108 + if b & 0x80 == 0 { 109 + return Ok((value, i)); 110 + } 111 + } 112 + } 113 + 114 + // --------------------------------------------------------------------------- 115 + // Huffman coding (RFC 7541 §5.2, Appendix B) 116 + // --------------------------------------------------------------------------- 117 + 118 + /// Code lengths for each symbol 0..=256 (256 = EOS) per RFC 7541 Appendix B. 119 + /// The canonical Huffman codes are derived from these lengths. 120 + #[rustfmt::skip] 121 + const HUFFMAN_CODE_LENGTHS: [u8; 257] = [ 122 + 13, 23, 28, 28, 28, 28, 28, 28, 28, 24, 30, 28, 28, 30, 28, 28, // 0-15 123 + 28, 28, 28, 28, 28, 28, 30, 28, 28, 28, 28, 28, 28, 28, 28, 28, // 16-31 124 + 6, 10, 10, 12, 13, 6, 8, 11, 10, 10, 8, 11, 8, 6, 6, 6, // 32-47 125 + 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 8, 15, 6, 12, 10, // 48-63 126 + 13, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // 64-79 127 + 7, 7, 7, 7, 7, 7, 7, 7, 8, 7, 8, 13, 19, 13, 14, 6, // 80-95 128 + 15, 5, 6, 5, 6, 5, 6, 6, 6, 5, 7, 7, 6, 6, 6, 5, // 96-111 129 + 6, 7, 6, 5, 5, 6, 7, 7, 7, 7, 7, 19, 11, 14, 13, 28, // 112-127 130 + 20, 22, 20, 20, 22, 22, 22, 23, 22, 23, 23, 23, 23, 23, 24, 23, // 128-143 131 + 24, 24, 22, 23, 24, 23, 23, 23, 23, 21, 22, 23, 22, 23, 23, 24, // 144-159 132 + 22, 21, 20, 22, 22, 23, 23, 21, 23, 22, 22, 24, 21, 22, 23, 23, // 160-175 133 + 21, 21, 22, 21, 23, 22, 23, 23, 20, 22, 22, 22, 23, 22, 22, 23, // 176-191 134 + 26, 26, 20, 19, 22, 23, 22, 25, 26, 26, 26, 27, 27, 26, 24, 25, // 192-207 135 + 19, 21, 26, 27, 27, 26, 27, 24, 21, 21, 26, 26, 28, 27, 27, 27, // 208-223 136 + 20, 24, 20, 21, 22, 21, 21, 23, 22, 22, 25, 25, 24, 24, 26, 23, // 224-239 137 + 26, 27, 26, 26, 27, 27, 27, 27, 27, 28, 27, 27, 27, 27, 27, 26, // 240-255 138 + 30, // 256 EOS 139 + ]; 140 + 141 + /// Build the canonical Huffman code table from the code lengths. 142 + /// Returns `(code, length)` for each symbol. 143 + fn build_huffman_table() -> Vec<(u32, u8)> { 144 + let max_len = *HUFFMAN_CODE_LENGTHS.iter().max().unwrap() as usize; 145 + 146 + // Count codes of each length 147 + let mut bl_count = vec![0u32; max_len + 1]; 148 + for &len in &HUFFMAN_CODE_LENGTHS { 149 + bl_count[len as usize] += 1; 150 + } 151 + 152 + // Compute the starting code for each length 153 + let mut next_code = vec![0u32; max_len + 1]; 154 + let mut code = 0u32; 155 + bl_count[0] = 0; 156 + for bits in 1..=max_len { 157 + code = (code + bl_count[bits - 1]) << 1; 158 + next_code[bits] = code; 159 + } 160 + 161 + // Assign codes to symbols in order 162 + let mut table = Vec::with_capacity(HUFFMAN_CODE_LENGTHS.len()); 163 + for &len in &HUFFMAN_CODE_LENGTHS { 164 + let c = next_code[len as usize]; 165 + next_code[len as usize] += 1; 166 + table.push((c, len)); 167 + } 168 + 169 + table 170 + } 171 + 172 + /// Thread-local Huffman encode table for reuse. 173 + fn with_huffman_table<R>(f: impl FnOnce(&Vec<(u32, u8)>) -> R) -> R { 174 + thread_local! { 175 + static TABLE: Vec<(u32, u8)> = build_huffman_table(); 176 + } 177 + TABLE.with(f) 178 + } 179 + 180 + /// Huffman-encode a byte slice. 181 + pub fn huffman_encode(src: &[u8]) -> Vec<u8> { 182 + with_huffman_table(|table| { 183 + let mut buf = Vec::new(); 184 + let mut current: u64 = 0; 185 + let mut bits_left: u8 = 0; 186 + 187 + for &byte in src { 188 + let (code, length) = table[byte as usize]; 189 + current = (current << length) | code as u64; 190 + bits_left += length; 191 + 192 + while bits_left >= 8 { 193 + bits_left -= 8; 194 + buf.push((current >> bits_left) as u8); 195 + } 196 + } 197 + 198 + // Pad with EOS prefix (all 1s) per RFC 7541 §5.2 199 + if bits_left > 0 { 200 + let pad = 8 - bits_left; 201 + current = (current << pad) | ((1u64 << pad) - 1); 202 + buf.push(current as u8); 203 + } 204 + 205 + buf 206 + }) 207 + } 208 + 209 + /// Huffman decode table node. We build a 256-entry lookup table for 8-bit 210 + /// chunks to speed up decoding. 211 + /// 212 + /// For simplicity we use a bit-at-a-time approach with a binary tree, 213 + /// which is correct and straightforward. 214 + struct HuffmanDecoder { 215 + /// Tree nodes: [left_child, right_child]. Index 0 is root. 216 + /// Leaf nodes store the decoded symbol value with a sentinel flag. 217 + nodes: Vec<[i32; 2]>, 218 + } 219 + 220 + /// Sentinel: bit 16 set means "this is a leaf with symbol = value & 0x1FF". 221 + const LEAF_FLAG: i32 = 0x10000; 222 + 223 + impl HuffmanDecoder { 224 + fn new() -> Self { 225 + let table = build_huffman_table(); 226 + 227 + // Start with root node. 228 + // Children are either 0 (empty), a positive node index, or LEAF_FLAG | symbol. 229 + let mut nodes = vec![[0i32; 2]]; 230 + 231 + for (sym, &(code, length)) in table.iter().enumerate() { 232 + if sym == 256 { 233 + break; // skip EOS — not a decodable symbol 234 + } 235 + 236 + let mut node = 0usize; 237 + for bit_pos in (0..length).rev() { 238 + let bit = ((code >> bit_pos) & 1) as usize; 239 + 240 + if bit_pos == 0 { 241 + // Last bit — store leaf directly in parent's child slot 242 + debug_assert!( 243 + nodes[node][bit] == 0, 244 + "Huffman table conflict at symbol {sym}" 245 + ); 246 + nodes[node][bit] = LEAF_FLAG | sym as i32; 247 + } else { 248 + // Intermediate bit — navigate to or create child node 249 + let child = nodes[node][bit]; 250 + if child == 0 { 251 + let new_node = nodes.len(); 252 + nodes.push([0i32; 2]); 253 + nodes[node][bit] = new_node as i32; 254 + node = new_node; 255 + } else { 256 + debug_assert!( 257 + child & LEAF_FLAG == 0, 258 + "Huffman table conflict: leaf in prefix path at symbol {sym}" 259 + ); 260 + node = child as usize; 261 + } 262 + } 263 + } 264 + } 265 + 266 + HuffmanDecoder { nodes } 267 + } 268 + 269 + fn decode(&self, data: &[u8], encoded_len: usize) -> Result<Vec<u8>, HpackError> { 270 + let mut out = Vec::new(); 271 + let mut node = 0usize; 272 + 273 + for &byte in data.iter().take(encoded_len) { 274 + for bit_pos in (0..8u8).rev() { 275 + let bit = ((byte >> bit_pos) & 1) as usize; 276 + let child = self.nodes[node][bit]; 277 + 278 + if child == 0 { 279 + return Err(HpackError::InvalidHuffman); 280 + } 281 + 282 + if child & LEAF_FLAG != 0 { 283 + let sym = child & 0x1FF; 284 + out.push(sym as u8); 285 + node = 0; 286 + } else { 287 + node = child as usize; 288 + } 289 + } 290 + } 291 + 292 + Ok(out) 293 + } 294 + } 295 + 296 + /// Thread-local Huffman decoder for reuse. 297 + fn with_huffman_decoder<R>(f: impl FnOnce(&HuffmanDecoder) -> R) -> R { 298 + thread_local! { 299 + static DECODER: HuffmanDecoder = HuffmanDecoder::new(); 300 + } 301 + DECODER.with(f) 302 + } 303 + 304 + /// Huffman-decode a byte slice. 305 + pub fn huffman_decode(data: &[u8]) -> Result<Vec<u8>, HpackError> { 306 + with_huffman_decoder(|dec| dec.decode(data, data.len())) 307 + } 308 + 309 + // --------------------------------------------------------------------------- 310 + // String encoding / decoding (RFC 7541 §5.2) 311 + // --------------------------------------------------------------------------- 312 + 313 + /// Encode a string (with optional Huffman coding) into the HPACK format. 314 + fn encode_string(buf: &mut Vec<u8>, value: &[u8], use_huffman: bool) { 315 + if use_huffman { 316 + let encoded = huffman_encode(value); 317 + encode_integer(buf, encoded.len(), 7, 0x80); 318 + buf.extend_from_slice(&encoded); 319 + } else { 320 + encode_integer(buf, value.len(), 7, 0x00); 321 + buf.extend_from_slice(value); 322 + } 323 + } 324 + 325 + /// Decode a string from HPACK format. Returns `(decoded_bytes, bytes_consumed)`. 326 + fn decode_string(data: &[u8]) -> Result<(Vec<u8>, usize), HpackError> { 327 + if data.is_empty() { 328 + return Err(HpackError::UnexpectedEnd); 329 + } 330 + 331 + let huffman = data[0] & 0x80 != 0; 332 + let (length, consumed) = decode_integer(data, 7)?; 333 + 334 + let total = consumed + length; 335 + if total > data.len() { 336 + return Err(HpackError::UnexpectedEnd); 337 + } 338 + 339 + let raw = &data[consumed..total]; 340 + let decoded = if huffman { 341 + huffman_decode(raw)? 342 + } else { 343 + raw.to_vec() 344 + }; 345 + 346 + Ok((decoded, total)) 347 + } 348 + 349 + // --------------------------------------------------------------------------- 350 + // Static table (RFC 7541 Appendix A) 351 + // --------------------------------------------------------------------------- 352 + 353 + /// A header field is a name-value pair. 354 + #[derive(Debug, Clone, PartialEq, Eq)] 355 + pub struct HeaderField { 356 + pub name: Vec<u8>, 357 + pub value: Vec<u8>, 358 + } 359 + 360 + impl HeaderField { 361 + pub fn new(name: impl Into<Vec<u8>>, value: impl Into<Vec<u8>>) -> Self { 362 + HeaderField { 363 + name: name.into(), 364 + value: value.into(), 365 + } 366 + } 367 + 368 + /// The size of this entry per RFC 7541 §4.1: name length + value length + 32. 369 + pub fn size(&self) -> usize { 370 + self.name.len() + self.value.len() + 32 371 + } 372 + } 373 + 374 + /// The 61 predefined static table entries (RFC 7541 Appendix A). 375 + /// Index 1..=61 (1-based). 376 + #[rustfmt::skip] 377 + const STATIC_TABLE: [(&[u8], &[u8]); 61] = [ 378 + (b":authority", b""), // 1 379 + (b":method", b"GET"), // 2 380 + (b":method", b"POST"), // 3 381 + (b":path", b"/"), // 4 382 + (b":path", b"/index.html"), // 5 383 + (b":scheme", b"http"), // 6 384 + (b":scheme", b"https"), // 7 385 + (b":status", b"200"), // 8 386 + (b":status", b"204"), // 9 387 + (b":status", b"206"), // 10 388 + (b":status", b"304"), // 11 389 + (b":status", b"400"), // 12 390 + (b":status", b"404"), // 13 391 + (b":status", b"500"), // 14 392 + (b"accept-charset", b""), // 15 393 + (b"accept-encoding", b"gzip, deflate"), // 16 394 + (b"accept-language", b""), // 17 395 + (b"accept-ranges", b""), // 18 396 + (b"accept", b""), // 19 397 + (b"access-control-allow-origin", b""), // 20 398 + (b"age", b""), // 21 399 + (b"allow", b""), // 22 400 + (b"authorization", b""), // 23 401 + (b"cache-control", b""), // 24 402 + (b"content-disposition", b""), // 25 403 + (b"content-encoding", b""), // 26 404 + (b"content-language", b""), // 27 405 + (b"content-length", b""), // 28 406 + (b"content-location", b""), // 29 407 + (b"content-range", b""), // 30 408 + (b"content-type", b""), // 31 409 + (b"cookie", b""), // 32 410 + (b"date", b""), // 33 411 + (b"etag", b""), // 34 412 + (b"expect", b""), // 35 413 + (b"expires", b""), // 36 414 + (b"from", b""), // 37 415 + (b"host", b""), // 38 416 + (b"if-match", b""), // 39 417 + (b"if-modified-since", b""), // 40 418 + (b"if-none-match", b""), // 41 419 + (b"if-range", b""), // 42 420 + (b"if-unmodified-since", b""), // 43 421 + (b"last-modified", b""), // 44 422 + (b"link", b""), // 45 423 + (b"location", b""), // 46 424 + (b"max-forwards", b""), // 47 425 + (b"proxy-authenticate", b""), // 48 426 + (b"proxy-authorization", b""), // 49 427 + (b"range", b""), // 50 428 + (b"referer", b""), // 51 429 + (b"refresh", b""), // 52 430 + (b"retry-after", b""), // 53 431 + (b"server", b""), // 54 432 + (b"set-cookie", b""), // 55 433 + (b"strict-transport-security", b""), // 56 434 + (b"transfer-encoding", b""), // 57 435 + (b"user-agent", b""), // 58 436 + (b"vary", b""), // 59 437 + (b"via", b""), // 60 438 + (b"www-authenticate", b""), // 61 439 + ]; 440 + 441 + /// Look up a header in the static table. 442 + /// Returns `Some((index, full_match))` where `full_match` is true if both 443 + /// name and value match, false if only the name matches. 444 + /// Index is 1-based. 445 + fn static_table_find(name: &[u8], value: &[u8]) -> Option<(usize, bool)> { 446 + let mut name_match = None; 447 + for (i, &(n, v)) in STATIC_TABLE.iter().enumerate() { 448 + if n == name { 449 + if v == value { 450 + return Some((i + 1, true)); 451 + } 452 + if name_match.is_none() { 453 + name_match = Some(i + 1); 454 + } 455 + } 456 + } 457 + name_match.map(|idx| (idx, false)) 458 + } 459 + 460 + /// Get a static table entry by 1-based index. 461 + fn static_table_get(index: usize) -> Option<(&'static [u8], &'static [u8])> { 462 + if index >= 1 && index <= STATIC_TABLE.len() { 463 + Some(STATIC_TABLE[index - 1]) 464 + } else { 465 + None 466 + } 467 + } 468 + 469 + // --------------------------------------------------------------------------- 470 + // Dynamic table (RFC 7541 §2.3) 471 + // --------------------------------------------------------------------------- 472 + 473 + /// The dynamic table is a FIFO buffer of recently-seen headers. 474 + /// Newest entries are at the front (index 0 = most recently inserted). 475 + /// HPACK indices continue from after the static table: the first dynamic 476 + /// entry has index `STATIC_TABLE.len() + 1` (= 62). 477 + pub struct DynamicTable { 478 + entries: Vec<HeaderField>, 479 + current_size: usize, 480 + max_size: usize, 481 + /// The protocol-level limit (from SETTINGS_HEADER_TABLE_SIZE). 482 + protocol_max_size: usize, 483 + } 484 + 485 + impl DynamicTable { 486 + /// Create a new dynamic table with the given maximum size (in bytes). 487 + pub fn new(max_size: usize) -> Self { 488 + DynamicTable { 489 + entries: Vec::new(), 490 + current_size: 0, 491 + max_size, 492 + protocol_max_size: max_size, 493 + } 494 + } 495 + 496 + /// Insert a new entry at the front of the table, evicting old entries as needed. 497 + pub fn insert(&mut self, field: HeaderField) { 498 + let entry_size = field.size(); 499 + 500 + // Evict entries until there's room (or the table is empty). 501 + while self.current_size + entry_size > self.max_size && !self.entries.is_empty() { 502 + let removed = self.entries.pop().unwrap(); 503 + self.current_size -= removed.size(); 504 + } 505 + 506 + // If the entry itself is larger than max_size, the table is emptied 507 + // but the entry is not added (per RFC 7541 §4.4). 508 + if entry_size <= self.max_size { 509 + self.current_size += entry_size; 510 + self.entries.insert(0, field); 511 + } 512 + } 513 + 514 + /// Get an entry by 0-based dynamic table index. 515 + pub fn get(&self, index: usize) -> Option<&HeaderField> { 516 + self.entries.get(index) 517 + } 518 + 519 + /// Update the maximum table size. Evicts entries if necessary. 520 + pub fn set_max_size(&mut self, new_max: usize) { 521 + self.max_size = new_max; 522 + self.evict(); 523 + } 524 + 525 + /// Set the protocol-level maximum size (from SETTINGS frame). 526 + pub fn set_protocol_max_size(&mut self, new_max: usize) { 527 + self.protocol_max_size = new_max; 528 + if self.max_size > new_max { 529 + self.max_size = new_max; 530 + self.evict(); 531 + } 532 + } 533 + 534 + /// Number of entries in the dynamic table. 535 + pub fn len(&self) -> usize { 536 + self.entries.len() 537 + } 538 + 539 + /// Whether the dynamic table is empty. 540 + pub fn is_empty(&self) -> bool { 541 + self.entries.is_empty() 542 + } 543 + 544 + /// Current size in bytes. 545 + pub fn size(&self) -> usize { 546 + self.current_size 547 + } 548 + 549 + /// Maximum table size in bytes. 550 + pub fn max_size(&self) -> usize { 551 + self.max_size 552 + } 553 + 554 + /// Find a header in the dynamic table. 555 + /// Returns `Some((index, full_match))` where index is 0-based within the 556 + /// dynamic table. 557 + fn find(&self, name: &[u8], value: &[u8]) -> Option<(usize, bool)> { 558 + let mut name_match = None; 559 + for (i, entry) in self.entries.iter().enumerate() { 560 + if entry.name == name { 561 + if entry.value == value { 562 + return Some((i, true)); 563 + } 564 + if name_match.is_none() { 565 + name_match = Some(i); 566 + } 567 + } 568 + } 569 + name_match.map(|idx| (idx, false)) 570 + } 571 + 572 + /// Evict entries to fit within max_size. 573 + fn evict(&mut self) { 574 + while self.current_size > self.max_size && !self.entries.is_empty() { 575 + let removed = self.entries.pop().unwrap(); 576 + self.current_size -= removed.size(); 577 + } 578 + } 579 + } 580 + 581 + // --------------------------------------------------------------------------- 582 + // Unified table access 583 + // --------------------------------------------------------------------------- 584 + 585 + /// The combined static + dynamic table index space. 586 + const STATIC_TABLE_LEN: usize = 61; 587 + 588 + /// Resolve a 1-based HPACK index to a `HeaderField`. 589 + fn table_get(index: usize, dynamic: &DynamicTable) -> Result<HeaderField, HpackError> { 590 + if index == 0 { 591 + return Err(HpackError::InvalidIndex(0)); 592 + } 593 + 594 + if index <= STATIC_TABLE_LEN { 595 + let (name, value) = static_table_get(index).unwrap(); 596 + Ok(HeaderField::new(name, value)) 597 + } else { 598 + let dyn_index = index - STATIC_TABLE_LEN - 1; 599 + dynamic 600 + .get(dyn_index) 601 + .cloned() 602 + .ok_or(HpackError::InvalidIndex(index)) 603 + } 604 + } 605 + 606 + /// Find a header in the combined static + dynamic tables. 607 + /// Returns `(index, full_match)` where index is 1-based HPACK index. 608 + fn table_find(name: &[u8], value: &[u8], dynamic: &DynamicTable) -> Option<(usize, bool)> { 609 + // Check static table first 610 + let static_result = static_table_find(name, value); 611 + if let Some((_, true)) = static_result { 612 + return static_result; 613 + } 614 + 615 + // Check dynamic table 616 + if let Some((dyn_idx, full)) = dynamic.find(name, value) { 617 + let hpack_idx = STATIC_TABLE_LEN + 1 + dyn_idx; 618 + if full { 619 + return Some((hpack_idx, true)); 620 + } 621 + // Return dynamic name-only match if static didn't match name either 622 + if static_result.is_none() { 623 + return Some((hpack_idx, false)); 624 + } 625 + } 626 + 627 + // Return static name-only match 628 + static_result 629 + } 630 + 631 + // --------------------------------------------------------------------------- 632 + // Encoder 633 + // --------------------------------------------------------------------------- 634 + 635 + /// Sensitivity hint for header encoding. 636 + #[derive(Debug, Clone, Copy, PartialEq, Eq)] 637 + pub enum Sensitive { 638 + /// Normal header — may be added to the dynamic table. 639 + No, 640 + /// Sensitive header (e.g., cookie) — literal, never indexed. 641 + Yes, 642 + } 643 + 644 + /// HPACK encoder. Maintains encoder-side dynamic table state. 645 + pub struct Encoder { 646 + dynamic_table: DynamicTable, 647 + use_huffman: bool, 648 + } 649 + 650 + impl Encoder { 651 + /// Create a new encoder with the given maximum dynamic table size. 652 + pub fn new(max_table_size: usize) -> Self { 653 + Encoder { 654 + dynamic_table: DynamicTable::new(max_table_size), 655 + use_huffman: true, 656 + } 657 + } 658 + 659 + /// Set whether to use Huffman coding for string literals. 660 + pub fn set_huffman(&mut self, use_huffman: bool) { 661 + self.use_huffman = use_huffman; 662 + } 663 + 664 + /// Encode a list of headers into an HPACK header block. 665 + pub fn encode(&mut self, headers: &[HeaderField]) -> Vec<u8> { 666 + let mut buf = Vec::new(); 667 + for header in headers { 668 + self.encode_header(&mut buf, header, Sensitive::No); 669 + } 670 + buf 671 + } 672 + 673 + /// Encode a list of headers with sensitivity hints. 674 + pub fn encode_with_sensitivity(&mut self, headers: &[(HeaderField, Sensitive)]) -> Vec<u8> { 675 + let mut buf = Vec::new(); 676 + for (header, sensitive) in headers { 677 + self.encode_header(&mut buf, header, *sensitive); 678 + } 679 + buf 680 + } 681 + 682 + /// Encode a dynamic table size update. 683 + pub fn encode_table_size_update(&mut self, buf: &mut Vec<u8>, new_size: usize) { 684 + encode_integer(buf, new_size, 5, 0x20); 685 + self.dynamic_table.set_max_size(new_size); 686 + } 687 + 688 + /// Encode a single header field. 689 + fn encode_header(&mut self, buf: &mut Vec<u8>, header: &HeaderField, sensitive: Sensitive) { 690 + if sensitive == Sensitive::Yes { 691 + self.encode_literal_never_indexed(buf, header); 692 + return; 693 + } 694 + 695 + match table_find(&header.name, &header.value, &self.dynamic_table) { 696 + Some((index, true)) => { 697 + // Fully indexed — emit indexed header field (§6.1) 698 + encode_integer(buf, index, 7, 0x80); 699 + } 700 + Some((index, false)) => { 701 + // Name match — literal with incremental indexing (§6.2.1) 702 + encode_integer(buf, index, 6, 0x40); 703 + encode_string(buf, &header.value, self.use_huffman); 704 + self.dynamic_table.insert(header.clone()); 705 + } 706 + None => { 707 + // No match — literal with incremental indexing, new name (§6.2.1) 708 + buf.push(0x40); 709 + encode_string(buf, &header.name, self.use_huffman); 710 + encode_string(buf, &header.value, self.use_huffman); 711 + self.dynamic_table.insert(header.clone()); 712 + } 713 + } 714 + } 715 + 716 + /// Encode a literal header field that must never be indexed (§6.2.3). 717 + fn encode_literal_never_indexed(&mut self, buf: &mut Vec<u8>, header: &HeaderField) { 718 + match static_table_find(&header.name, &header.value) { 719 + Some((index, _)) => { 720 + encode_integer(buf, index, 4, 0x10); 721 + encode_string(buf, &header.value, self.use_huffman); 722 + } 723 + None => { 724 + // Also check dynamic table for name-only match 725 + match self.dynamic_table.find(&header.name, &header.value) { 726 + Some((dyn_idx, _)) => { 727 + let hpack_idx = STATIC_TABLE_LEN + 1 + dyn_idx; 728 + encode_integer(buf, hpack_idx, 4, 0x10); 729 + encode_string(buf, &header.value, self.use_huffman); 730 + } 731 + None => { 732 + buf.push(0x10); 733 + encode_string(buf, &header.name, self.use_huffman); 734 + encode_string(buf, &header.value, self.use_huffman); 735 + } 736 + } 737 + } 738 + } 739 + } 740 + 741 + /// Access the encoder's dynamic table. 742 + pub fn dynamic_table(&self) -> &DynamicTable { 743 + &self.dynamic_table 744 + } 745 + } 746 + 747 + // --------------------------------------------------------------------------- 748 + // Decoder 749 + // --------------------------------------------------------------------------- 750 + 751 + /// HPACK decoder. Maintains decoder-side dynamic table state. 752 + pub struct Decoder { 753 + dynamic_table: DynamicTable, 754 + } 755 + 756 + impl Decoder { 757 + /// Create a new decoder with the given maximum dynamic table size. 758 + pub fn new(max_table_size: usize) -> Self { 759 + Decoder { 760 + dynamic_table: DynamicTable::new(max_table_size), 761 + } 762 + } 763 + 764 + /// Decode an HPACK header block into a list of header fields. 765 + pub fn decode(&mut self, data: &[u8]) -> Result<Vec<HeaderField>, HpackError> { 766 + let mut headers = Vec::new(); 767 + let mut pos = 0; 768 + 769 + while pos < data.len() { 770 + let byte = data[pos]; 771 + 772 + if byte & 0x80 != 0 { 773 + // §6.1 Indexed Header Field Representation 774 + let (index, consumed) = decode_integer(&data[pos..], 7)?; 775 + pos += consumed; 776 + 777 + if index == 0 { 778 + return Err(HpackError::InvalidIndex(0)); 779 + } 780 + 781 + let field = table_get(index, &self.dynamic_table)?; 782 + headers.push(field); 783 + } else if byte & 0x40 != 0 { 784 + // §6.2.1 Literal Header Field with Incremental Indexing 785 + let (index, consumed) = decode_integer(&data[pos..], 6)?; 786 + pos += consumed; 787 + 788 + let name = if index > 0 { 789 + table_get(index, &self.dynamic_table)?.name 790 + } else { 791 + let (n, c) = decode_string(&data[pos..])?; 792 + pos += c; 793 + n 794 + }; 795 + 796 + let (value, consumed) = decode_string(&data[pos..])?; 797 + pos += consumed; 798 + 799 + let field = HeaderField::new(name, value); 800 + self.dynamic_table.insert(field.clone()); 801 + headers.push(field); 802 + } else if byte & 0x20 != 0 { 803 + // §6.3 Dynamic Table Size Update 804 + let (new_size, consumed) = decode_integer(&data[pos..], 5)?; 805 + pos += consumed; 806 + 807 + if new_size > self.dynamic_table.protocol_max_size { 808 + return Err(HpackError::InvalidTableSize(new_size)); 809 + } 810 + 811 + self.dynamic_table.set_max_size(new_size); 812 + } else if byte & 0x10 != 0 { 813 + // §6.2.3 Literal Header Field Never Indexed 814 + let (index, consumed) = decode_integer(&data[pos..], 4)?; 815 + pos += consumed; 816 + 817 + let name = if index > 0 { 818 + table_get(index, &self.dynamic_table)?.name 819 + } else { 820 + let (n, c) = decode_string(&data[pos..])?; 821 + pos += c; 822 + n 823 + }; 824 + 825 + let (value, consumed) = decode_string(&data[pos..])?; 826 + pos += consumed; 827 + 828 + headers.push(HeaderField::new(name, value)); 829 + // Never indexed — do NOT add to dynamic table 830 + } else { 831 + // §6.2.2 Literal Header Field without Indexing 832 + let (index, consumed) = decode_integer(&data[pos..], 4)?; 833 + pos += consumed; 834 + 835 + let name = if index > 0 { 836 + table_get(index, &self.dynamic_table)?.name 837 + } else { 838 + let (n, c) = decode_string(&data[pos..])?; 839 + pos += c; 840 + n 841 + }; 842 + 843 + let (value, consumed) = decode_string(&data[pos..])?; 844 + pos += consumed; 845 + 846 + headers.push(HeaderField::new(name, value)); 847 + // Without indexing — do NOT add to dynamic table 848 + } 849 + } 850 + 851 + Ok(headers) 852 + } 853 + 854 + /// Update the protocol-level maximum table size (from SETTINGS frame). 855 + pub fn set_protocol_max_size(&mut self, max_size: usize) { 856 + self.dynamic_table.set_protocol_max_size(max_size); 857 + } 858 + 859 + /// Access the decoder's dynamic table. 860 + pub fn dynamic_table(&self) -> &DynamicTable { 861 + &self.dynamic_table 862 + } 863 + } 864 + 865 + // --------------------------------------------------------------------------- 866 + // Tests 867 + // --------------------------------------------------------------------------- 868 + 869 + #[cfg(test)] 870 + mod tests { 871 + use super::*; 872 + 873 + // ----------------------------------------------------------------------- 874 + // Integer encoding / decoding (RFC 7541 §C.1) 875 + // ----------------------------------------------------------------------- 876 + 877 + #[test] 878 + fn test_encode_integer_10_with_5bit_prefix() { 879 + // RFC 7541 §C.1.1: encode 10 with 5-bit prefix 880 + let mut buf = Vec::new(); 881 + encode_integer(&mut buf, 10, 5, 0x00); 882 + assert_eq!(buf, vec![10]); 883 + } 884 + 885 + #[test] 886 + fn test_encode_integer_1337_with_5bit_prefix() { 887 + // RFC 7541 §C.1.2: encode 1337 with 5-bit prefix 888 + let mut buf = Vec::new(); 889 + encode_integer(&mut buf, 1337, 5, 0x00); 890 + assert_eq!(buf, vec![31, 154, 10]); 891 + } 892 + 893 + #[test] 894 + fn test_encode_integer_42_at_octet_boundary() { 895 + // RFC 7541 §C.1.3: encode 42 starting at octet boundary (8-bit prefix) 896 + let mut buf = Vec::new(); 897 + encode_integer(&mut buf, 42, 8, 0x00); 898 + assert_eq!(buf, vec![42]); 899 + } 900 + 901 + #[test] 902 + fn test_decode_integer_10_with_5bit_prefix() { 903 + let data = [10u8]; 904 + let (value, consumed) = decode_integer(&data, 5).unwrap(); 905 + assert_eq!(value, 10); 906 + assert_eq!(consumed, 1); 907 + } 908 + 909 + #[test] 910 + fn test_decode_integer_1337_with_5bit_prefix() { 911 + let data = [31u8, 154, 10]; 912 + let (value, consumed) = decode_integer(&data, 5).unwrap(); 913 + assert_eq!(value, 1337); 914 + assert_eq!(consumed, 3); 915 + } 916 + 917 + #[test] 918 + fn test_decode_integer_42_at_octet_boundary() { 919 + let data = [42u8]; 920 + let (value, consumed) = decode_integer(&data, 8).unwrap(); 921 + assert_eq!(value, 42); 922 + assert_eq!(consumed, 1); 923 + } 924 + 925 + #[test] 926 + fn test_integer_roundtrip() { 927 + for prefix in 1..=8 { 928 + for &value in &[0, 1, 30, 31, 127, 128, 255, 256, 1337, 65535, 1_000_000] { 929 + let mut buf = Vec::new(); 930 + encode_integer(&mut buf, value, prefix, 0x00); 931 + let (decoded, consumed) = decode_integer(&buf, prefix).unwrap(); 932 + assert_eq!(decoded, value, "prefix={prefix}, value={value}"); 933 + assert_eq!(consumed, buf.len()); 934 + } 935 + } 936 + } 937 + 938 + #[test] 939 + fn test_integer_with_prefix_value() { 940 + // Encode with high bits set in prefix 941 + let mut buf = Vec::new(); 942 + encode_integer(&mut buf, 10, 5, 0xE0); // top 3 bits = 111 943 + assert_eq!(buf[0], 0xE0 | 10); 944 + assert_eq!(buf.len(), 1); 945 + } 946 + 947 + #[test] 948 + fn test_decode_integer_truncated() { 949 + let data = [31u8, 154]; // missing last byte of 1337 950 + assert!(decode_integer(&data, 5).is_err()); 951 + } 952 + 953 + #[test] 954 + fn test_decode_integer_empty() { 955 + assert!(decode_integer(&[], 5).is_err()); 956 + } 957 + 958 + // ----------------------------------------------------------------------- 959 + // Huffman coding 960 + // ----------------------------------------------------------------------- 961 + 962 + #[test] 963 + fn test_huffman_encode_www_example_com() { 964 + // RFC 7541 §C.4.1: "www.example.com" Huffman encoding 965 + let encoded = huffman_encode(b"www.example.com"); 966 + assert_eq!( 967 + encoded, 968 + vec![0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff] 969 + ); 970 + } 971 + 972 + #[test] 973 + fn test_huffman_decode_www_example_com() { 974 + let data = vec![ 975 + 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff, 976 + ]; 977 + let decoded = huffman_decode(&data).unwrap(); 978 + assert_eq!(decoded, b"www.example.com"); 979 + } 980 + 981 + #[test] 982 + fn test_huffman_roundtrip() { 983 + let test_strings: &[&[u8]] = &[ 984 + b"", 985 + b"a", 986 + b"hello", 987 + b"www.example.com", 988 + b"no-cache", 989 + b"custom-key", 990 + b"custom-value", 991 + b"/sample/path", 992 + b"Mon, 21 Oct 2013 20:13:21 GMT", 993 + b"https://www.example.com", 994 + ]; 995 + 996 + for s in test_strings { 997 + let encoded = huffman_encode(s); 998 + let decoded = huffman_decode(&encoded).unwrap(); 999 + assert_eq!( 1000 + &decoded, 1001 + s, 1002 + "roundtrip failed for {:?}", 1003 + String::from_utf8_lossy(s) 1004 + ); 1005 + } 1006 + } 1007 + 1008 + #[test] 1009 + fn test_huffman_encode_no_cache() { 1010 + // RFC 7541 §C.4.1 1011 + let encoded = huffman_encode(b"no-cache"); 1012 + assert_eq!(encoded, vec![0xa8, 0xeb, 0x10, 0x64, 0x9c, 0xbf]); 1013 + } 1014 + 1015 + // ----------------------------------------------------------------------- 1016 + // Static table 1017 + // ----------------------------------------------------------------------- 1018 + 1019 + #[test] 1020 + fn test_static_table_size() { 1021 + assert_eq!(STATIC_TABLE.len(), 61); 1022 + } 1023 + 1024 + #[test] 1025 + fn test_static_table_entries() { 1026 + // Verify a few known entries 1027 + assert_eq!(static_table_get(1), Some((&b":authority"[..], &b""[..]))); 1028 + assert_eq!(static_table_get(2), Some((&b":method"[..], &b"GET"[..]))); 1029 + assert_eq!(static_table_get(3), Some((&b":method"[..], &b"POST"[..]))); 1030 + assert_eq!(static_table_get(4), Some((&b":path"[..], &b"/"[..]))); 1031 + assert_eq!(static_table_get(8), Some((&b":status"[..], &b"200"[..]))); 1032 + assert_eq!( 1033 + static_table_get(61), 1034 + Some((&b"www-authenticate"[..], &b""[..])) 1035 + ); 1036 + assert_eq!(static_table_get(0), None); 1037 + assert_eq!(static_table_get(62), None); 1038 + } 1039 + 1040 + #[test] 1041 + fn test_static_table_find() { 1042 + // Full match 1043 + assert_eq!(static_table_find(b":method", b"GET"), Some((2, true))); 1044 + assert_eq!(static_table_find(b":method", b"POST"), Some((3, true))); 1045 + assert_eq!(static_table_find(b":path", b"/"), Some((4, true))); 1046 + 1047 + // Name-only match 1048 + assert_eq!(static_table_find(b":method", b"PUT"), Some((2, false))); 1049 + assert_eq!(static_table_find(b":status", b"201"), Some((8, false))); 1050 + 1051 + // No match 1052 + assert_eq!(static_table_find(b"x-custom", b"value"), None); 1053 + } 1054 + 1055 + // ----------------------------------------------------------------------- 1056 + // Dynamic table 1057 + // ----------------------------------------------------------------------- 1058 + 1059 + #[test] 1060 + fn test_dynamic_table_insert_and_get() { 1061 + let mut table = DynamicTable::new(4096); 1062 + table.insert(HeaderField::new( 1063 + b"custom-key".to_vec(), 1064 + b"custom-value".to_vec(), 1065 + )); 1066 + 1067 + assert_eq!(table.len(), 1); 1068 + let entry = table.get(0).unwrap(); 1069 + assert_eq!(entry.name, b"custom-key"); 1070 + assert_eq!(entry.value, b"custom-value"); 1071 + } 1072 + 1073 + #[test] 1074 + fn test_dynamic_table_fifo_order() { 1075 + let mut table = DynamicTable::new(4096); 1076 + table.insert(HeaderField::new("first", "1")); 1077 + table.insert(HeaderField::new("second", "2")); 1078 + table.insert(HeaderField::new("third", "3")); 1079 + 1080 + // Newest at index 0 1081 + assert_eq!(table.get(0).unwrap().name, b"third"); 1082 + assert_eq!(table.get(1).unwrap().name, b"second"); 1083 + assert_eq!(table.get(2).unwrap().name, b"first"); 1084 + } 1085 + 1086 + #[test] 1087 + fn test_dynamic_table_eviction() { 1088 + // Each entry: name(1) + value(1) + 32 = 34 bytes. Max size = 70 → fits 2 entries. 1089 + let mut table = DynamicTable::new(70); 1090 + table.insert(HeaderField::new("a", "1")); 1091 + table.insert(HeaderField::new("b", "2")); 1092 + assert_eq!(table.len(), 2); 1093 + 1094 + // Third entry evicts oldest 1095 + table.insert(HeaderField::new("c", "3")); 1096 + assert_eq!(table.len(), 2); 1097 + assert_eq!(table.get(0).unwrap().name, b"c"); 1098 + assert_eq!(table.get(1).unwrap().name, b"b"); 1099 + } 1100 + 1101 + #[test] 1102 + fn test_dynamic_table_entry_too_large() { 1103 + // Table size < single entry size → table stays empty 1104 + let mut table = DynamicTable::new(10); 1105 + table.insert(HeaderField::new("custom-key", "custom-value")); 1106 + assert_eq!(table.len(), 0); 1107 + assert_eq!(table.size(), 0); 1108 + } 1109 + 1110 + #[test] 1111 + fn test_dynamic_table_size_update() { 1112 + let mut table = DynamicTable::new(4096); 1113 + table.insert(HeaderField::new("custom-key", "custom-value")); 1114 + assert!(table.size() > 0); 1115 + 1116 + // Shrink to 0 evicts everything 1117 + table.set_max_size(0); 1118 + assert_eq!(table.len(), 0); 1119 + assert_eq!(table.size(), 0); 1120 + } 1121 + 1122 + // ----------------------------------------------------------------------- 1123 + // Full encoder / decoder (RFC 7541 Appendix C examples) 1124 + // ----------------------------------------------------------------------- 1125 + 1126 + #[test] 1127 + fn test_rfc7541_c2_1_literal_with_indexing() { 1128 + // §C.2.1: Literal Header Field with Indexing 1129 + // custom-key: custom-header 1130 + let mut decoder = Decoder::new(4096); 1131 + 1132 + let data = [ 1133 + 0x40, // literal with indexing, new name 1134 + 0x0a, // name length = 10 1135 + b'c', b'u', b's', b't', b'o', b'm', b'-', b'k', b'e', b'y', 1136 + 0x0d, // value length = 13 1137 + b'c', b'u', b's', b't', b'o', b'm', b'-', b'h', b'e', b'a', b'd', b'e', b'r', 1138 + ]; 1139 + 1140 + let headers = decoder.decode(&data).unwrap(); 1141 + assert_eq!(headers.len(), 1); 1142 + assert_eq!(headers[0].name, b"custom-key"); 1143 + assert_eq!(headers[0].value, b"custom-header"); 1144 + 1145 + // Entry should be in dynamic table 1146 + assert_eq!(decoder.dynamic_table().len(), 1); 1147 + assert_eq!(decoder.dynamic_table().size(), 55); // 10 + 13 + 32 1148 + } 1149 + 1150 + #[test] 1151 + fn test_rfc7541_c2_2_literal_without_indexing() { 1152 + // §C.2.2: Literal Header Field without Indexing 1153 + // :path: /sample/path 1154 + let mut decoder = Decoder::new(4096); 1155 + 1156 + let data = [ 1157 + 0x04, // literal without indexing, indexed name (index=4 → :path) 1158 + 0x0c, // value length = 12 1159 + b'/', b's', b'a', b'm', b'p', b'l', b'e', b'/', b'p', b'a', b't', b'h', 1160 + ]; 1161 + 1162 + let headers = decoder.decode(&data).unwrap(); 1163 + assert_eq!(headers.len(), 1); 1164 + assert_eq!(headers[0].name, b":path"); 1165 + assert_eq!(headers[0].value, b"/sample/path"); 1166 + 1167 + // Should NOT be added to dynamic table 1168 + assert_eq!(decoder.dynamic_table().len(), 0); 1169 + } 1170 + 1171 + #[test] 1172 + fn test_rfc7541_c2_3_literal_never_indexed() { 1173 + // §C.2.3: Literal Header Field Never Indexed 1174 + // password: secret 1175 + let mut decoder = Decoder::new(4096); 1176 + 1177 + let data = [ 1178 + 0x10, // never indexed, new name 1179 + 0x08, // name length = 8 1180 + b'p', b'a', b's', b's', b'w', b'o', b'r', b'd', 0x06, // value length = 6 1181 + b's', b'e', b'c', b'r', b'e', b't', 1182 + ]; 1183 + 1184 + let headers = decoder.decode(&data).unwrap(); 1185 + assert_eq!(headers.len(), 1); 1186 + assert_eq!(headers[0].name, b"password"); 1187 + assert_eq!(headers[0].value, b"secret"); 1188 + 1189 + // Should NOT be added to dynamic table 1190 + assert_eq!(decoder.dynamic_table().len(), 0); 1191 + } 1192 + 1193 + #[test] 1194 + fn test_rfc7541_c2_4_indexed_header_field() { 1195 + // §C.2.4: Indexed Header Field 1196 + // :method: GET (index 2) 1197 + let mut decoder = Decoder::new(4096); 1198 + 1199 + let data = [0x82]; // indexed, index=2 1200 + let headers = decoder.decode(&data).unwrap(); 1201 + assert_eq!(headers.len(), 1); 1202 + assert_eq!(headers[0].name, b":method"); 1203 + assert_eq!(headers[0].value, b"GET"); 1204 + } 1205 + 1206 + #[test] 1207 + fn test_rfc7541_c3_request_without_huffman() { 1208 + // §C.3: Request Examples without Huffman Coding 1209 + let mut decoder = Decoder::new(4096); 1210 + 1211 + // First request: :method GET, :scheme http, :path /, :authority www.example.com 1212 + let req1 = [ 1213 + 0x82, // :method: GET (indexed 2) 1214 + 0x86, // :scheme: http (indexed 6) 1215 + 0x84, // :path: / (indexed 4) 1216 + 0x41, // :authority (indexed name 1), literal with indexing 1217 + 0x0f, // value length = 15 1218 + b'w', b'w', b'w', b'.', b'e', b'x', b'a', b'm', b'p', b'l', b'e', b'.', b'c', b'o', 1219 + b'm', 1220 + ]; 1221 + let headers1 = decoder.decode(&req1).unwrap(); 1222 + assert_eq!(headers1.len(), 4); 1223 + assert_eq!( 1224 + headers1[0], 1225 + HeaderField::new(b":method".to_vec(), b"GET".to_vec()) 1226 + ); 1227 + assert_eq!( 1228 + headers1[1], 1229 + HeaderField::new(b":scheme".to_vec(), b"http".to_vec()) 1230 + ); 1231 + assert_eq!( 1232 + headers1[2], 1233 + HeaderField::new(b":path".to_vec(), b"/".to_vec()) 1234 + ); 1235 + assert_eq!( 1236 + headers1[3], 1237 + HeaderField::new(b":authority".to_vec(), b"www.example.com".to_vec()) 1238 + ); 1239 + assert_eq!(decoder.dynamic_table().len(), 1); 1240 + assert_eq!(decoder.dynamic_table().size(), 57); // 10 + 15 + 32 1241 + 1242 + // Second request: :method GET, :scheme http, :path /, :authority www.example.com, cache-control: no-cache 1243 + let req2 = [ 1244 + 0x82, // :method: GET (indexed 2) 1245 + 0x86, // :scheme: http (indexed 6) 1246 + 0x84, // :path: / (indexed 4) 1247 + 0xbe, // :authority: www.example.com (indexed 62, dynamic table entry 0) 1248 + 0x58, // cache-control (indexed name 24), literal with indexing 1249 + 0x08, // value length = 8 1250 + b'n', b'o', b'-', b'c', b'a', b'c', b'h', b'e', 1251 + ]; 1252 + let headers2 = decoder.decode(&req2).unwrap(); 1253 + assert_eq!(headers2.len(), 5); 1254 + assert_eq!( 1255 + headers2[3], 1256 + HeaderField::new(b":authority".to_vec(), b"www.example.com".to_vec()) 1257 + ); 1258 + assert_eq!( 1259 + headers2[4], 1260 + HeaderField::new(b"cache-control".to_vec(), b"no-cache".to_vec()) 1261 + ); 1262 + assert_eq!(decoder.dynamic_table().len(), 2); 1263 + assert_eq!(decoder.dynamic_table().size(), 110); // 57 + 13 + 8 + 32 1264 + 1265 + // Third request: :method GET, :scheme https, :path /index.html, :authority www.example.com, custom-key: custom-value 1266 + let req3 = [ 1267 + 0x82, // :method: GET (indexed 2) 1268 + 0x87, // :scheme: https (indexed 7) 1269 + 0x85, // :path: /index.html (indexed 5) 1270 + 0xbf, // :authority: www.example.com (indexed 63, dynamic table entry 1) 1271 + 0x40, // literal with indexing, new name 1272 + 0x0a, // name length = 10 1273 + b'c', b'u', b's', b't', b'o', b'm', b'-', b'k', b'e', b'y', 1274 + 0x0c, // value length = 12 1275 + b'c', b'u', b's', b't', b'o', b'm', b'-', b'v', b'a', b'l', b'u', b'e', 1276 + ]; 1277 + let headers3 = decoder.decode(&req3).unwrap(); 1278 + assert_eq!(headers3.len(), 5); 1279 + assert_eq!( 1280 + headers3[3], 1281 + HeaderField::new(b":authority".to_vec(), b"www.example.com".to_vec()) 1282 + ); 1283 + assert_eq!( 1284 + headers3[4], 1285 + HeaderField::new(b"custom-key".to_vec(), b"custom-value".to_vec()) 1286 + ); 1287 + assert_eq!(decoder.dynamic_table().len(), 3); 1288 + assert_eq!(decoder.dynamic_table().size(), 164); // 110 + 10 + 12 + 32 1289 + } 1290 + 1291 + #[test] 1292 + fn test_rfc7541_c4_request_with_huffman() { 1293 + // §C.4: Request Examples with Huffman Coding 1294 + let mut decoder = Decoder::new(4096); 1295 + 1296 + // First request 1297 + let req1 = [ 1298 + 0x82, // :method: GET 1299 + 0x86, // :scheme: http 1300 + 0x84, // :path: / 1301 + 0x41, // :authority, literal with indexing 1302 + 0x8c, // Huffman-encoded value, length 12 1303 + 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff, 1304 + ]; 1305 + let headers1 = decoder.decode(&req1).unwrap(); 1306 + assert_eq!(headers1.len(), 4); 1307 + assert_eq!( 1308 + headers1[3], 1309 + HeaderField::new(b":authority".to_vec(), b"www.example.com".to_vec()) 1310 + ); 1311 + assert_eq!(decoder.dynamic_table().size(), 57); 1312 + 1313 + // Second request 1314 + let req2 = [ 1315 + 0x82, // :method: GET 1316 + 0x86, // :scheme: http 1317 + 0x84, // :path: / 1318 + 0xbe, // :authority (dyn 62) 1319 + 0x58, // cache-control, literal with indexing 1320 + 0x86, // Huffman-encoded value, length 6 1321 + 0xa8, 0xeb, 0x10, 0x64, 0x9c, 0xbf, 1322 + ]; 1323 + let headers2 = decoder.decode(&req2).unwrap(); 1324 + assert_eq!(headers2.len(), 5); 1325 + assert_eq!( 1326 + headers2[4], 1327 + HeaderField::new(b"cache-control".to_vec(), b"no-cache".to_vec()) 1328 + ); 1329 + assert_eq!(decoder.dynamic_table().size(), 110); 1330 + 1331 + // Third request 1332 + let req3 = [ 1333 + 0x82, // :method: GET 1334 + 0x87, // :scheme: https 1335 + 0x85, // :path: /index.html 1336 + 0xbf, // :authority (dyn 63) 1337 + 0x40, // literal with indexing, new name 1338 + 0x88, // Huffman-encoded name, length 8 1339 + 0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xa9, 0x7d, 0x7f, 1340 + 0x89, // Huffman-encoded value, length 9 1341 + 0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xb8, 0xe8, 0xb4, 0xbf, 1342 + ]; 1343 + let headers3 = decoder.decode(&req3).unwrap(); 1344 + assert_eq!(headers3.len(), 5); 1345 + assert_eq!( 1346 + headers3[4], 1347 + HeaderField::new(b"custom-key".to_vec(), b"custom-value".to_vec()) 1348 + ); 1349 + assert_eq!(decoder.dynamic_table().size(), 164); 1350 + } 1351 + 1352 + #[test] 1353 + fn test_rfc7541_c5_response_without_huffman() { 1354 + // §C.5: Response Examples without Huffman Coding 1355 + // Dynamic table max size = 256 1356 + let mut decoder = Decoder::new(256); 1357 + 1358 + // First response 1359 + let res1 = [ 1360 + 0x48, // :status (indexed name 8), literal with indexing 1361 + 0x03, // value length = 3 1362 + b'3', b'0', b'2', 0x58, // cache-control (indexed name 24), literal with indexing 1363 + 0x07, // value length = 7 1364 + b'p', b'r', b'i', b'v', b'a', b't', b'e', 1365 + 0x61, // date (indexed name 33), literal with indexing 1366 + 0x1d, // value length = 29 1367 + b'M', b'o', b'n', b',', b' ', b'2', b'1', b' ', b'O', b'c', b't', b' ', b'2', b'0', 1368 + b'1', b'3', b' ', b'2', b'0', b':', b'1', b'3', b':', b'2', b'1', b' ', b'G', b'M', 1369 + b'T', 0x6e, // location (indexed name 46), literal with indexing 1370 + 0x17, // value length = 23 1371 + b'h', b't', b't', b'p', b's', b':', b'/', b'/', b'w', b'w', b'w', b'.', b'e', b'x', 1372 + b'a', b'm', b'p', b'l', b'e', b'.', b'c', b'o', b'm', 1373 + ]; 1374 + let headers1 = decoder.decode(&res1).unwrap(); 1375 + assert_eq!(headers1.len(), 4); 1376 + assert_eq!( 1377 + headers1[0], 1378 + HeaderField::new(b":status".to_vec(), b"302".to_vec()) 1379 + ); 1380 + assert_eq!( 1381 + headers1[1], 1382 + HeaderField::new(b"cache-control".to_vec(), b"private".to_vec()) 1383 + ); 1384 + assert_eq!( 1385 + headers1[2], 1386 + HeaderField::new(b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()) 1387 + ); 1388 + assert_eq!( 1389 + headers1[3], 1390 + HeaderField::new(b"location".to_vec(), b"https://www.example.com".to_vec()) 1391 + ); 1392 + // Dynamic table: only entries that fit in 256 bytes 1393 + // location: 8+23+32=63, date: 4+29+32=65, cache-control: 13+7+32=52, :status: 7+3+32=42 1394 + // Total = 63+65+52+42=222. All fit. 1395 + assert_eq!(decoder.dynamic_table().len(), 4); 1396 + assert_eq!(decoder.dynamic_table().size(), 222); 1397 + 1398 + // Second response 1399 + let res2 = [ 1400 + 0x48, // :status, literal with indexing 1401 + 0x03, b'3', b'0', b'7', 0xc1, // cache-control: private (dyn table index 63) 1402 + 0xc0, // date (dyn table index 64, but after insert above it shifted) 1403 + 0xbf, // location (dyn table index 65) 1404 + ]; 1405 + let headers2 = decoder.decode(&res2).unwrap(); 1406 + assert_eq!(headers2.len(), 4); 1407 + assert_eq!( 1408 + headers2[0], 1409 + HeaderField::new(b":status".to_vec(), b"307".to_vec()) 1410 + ); 1411 + assert_eq!( 1412 + headers2[1], 1413 + HeaderField::new(b"cache-control".to_vec(), b"private".to_vec()) 1414 + ); 1415 + 1416 + // Third response 1417 + let res3 = [ 1418 + 0x88, // :status: 200 (static index 8) 1419 + 0xc1, // cache-control: private (dyn entry) 1420 + 0x61, // date, literal with indexing 1421 + 0x1d, b'M', b'o', b'n', b',', b' ', b'2', b'1', b' ', b'O', b'c', b't', b' ', b'2', 1422 + b'0', b'1', b'3', b' ', b'2', b'0', b':', b'1', b'3', b':', b'2', b'2', b' ', b'G', 1423 + b'M', b'T', 0xc0, // location (dyn entry) 1424 + 0x5a, // content-encoding (indexed name 26), literal with indexing 1425 + 0x04, // value length = 4 1426 + b'g', b'z', b'i', b'p', 1427 + ]; 1428 + let headers3 = decoder.decode(&res3).unwrap(); 1429 + assert_eq!(headers3.len(), 5); 1430 + assert_eq!( 1431 + headers3[0], 1432 + HeaderField::new(b":status".to_vec(), b"200".to_vec()) 1433 + ); 1434 + assert_eq!( 1435 + headers3[4], 1436 + HeaderField::new(b"content-encoding".to_vec(), b"gzip".to_vec()) 1437 + ); 1438 + } 1439 + 1440 + #[test] 1441 + fn test_rfc7541_c6_response_with_huffman() { 1442 + // §C.6: Response Examples with Huffman Coding 1443 + let mut decoder = Decoder::new(256); 1444 + 1445 + // First response (Huffman encoded) 1446 + let res1 = [ 1447 + 0x48, // :status, literal with indexing 1448 + 0x82, 0x64, 0x02, // Huffman: "302" 1449 + 0x58, // cache-control, literal with indexing 1450 + 0x85, 0xae, 0xc3, 0x77, 0x1a, 0x4b, // Huffman: "private" 1451 + 0x61, // date, literal with indexing 1452 + 0x96, // Huffman-encoded, length 22 1453 + 0xd0, 0x7a, 0xbe, 0x94, 0x10, 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, 0x95, 0x04, 0x0b, 1454 + 0x81, 0x66, 0xe0, 0x82, 0xa6, 0x2d, 0x1b, 0xff, 1455 + 0x6e, // location, literal with indexing 1456 + 0x91, // Huffman-encoded, length 17 1457 + 0x9d, 0x29, 0xad, 0x17, 0x18, 0x63, 0xc7, 0x8f, 0x0b, 0x97, 0xc8, 0xe9, 0xae, 0x82, 1458 + 0xae, 0x43, 0xd3, 1459 + ]; 1460 + let headers1 = decoder.decode(&res1).unwrap(); 1461 + assert_eq!(headers1.len(), 4); 1462 + assert_eq!( 1463 + headers1[0], 1464 + HeaderField::new(b":status".to_vec(), b"302".to_vec()) 1465 + ); 1466 + assert_eq!( 1467 + headers1[1], 1468 + HeaderField::new(b"cache-control".to_vec(), b"private".to_vec()) 1469 + ); 1470 + assert_eq!( 1471 + headers1[2], 1472 + HeaderField::new(b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()) 1473 + ); 1474 + assert_eq!( 1475 + headers1[3], 1476 + HeaderField::new(b"location".to_vec(), b"https://www.example.com".to_vec()) 1477 + ); 1478 + assert_eq!(decoder.dynamic_table().size(), 222); 1479 + 1480 + // Second response 1481 + let res2 = [ 1482 + 0x48, // :status, literal with indexing 1483 + 0x83, 0x64, 0x0e, 0xff, // Huffman: "307" 1484 + 0xc1, // cache-control: private 1485 + 0xc0, // date 1486 + 0xbf, // location 1487 + ]; 1488 + let headers2 = decoder.decode(&res2).unwrap(); 1489 + assert_eq!(headers2.len(), 4); 1490 + assert_eq!( 1491 + headers2[0], 1492 + HeaderField::new(b":status".to_vec(), b"307".to_vec()) 1493 + ); 1494 + } 1495 + 1496 + // ----------------------------------------------------------------------- 1497 + // Encoder + Decoder roundtrip 1498 + // ----------------------------------------------------------------------- 1499 + 1500 + #[test] 1501 + fn test_encoder_decoder_roundtrip() { 1502 + let mut encoder = Encoder::new(4096); 1503 + let mut decoder = Decoder::new(4096); 1504 + 1505 + let headers = vec![ 1506 + HeaderField::new(b":method".to_vec(), b"GET".to_vec()), 1507 + HeaderField::new(b":scheme".to_vec(), b"https".to_vec()), 1508 + HeaderField::new(b":path".to_vec(), b"/".to_vec()), 1509 + HeaderField::new(b":authority".to_vec(), b"www.example.com".to_vec()), 1510 + HeaderField::new(b"user-agent".to_vec(), b"we-browser/0.1".to_vec()), 1511 + ]; 1512 + 1513 + let encoded = encoder.encode(&headers); 1514 + let decoded = decoder.decode(&encoded).unwrap(); 1515 + 1516 + assert_eq!(decoded, headers); 1517 + } 1518 + 1519 + #[test] 1520 + fn test_encoder_decoder_multiple_requests() { 1521 + let mut encoder = Encoder::new(4096); 1522 + let mut decoder = Decoder::new(4096); 1523 + 1524 + // First request 1525 + let req1 = vec![ 1526 + HeaderField::new(b":method".to_vec(), b"GET".to_vec()), 1527 + HeaderField::new(b":path".to_vec(), b"/".to_vec()), 1528 + HeaderField::new(b"host".to_vec(), b"example.com".to_vec()), 1529 + ]; 1530 + let encoded1 = encoder.encode(&req1); 1531 + let decoded1 = decoder.decode(&encoded1).unwrap(); 1532 + assert_eq!(decoded1, req1); 1533 + 1534 + // Second request — should benefit from dynamic table 1535 + let req2 = vec![ 1536 + HeaderField::new(b":method".to_vec(), b"GET".to_vec()), 1537 + HeaderField::new(b":path".to_vec(), b"/style.css".to_vec()), 1538 + HeaderField::new(b"host".to_vec(), b"example.com".to_vec()), 1539 + ]; 1540 + let encoded2 = encoder.encode(&req2); 1541 + let decoded2 = decoder.decode(&encoded2).unwrap(); 1542 + assert_eq!(decoded2, req2); 1543 + 1544 + // The second encoding should be smaller (host is in dynamic table) 1545 + assert!(encoded2.len() <= encoded1.len()); 1546 + } 1547 + 1548 + #[test] 1549 + fn test_encoder_sensitive_headers() { 1550 + let mut encoder = Encoder::new(4096); 1551 + let mut decoder = Decoder::new(4096); 1552 + 1553 + let headers = vec![ 1554 + ( 1555 + HeaderField::new(b":method".to_vec(), b"GET".to_vec()), 1556 + Sensitive::No, 1557 + ), 1558 + ( 1559 + HeaderField::new(b"cookie".to_vec(), b"session=abc123".to_vec()), 1560 + Sensitive::Yes, 1561 + ), 1562 + ]; 1563 + 1564 + let encoded = encoder.encode_with_sensitivity(&headers); 1565 + let decoded = decoder.decode(&encoded).unwrap(); 1566 + 1567 + assert_eq!(decoded.len(), 2); 1568 + assert_eq!(decoded[0].name, b":method"); 1569 + assert_eq!(decoded[1].name, b"cookie"); 1570 + assert_eq!(decoded[1].value, b"session=abc123"); 1571 + 1572 + // Cookie should NOT be in decoder's dynamic table 1573 + // (it was never-indexed), but :method was indexed (static match) 1574 + // So dynamic table should be empty (both were static/never-indexed) 1575 + assert_eq!(decoder.dynamic_table().len(), 0); 1576 + } 1577 + 1578 + #[test] 1579 + fn test_encoder_without_huffman() { 1580 + let mut encoder = Encoder::new(4096); 1581 + encoder.set_huffman(false); 1582 + let mut decoder = Decoder::new(4096); 1583 + 1584 + let headers = vec![ 1585 + HeaderField::new(b":method".to_vec(), b"GET".to_vec()), 1586 + HeaderField::new(b"x-custom".to_vec(), b"hello".to_vec()), 1587 + ]; 1588 + 1589 + let encoded = encoder.encode(&headers); 1590 + let decoded = decoder.decode(&encoded).unwrap(); 1591 + assert_eq!(decoded, headers); 1592 + } 1593 + 1594 + #[test] 1595 + fn test_table_size_update() { 1596 + let mut encoder = Encoder::new(4096); 1597 + let mut decoder = Decoder::new(4096); 1598 + 1599 + // Encode some headers to populate dynamic table 1600 + let headers = vec![HeaderField::new( 1601 + b"custom-key".to_vec(), 1602 + b"custom-value".to_vec(), 1603 + )]; 1604 + let encoded = encoder.encode(&headers); 1605 + decoder.decode(&encoded).unwrap(); 1606 + 1607 + assert_eq!(encoder.dynamic_table().len(), 1); 1608 + assert_eq!(decoder.dynamic_table().len(), 1); 1609 + 1610 + // Encode a table size update to 0 (clears table) 1611 + let mut buf = Vec::new(); 1612 + encoder.encode_table_size_update(&mut buf, 0); 1613 + // Then encode more headers 1614 + let headers2 = vec![HeaderField::new(b":method".to_vec(), b"GET".to_vec())]; 1615 + buf.extend(encoder.encode(&headers2)); 1616 + 1617 + let decoded = decoder.decode(&buf).unwrap(); 1618 + assert_eq!(decoded, headers2); 1619 + assert_eq!(decoder.dynamic_table().len(), 0); 1620 + } 1621 + 1622 + #[test] 1623 + fn test_decode_invalid_index() { 1624 + let mut decoder = Decoder::new(4096); 1625 + // Index 0 is invalid 1626 + let data = [0x80]; 1627 + assert!(decoder.decode(&data).is_err()); 1628 + 1629 + // Index far beyond table 1630 + let data = [0xFF, 0x80, 0x80, 0x01]; // large index 1631 + assert!(decoder.decode(&data).is_err()); 1632 + } 1633 + 1634 + #[test] 1635 + fn test_decode_empty() { 1636 + let mut decoder = Decoder::new(4096); 1637 + let headers = decoder.decode(&[]).unwrap(); 1638 + assert!(headers.is_empty()); 1639 + } 1640 + 1641 + #[test] 1642 + fn test_dynamic_table_entry_size() { 1643 + let field = HeaderField::new(b"custom-key".to_vec(), b"custom-value".to_vec()); 1644 + assert_eq!(field.size(), 10 + 12 + 32); // = 54 1645 + } 1646 + 1647 + // ----------------------------------------------------------------------- 1648 + // String encoding / decoding 1649 + // ----------------------------------------------------------------------- 1650 + 1651 + #[test] 1652 + fn test_string_encode_decode_plain() { 1653 + let mut buf = Vec::new(); 1654 + encode_string(&mut buf, b"hello", false); 1655 + let (decoded, consumed) = decode_string(&buf).unwrap(); 1656 + assert_eq!(decoded, b"hello"); 1657 + assert_eq!(consumed, buf.len()); 1658 + } 1659 + 1660 + #[test] 1661 + fn test_string_encode_decode_huffman() { 1662 + let mut buf = Vec::new(); 1663 + encode_string(&mut buf, b"www.example.com", true); 1664 + let (decoded, consumed) = decode_string(&buf).unwrap(); 1665 + assert_eq!(decoded, b"www.example.com"); 1666 + assert_eq!(consumed, buf.len()); 1667 + } 1668 + 1669 + #[test] 1670 + fn test_string_decode_truncated() { 1671 + let data = [0x05, b'h', b'e']; // claims length 5 but only 2 bytes 1672 + assert!(decode_string(&data).is_err()); 1673 + } 1674 + }
+3
crates/net/src/http2/mod.rs
··· 1 + //! HTTP/2 protocol implementation. 2 + 3 + pub mod hpack;
+1
crates/net/src/lib.rs
··· 5 5 pub mod cors; 6 6 pub mod dns; 7 7 pub mod http; 8 + pub mod http2; 8 9 pub mod referrer; 9 10 pub mod tcp; 10 11 pub mod tls;