1pub mod error;
44pub mod frame;
45pub mod hpack;
46
47pub use error::{H2ParseError, ReassemblerOverflowKind};
48#[allow(unused_imports)]
49pub use frame::{FrameHeader, DEFAULT_MAX_FRAME_SIZE};
50#[allow(unused_imports)]
51pub use hpack::{AuthorityProvenance, DecodedAuthority, HpackDecoder};
52
53use frame::{
54 parse_one_frame, strip_headers_padding_and_priority, FLAG_END_HEADERS, FRAME_TYPE_CONTINUATION,
55 FRAME_TYPE_HEADERS, FRAME_TYPE_SETTINGS,
56};
57use std::collections::HashMap;
58
59pub const HTTP2_PREFACE: &[u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
61
62pub const MAX_HEADER_BLOCK_SIZE: usize = 64 * 1024;
68
69pub fn is_h2c_preface(bytes: &[u8]) -> bool {
72 bytes.len() >= HTTP2_PREFACE.len() && &bytes[..HTTP2_PREFACE.len()] == HTTP2_PREFACE
73}
74
75pub type ReassembledBlock<'a> = (Vec<u8>, &'a [u8]);
79
80pub fn reassemble_header_block(buf: &[u8]) -> Result<Option<ReassembledBlock<'_>>, H2ParseError> {
92 let mut cursor = buf;
93 let mut accumulated: Vec<u8> = Vec::new();
94 let mut first_frame = true;
95
96 loop {
97 let (header, payload, rest) = match parse_one_frame(cursor)? {
98 Some(p) => p,
99 None => return Ok(None),
100 };
101
102 if first_frame {
103 if header.frame_type != FRAME_TYPE_HEADERS {
104 return Err(H2ParseError::UnexpectedFirstFrame {
105 frame_type: header.frame_type,
106 });
107 }
108 let block = strip_headers_padding_and_priority(payload, header.flags)?;
109 if accumulated.len() + block.len() > MAX_HEADER_BLOCK_SIZE {
110 return Err(H2ParseError::HpackOversizedHeaderBlock {
111 total: accumulated.len() + block.len(),
112 max: MAX_HEADER_BLOCK_SIZE,
113 });
114 }
115 accumulated.extend_from_slice(block);
116 cursor = rest;
117 if header.flags & FLAG_END_HEADERS != 0 {
118 return Ok(Some((accumulated, cursor)));
119 }
120 first_frame = false;
121 } else {
122 if header.frame_type != FRAME_TYPE_CONTINUATION {
123 return Err(H2ParseError::InterleavedFrame {
124 frame_type: header.frame_type,
125 });
126 }
127 if accumulated.len() + payload.len() > MAX_HEADER_BLOCK_SIZE {
129 return Err(H2ParseError::HpackOversizedHeaderBlock {
130 total: accumulated.len() + payload.len(),
131 max: MAX_HEADER_BLOCK_SIZE,
132 });
133 }
134 accumulated.extend_from_slice(payload);
135 cursor = rest;
136 if header.flags & FLAG_END_HEADERS != 0 {
137 return Ok(Some((accumulated, cursor)));
138 }
139 }
140 }
141}
142
143pub fn extract_h2_authority(after_preface: &[u8]) -> Result<Option<String>, H2ParseError> {
154 extract_h2_authority_with(after_preface, &mut HpackDecoder::new())
155 .map(|opt| opt.map(|d| d.value))
156}
157
158pub fn extract_h2_authority_with(
163 after_preface: &[u8],
164 decoder: &mut HpackDecoder,
165) -> Result<Option<DecodedAuthority>, H2ParseError> {
166 let mut cursor = after_preface;
167
168 if let Some((header, _payload, rest)) = parse_one_frame(cursor)? {
170 if header.frame_type == FRAME_TYPE_SETTINGS {
171 cursor = rest;
172 }
173 } else {
174 return Ok(None);
175 }
176
177 let (block, _rest_after) = match reassemble_header_block(cursor)? {
179 Some(p) => p,
180 None => return Ok(None),
181 };
182
183 decoder.decode_block(&block)
185}
186
187pub const REASSEMBLER_TOTAL_IN_FLIGHT_MAX: usize = 256 * 1024;
217
218pub const REASSEMBLER_MAX_CONCURRENT_STREAMS: usize = 64;
225
226pub struct H2StreamReassembler {
238 blocks: HashMap<u32, Vec<u8>>,
240 active_stream: Option<u32>,
244 total_in_flight: usize,
247}
248
249impl Default for H2StreamReassembler {
250 fn default() -> Self {
251 Self::new()
252 }
253}
254
255impl H2StreamReassembler {
256 pub fn new() -> Self {
258 Self {
259 blocks: HashMap::new(),
260 active_stream: None,
261 total_in_flight: 0,
262 }
263 }
264
265 pub fn ingest(
284 &mut self,
285 frame: &FrameHeader,
286 payload: &[u8],
287 ) -> Result<Option<(u32, Vec<u8>)>, H2ParseError> {
288 if let Some(active) = self.active_stream {
293 if frame.frame_type == FRAME_TYPE_CONTINUATION {
294 if frame.stream_id != active {
295 return Err(H2ParseError::InterleavedFrame {
296 frame_type: frame.frame_type,
297 });
298 }
299 let entry = self
301 .blocks
302 .get_mut(&active)
303 .expect("active_stream invariant: blocks contains active");
304 if entry.len() + payload.len() > MAX_HEADER_BLOCK_SIZE {
305 return Err(H2ParseError::ReassemblerOverflow {
306 kind: ReassemblerOverflowKind::PerStreamBlock,
307 });
308 }
309 if self.total_in_flight + payload.len() > REASSEMBLER_TOTAL_IN_FLIGHT_MAX {
310 return Err(H2ParseError::ReassemblerOverflow {
311 kind: ReassemblerOverflowKind::TotalInFlight,
312 });
313 }
314 entry.extend_from_slice(payload);
315 self.total_in_flight += payload.len();
316 if frame.flags & FLAG_END_HEADERS != 0 {
317 let block = self
318 .blocks
319 .remove(&active)
320 .expect("active reassembly entry");
321 self.total_in_flight = self.total_in_flight.saturating_sub(block.len());
322 self.active_stream = None;
323 return Ok(Some((active, block)));
324 }
325 return Ok(None);
326 }
327 return Err(H2ParseError::InterleavedFrame {
329 frame_type: frame.frame_type,
330 });
331 }
332
333 if frame.frame_type == FRAME_TYPE_HEADERS {
335 if self.blocks.len() >= REASSEMBLER_MAX_CONCURRENT_STREAMS {
336 return Err(H2ParseError::ReassemblerOverflow {
337 kind: ReassemblerOverflowKind::ConcurrentStreams,
338 });
339 }
340 let inner = strip_headers_padding_and_priority(payload, frame.flags)?;
341 if inner.len() > MAX_HEADER_BLOCK_SIZE {
342 return Err(H2ParseError::ReassemblerOverflow {
343 kind: ReassemblerOverflowKind::PerStreamBlock,
344 });
345 }
346 if self.total_in_flight + inner.len() > REASSEMBLER_TOTAL_IN_FLIGHT_MAX {
347 return Err(H2ParseError::ReassemblerOverflow {
348 kind: ReassemblerOverflowKind::TotalInFlight,
349 });
350 }
351 if frame.flags & FLAG_END_HEADERS != 0 {
352 return Ok(Some((frame.stream_id, inner.to_vec())));
355 }
356 self.blocks.insert(frame.stream_id, inner.to_vec());
357 self.total_in_flight += inner.len();
358 self.active_stream = Some(frame.stream_id);
359 return Ok(None);
360 }
361
362 if frame.frame_type == FRAME_TYPE_CONTINUATION {
364 return Err(H2ParseError::InterleavedFrame {
365 frame_type: frame.frame_type,
366 });
367 }
368
369 Ok(None)
372 }
373
374 #[cfg(test)]
377 pub fn pending_count(&self) -> usize {
378 self.blocks.len()
379 }
380}
381
382#[derive(Debug, Clone)]
385pub struct H2HeadersDecoded {
386 pub stream_id: u32,
388 pub authority: Option<String>,
392 pub via_dynamic_table: bool,
394 pub via_huffman: bool,
396}
397
398pub struct H2ConnectionDecoder {
406 hpack: HpackDecoder,
407 reassembler: H2StreamReassembler,
408}
409
410impl Default for H2ConnectionDecoder {
411 fn default() -> Self {
412 Self::new()
413 }
414}
415
416impl H2ConnectionDecoder {
417 pub fn new() -> Self {
419 Self {
420 hpack: HpackDecoder::new(),
421 reassembler: H2StreamReassembler::new(),
422 }
423 }
424
425 pub fn feed_frame(
433 &mut self,
434 frame: &FrameHeader,
435 payload: &[u8],
436 ) -> Result<Option<H2HeadersDecoded>, H2ParseError> {
437 let (stream_id, block) = match self.reassembler.ingest(frame, payload)? {
438 Some(p) => p,
439 None => return Ok(None),
440 };
441 let decoded = self.hpack.decode_block(&block)?;
442 let (authority, via_dynamic_table, via_huffman) = match decoded {
443 Some(d) => {
444 let dyn_indexed = matches!(d.provenance, AuthorityProvenance::DynamicIndexed);
445 let huff = matches!(d.provenance, AuthorityProvenance::Huffman);
446 (Some(d.value), dyn_indexed, huff)
447 }
448 None => (None, false, false),
449 };
450 Ok(Some(H2HeadersDecoded {
451 stream_id,
452 authority,
453 via_dynamic_table,
454 via_huffman,
455 }))
456 }
457}
458
459#[cfg(test)]
460pub(crate) mod test_helpers {
461 use super::frame::{FRAME_TYPE_CONTINUATION, FRAME_TYPE_HEADERS, FRAME_TYPE_SETTINGS};
467 use super::hpack::huffman;
468
469 pub fn frame_header(length: u32, frame_type: u8, flags: u8, stream_id: u32) -> Vec<u8> {
471 let mut out = Vec::with_capacity(9);
472 out.push(((length >> 16) & 0xFF) as u8);
473 out.push(((length >> 8) & 0xFF) as u8);
474 out.push((length & 0xFF) as u8);
475 out.push(frame_type);
476 out.push(flags);
477 out.extend_from_slice(&(stream_id & 0x7FFF_FFFF).to_be_bytes());
478 out
479 }
480
481 pub fn empty_settings_frame() -> Vec<u8> {
483 frame_header(0, FRAME_TYPE_SETTINGS, 0, 0)
484 }
485
486 pub fn headers_frame(header_block: &[u8]) -> Vec<u8> {
489 let length = header_block.len() as u32;
490 let flags = super::frame::FLAG_END_HEADERS | 0x1; let mut out = frame_header(length, FRAME_TYPE_HEADERS, flags, 1);
492 out.extend_from_slice(header_block);
493 out
494 }
495
496 pub fn continuation_fragmented_headers(header_block: &[u8]) -> Vec<u8> {
499 let length = header_block.len() as u32;
500 let flags = 0x0;
501 let mut out = frame_header(length, FRAME_TYPE_HEADERS, flags, 1);
502 out.extend_from_slice(header_block);
503 out
504 }
505
506 pub fn continuation_frame(header_block: &[u8], end_headers: bool) -> Vec<u8> {
508 let length = header_block.len() as u32;
509 let flags = if end_headers {
510 super::frame::FLAG_END_HEADERS
511 } else {
512 0x0
513 };
514 let mut out = frame_header(length, FRAME_TYPE_CONTINUATION, flags, 1);
515 out.extend_from_slice(header_block);
516 out
517 }
518
519 pub fn hpack_literal_indexed_name(name_index: u8, value: &str) -> Vec<u8> {
523 let mut out = Vec::new();
524 out.push(0x40 | (name_index & 0x3F));
525 encode_literal_string(&mut out, value);
526 out
527 }
528
529 pub fn hpack_literal_indexed_with_name(name: &str, value: &str) -> Vec<u8> {
532 let mut out = Vec::new();
533 out.push(0x40);
534 encode_literal_string(&mut out, name);
535 encode_literal_string(&mut out, value);
536 out
537 }
538
539 pub fn hpack_literal_no_indexing(name_index: u8, value: &str) -> Vec<u8> {
543 let mut out = Vec::new();
544 out.push(name_index & 0x0F);
545 encode_literal_string(&mut out, value);
546 out
547 }
548
549 pub fn hpack_indexed(index: u8) -> Vec<u8> {
551 vec![0x80 | (index & 0x7F)]
552 }
553
554 pub fn hpack_literal_indexed_name_huffman(name_index: u8, value: &str) -> Vec<u8> {
557 let mut out = Vec::new();
558 out.push(0x40 | (name_index & 0x3F));
559 let payload = huffman::encode(value);
560 if payload.len() < 0x7F {
562 out.push(0x80 | payload.len() as u8);
563 } else {
564 out.push(0xFF);
565 let mut v = payload.len() as u64 - 0x7F;
566 while v >= 128 {
567 out.push(((v & 0x7F) as u8) | 0x80);
568 v >>= 7;
569 }
570 out.push(v as u8);
571 }
572 out.extend_from_slice(&payload);
573 out
574 }
575
576 pub fn encode_literal_string(out: &mut Vec<u8>, s: &str) {
579 let len = s.len() as u64;
580 if len < 0x7F {
581 out.push(len as u8);
582 } else {
583 out.push(0x7F);
584 let mut v = len - 0x7F;
585 while v >= 128 {
586 out.push(((v & 0x7F) as u8) | 0x80);
587 v >>= 7;
588 }
589 out.push(v as u8);
590 }
591 out.extend_from_slice(s.as_bytes());
592 }
593
594 pub fn hpack_literal_huffman_indexed_name_with_raw(
598 name_index: u8,
599 raw_value: &[u8],
600 ) -> Vec<u8> {
601 let mut out = Vec::new();
602 out.push(0x40 | (name_index & 0x3F));
603 out.push(0x80 | (raw_value.len() as u8));
604 out.extend_from_slice(raw_value);
605 out
606 }
607
608 pub fn settings_then_headers(header_block: &[u8]) -> Vec<u8> {
612 let mut out = empty_settings_frame();
613 out.extend_from_slice(&headers_frame(header_block));
614 out
615 }
616
617 pub fn headers_frame_on_stream(header_block: &[u8], stream_id: u32) -> Vec<u8> {
621 let length = header_block.len() as u32;
622 let flags = super::frame::FLAG_END_HEADERS | 0x1; let mut out = frame_header(length, FRAME_TYPE_HEADERS, flags, stream_id);
624 out.extend_from_slice(header_block);
625 out
626 }
627
628 pub fn headers_frame_on_stream_no_end(header_block: &[u8], stream_id: u32) -> Vec<u8> {
631 let length = header_block.len() as u32;
632 let mut out = frame_header(length, FRAME_TYPE_HEADERS, 0x0, stream_id);
633 out.extend_from_slice(header_block);
634 out
635 }
636
637 pub fn continuation_frame_on_stream(
639 header_block: &[u8],
640 stream_id: u32,
641 end_headers: bool,
642 ) -> Vec<u8> {
643 let length = header_block.len() as u32;
644 let flags = if end_headers {
645 super::frame::FLAG_END_HEADERS
646 } else {
647 0x0
648 };
649 let mut out = frame_header(length, FRAME_TYPE_CONTINUATION, flags, stream_id);
650 out.extend_from_slice(header_block);
651 out
652 }
653
654 pub fn data_frame_on_stream(payload: &[u8], stream_id: u32) -> Vec<u8> {
656 let length = payload.len() as u32;
657 let mut out = frame_header(length, super::frame::FRAME_TYPE_DATA, 0x0, stream_id);
658 out.extend_from_slice(payload);
659 out
660 }
661}
662
663#[cfg(test)]
664mod tests {
665 use super::test_helpers::*;
666 use super::*;
667
668 #[test]
670 fn extracts_authority_from_static_table_index_1() {
671 let block = hpack_indexed(1);
672 let bytes = settings_then_headers(&block);
673 let result = extract_h2_authority(&bytes).unwrap();
674 assert_eq!(
675 result, None,
676 "indexed-only :authority has no value; parser returns None"
677 );
678 }
679
680 #[test]
682 fn extracts_authority_from_literal_indexed() {
683 let block = hpack_literal_indexed_name(1, "api.example.com");
684 let bytes = settings_then_headers(&block);
685 let result = extract_h2_authority(&bytes).unwrap();
686 assert_eq!(result.as_deref(), Some("api.example.com"));
687 }
688
689 #[test]
691 fn extracts_authority_from_literal_with_incremental_indexing() {
692 let block = hpack_literal_indexed_with_name(":authority", "api.example.com");
693 let bytes = settings_then_headers(&block);
694 let result = extract_h2_authority(&bytes).unwrap();
695 assert_eq!(result.as_deref(), Some("api.example.com"));
696 }
697
698 #[test]
700 fn rejects_oversized_frame() {
701 let mut bytes = empty_settings_frame();
702 let bogus_header = frame_header(
703 16_385,
704 frame::FRAME_TYPE_HEADERS,
705 frame::FLAG_END_HEADERS,
706 1,
707 );
708 bytes.extend_from_slice(&bogus_header);
709 let err = extract_h2_authority(&bytes).unwrap_err();
710 assert!(matches!(
711 err,
712 H2ParseError::OversizedFrame {
713 declared: 16_385,
714 max: 16_384
715 }
716 ));
717 }
718
719 #[test]
724 fn reassembles_headers_with_one_continuation() {
725 let block_full = hpack_literal_indexed_name(1, "api.example.com");
726 let mid = block_full.len() / 2;
729 let (first_half, second_half) = block_full.split_at(mid);
730 let mut bytes = empty_settings_frame();
731 bytes.extend_from_slice(&continuation_fragmented_headers(first_half));
732 bytes.extend_from_slice(&continuation_frame(second_half, true));
733 let result = extract_h2_authority(&bytes).unwrap();
734 assert_eq!(result.as_deref(), Some("api.example.com"));
735 }
736
737 #[test]
739 fn rejects_non_headers_first_frame() {
740 let mut bytes = empty_settings_frame();
741 bytes.extend_from_slice(&frame_header(0, frame::FRAME_TYPE_DATA, 0, 1));
742 let err = extract_h2_authority(&bytes).unwrap_err();
743 assert!(matches!(
744 err,
745 H2ParseError::UnexpectedFirstFrame { frame_type: 0 }
746 ));
747 }
748
749 #[test]
751 fn extracts_lowercased_authority() {
752 let block = hpack_literal_indexed_name(1, "API.Example.COM");
753 let bytes = settings_then_headers(&block);
754 let result = extract_h2_authority(&bytes).unwrap();
755 assert_eq!(result.as_deref(), Some("api.example.com"));
756 }
757
758 #[test]
760 fn strips_port_from_authority() {
761 let block = hpack_literal_indexed_name(1, "api.example.com:8443");
762 let bytes = settings_then_headers(&block);
763 let result = extract_h2_authority(&bytes).unwrap();
764 assert_eq!(result.as_deref(), Some("api.example.com"));
765 }
766
767 #[test]
769 fn returns_none_when_buffer_short() {
770 let bytes = empty_settings_frame();
771 let result = extract_h2_authority(&bytes).unwrap();
772 assert_eq!(result, None);
773
774 let truncated = &bytes[..5];
775 let result = extract_h2_authority(truncated).unwrap();
776 assert_eq!(result, None);
777 }
778
779 #[test]
781 fn is_h2c_preface_recognises_canonical_preface() {
782 let mut bytes = HTTP2_PREFACE.to_vec();
783 bytes.extend_from_slice(&[0x00, 0x00, 0x00]);
784 assert!(is_h2c_preface(&bytes));
785 assert!(is_h2c_preface(HTTP2_PREFACE));
786 }
787
788 #[test]
789 fn is_h2c_preface_rejects_malformed() {
790 assert!(!is_h2c_preface(&HTTP2_PREFACE[..23]));
791 let mut bad = HTTP2_PREFACE.to_vec();
792 bad[5] = b'x';
793 assert!(!is_h2c_preface(&bad));
794 assert!(!is_h2c_preface(b"GET / HTTP/1.1\r\nHost: x\r\n\r\n"));
795 assert!(!is_h2c_preface(&[0x16, 0x03, 0x01]));
796 }
797
798 #[test]
800 fn extracts_authority_from_padded_headers_frame() {
801 let block = hpack_literal_indexed_name(1, "api.example.com");
802 let pad_len: u8 = 4;
803 let mut padded_payload = Vec::new();
804 padded_payload.push(pad_len);
805 padded_payload.extend_from_slice(&block);
806 padded_payload.extend(std::iter::repeat_n(0u8, pad_len as usize));
807 let length = padded_payload.len() as u32;
808 let flags = frame::FLAG_END_HEADERS | frame::FLAG_PADDED;
809 let mut bytes = empty_settings_frame();
810 bytes.extend_from_slice(&frame_header(length, frame::FRAME_TYPE_HEADERS, flags, 1));
811 bytes.extend_from_slice(&padded_payload);
812 let result = extract_h2_authority(&bytes).unwrap();
813 assert_eq!(result.as_deref(), Some("api.example.com"));
814 }
815
816 #[test]
818 fn rejects_padding_overflow() {
819 let block = hpack_literal_indexed_name(1, "api.example.com");
820 let mut padded_payload = Vec::new();
821 padded_payload.push(255);
822 padded_payload.extend_from_slice(&block);
823 let length = padded_payload.len() as u32;
824 let flags = frame::FLAG_END_HEADERS | frame::FLAG_PADDED;
825 let mut bytes = empty_settings_frame();
826 bytes.extend_from_slice(&frame_header(length, frame::FRAME_TYPE_HEADERS, flags, 1));
827 bytes.extend_from_slice(&padded_payload);
828 let err = extract_h2_authority(&bytes).unwrap_err();
829 assert_eq!(err, H2ParseError::PaddingOverflow);
830 }
831
832 #[test]
835 fn decodes_huffman_coded_literal() {
836 let block = hpack_literal_indexed_name_huffman(1, "api.example.com");
837 let bytes = settings_then_headers(&block);
838 let result = extract_h2_authority(&bytes).unwrap();
839 assert_eq!(result.as_deref(), Some("api.example.com"));
840 }
841
842 #[test]
846 fn decodes_dynamic_table_reference_with_stateful_decoder() {
847 let mut decoder = HpackDecoder::new();
848 let block1 = hpack_literal_indexed_name(1, "api.example.com");
850 let bytes1 = settings_then_headers(&block1);
851 let r1 = extract_h2_authority_with(&bytes1, &mut decoder)
852 .unwrap()
853 .unwrap();
854 assert_eq!(r1.value, "api.example.com");
855 let block2 = vec![0x80 | 62];
858 let bytes2 = settings_then_headers(&block2);
859 let r2 = extract_h2_authority_with(&bytes2, &mut decoder)
860 .unwrap()
861 .unwrap();
862 assert_eq!(r2.value, "api.example.com");
863 assert_eq!(r2.provenance, AuthorityProvenance::DynamicIndexed);
864 }
865
866 #[test]
868 fn extracts_authority_with_priority_flag() {
869 let block = hpack_literal_indexed_name(1, "api.example.com");
870 let priority_section = [0u8; 5];
871 let mut payload = Vec::new();
872 payload.extend_from_slice(&priority_section);
873 payload.extend_from_slice(&block);
874 let length = payload.len() as u32;
875 let flags = frame::FLAG_END_HEADERS | frame::FLAG_PRIORITY;
876 let mut bytes = empty_settings_frame();
877 bytes.extend_from_slice(&frame_header(length, frame::FRAME_TYPE_HEADERS, flags, 1));
878 bytes.extend_from_slice(&payload);
879 let result = extract_h2_authority(&bytes).unwrap();
880 assert_eq!(result.as_deref(), Some("api.example.com"));
881 }
882
883 #[test]
885 fn strips_trailing_dot_from_authority() {
886 let block = hpack_literal_indexed_name(1, "api.example.com.");
887 let bytes = settings_then_headers(&block);
888 let result = extract_h2_authority(&bytes).unwrap();
889 assert_eq!(result.as_deref(), Some("api.example.com"));
890 }
891
892 #[test]
894 fn ipv6_literal_authority_keeps_brackets() {
895 let block = hpack_literal_indexed_name(1, "[::1]:443");
896 let bytes = settings_then_headers(&block);
897 let result = extract_h2_authority(&bytes).unwrap();
898 assert_eq!(result.as_deref(), Some("[::1]"));
899 }
900
901 #[test]
903 fn extracts_authority_from_literal_without_indexing() {
904 let block = hpack_literal_no_indexing(1, "api.example.com");
905 let bytes = settings_then_headers(&block);
906 let result = extract_h2_authority(&bytes).unwrap();
907 assert_eq!(result.as_deref(), Some("api.example.com"));
908 }
909
910 #[test]
912 fn rejects_broken_huffman_literal() {
913 let block = hpack_literal_huffman_indexed_name_with_raw(1, &[0x00, 0x00, 0x00]);
917 let bytes = settings_then_headers(&block);
918 let err = extract_h2_authority(&bytes).unwrap_err();
919 assert!(matches!(
920 err,
921 H2ParseError::HuffmanInvalid | H2ParseError::HuffmanOversized { .. }
922 ));
923 }
924
925 #[test]
927 fn reassembly_rejects_interleaved_data_frame() {
928 let block_full = hpack_literal_indexed_name(1, "api.example.com");
929 let mid = block_full.len() / 2;
930 let (first_half, second_half) = block_full.split_at(mid);
931 let mut bytes = empty_settings_frame();
932 bytes.extend_from_slice(&continuation_fragmented_headers(first_half));
933 bytes.extend_from_slice(&frame_header(0, frame::FRAME_TYPE_DATA, 0, 1));
935 bytes.extend_from_slice(&continuation_frame(second_half, true));
936 let err = extract_h2_authority(&bytes).unwrap_err();
937 assert!(matches!(
938 err,
939 H2ParseError::InterleavedFrame { frame_type: 0 }
940 ));
941 }
942
943 #[test]
945 fn reassembles_three_fragments_with_priority_and_padding() {
946 let block_full = hpack_literal_indexed_name(1, "api.example.com");
947 let third = block_full.len() / 3;
948 let (a, rest) = block_full.split_at(third);
949 let (b, c) = rest.split_at(third);
950
951 let pad_len: u8 = 2;
953 let priority = [0u8; 5];
954 let mut headers_payload = Vec::new();
955 headers_payload.push(pad_len);
956 headers_payload.extend_from_slice(&priority);
957 headers_payload.extend_from_slice(a);
958 headers_payload.extend(std::iter::repeat_n(0u8, pad_len as usize));
959 let headers_frame_bytes = {
960 let length = headers_payload.len() as u32;
961 let flags = frame::FLAG_PADDED | frame::FLAG_PRIORITY;
962 let mut out = frame_header(length, frame::FRAME_TYPE_HEADERS, flags, 1);
963 out.extend_from_slice(&headers_payload);
964 out
965 };
966
967 let mut bytes = empty_settings_frame();
968 bytes.extend_from_slice(&headers_frame_bytes);
969 bytes.extend_from_slice(&continuation_frame(b, false));
970 bytes.extend_from_slice(&continuation_frame(c, true));
971
972 let result = extract_h2_authority(&bytes).unwrap();
973 assert_eq!(result.as_deref(), Some("api.example.com"));
974 }
975
976 #[test]
978 fn reassembly_rejects_oversized_aggregate_block() {
979 let mut bytes = empty_settings_frame();
986 let chunk = vec![0xAAu8; 16_383];
991 bytes.extend_from_slice(&continuation_fragmented_headers(&chunk));
992 bytes.extend_from_slice(&continuation_frame(&chunk, false));
993 bytes.extend_from_slice(&continuation_frame(&chunk, false));
994 bytes.extend_from_slice(&continuation_frame(&chunk, false));
995 bytes.extend_from_slice(&continuation_frame(&chunk, true));
996 let err = extract_h2_authority(&bytes).unwrap_err();
997 assert!(matches!(
998 err,
999 H2ParseError::HpackOversizedHeaderBlock { .. }
1000 ));
1001 }
1002
1003 #[test]
1005 fn single_headers_with_end_headers_works_unchanged_via_reassembly_path() {
1006 let block = hpack_literal_indexed_name(1, "api.example.com");
1008 let bytes = settings_then_headers(&block);
1009 let result = extract_h2_authority(&bytes).unwrap();
1010 assert_eq!(result.as_deref(), Some("api.example.com"));
1011 }
1012
1013 fn parse_first_frame(bytes: &[u8]) -> (frame::FrameHeader, Vec<u8>) {
1018 let (header, payload, _rest) = parse_one_frame(bytes).unwrap().unwrap();
1019 (header, payload.to_vec())
1020 }
1021
1022 #[test]
1023 fn ingest_single_headers_with_end_headers_returns_block() {
1024 let mut r = H2StreamReassembler::new();
1025 let block = hpack_literal_indexed_name(1, "api.example.com");
1026 let frame_bytes = headers_frame(&block);
1027 let (h, p) = parse_first_frame(&frame_bytes);
1028 let out = r.ingest(&h, &p).unwrap();
1029 let (sid, returned) = out.expect("expected immediate completion");
1030 assert_eq!(sid, 1);
1031 assert_eq!(returned, block);
1032 assert_eq!(r.pending_count(), 0);
1033 }
1034
1035 #[test]
1036 fn ingest_two_headers_on_different_streams_returns_two_blocks() {
1037 let mut r = H2StreamReassembler::new();
1038 let block_a = hpack_literal_indexed_name(1, "api.example.com");
1039 let block_b = hpack_literal_indexed_name(1, "api.other.com");
1040 let f_a = headers_frame_on_stream(&block_a, 1);
1041 let f_b = headers_frame_on_stream(&block_b, 3);
1042 let (ha, pa) = parse_first_frame(&f_a);
1043 let (hb, pb) = parse_first_frame(&f_b);
1044 let out_a = r.ingest(&ha, &pa).unwrap().unwrap();
1045 let out_b = r.ingest(&hb, &pb).unwrap().unwrap();
1046 assert_eq!(out_a.0, 1);
1047 assert_eq!(out_b.0, 3);
1048 assert_eq!(out_a.1, block_a);
1049 assert_eq!(out_b.1, block_b);
1050 }
1051
1052 #[test]
1053 fn ingest_continuation_on_correct_active_stream_reassembles() {
1054 let mut r = H2StreamReassembler::new();
1055 let block_full = hpack_literal_indexed_name(1, "api.example.com");
1056 let mid = block_full.len() / 2;
1057 let (a, b) = block_full.split_at(mid);
1058 let f1 = headers_frame_on_stream_no_end(a, 1);
1059 let f2 = continuation_frame_on_stream(b, 1, true);
1060 let (h1, p1) = parse_first_frame(&f1);
1061 let (h2, p2) = parse_first_frame(&f2);
1062 assert!(r.ingest(&h1, &p1).unwrap().is_none());
1063 assert_eq!(r.pending_count(), 1);
1064 let (sid, block) = r.ingest(&h2, &p2).unwrap().unwrap();
1065 assert_eq!(sid, 1);
1066 assert_eq!(block, block_full);
1067 assert_eq!(r.pending_count(), 0);
1068 }
1069
1070 #[test]
1071 fn ingest_continuation_on_wrong_stream_returns_interleaved() {
1072 let mut r = H2StreamReassembler::new();
1073 let block_full = hpack_literal_indexed_name(1, "api.example.com");
1074 let mid = block_full.len() / 2;
1075 let (a, b) = block_full.split_at(mid);
1076 let f1 = headers_frame_on_stream_no_end(a, 1);
1077 let f2 = continuation_frame_on_stream(b, 3, true);
1078 let (h1, p1) = parse_first_frame(&f1);
1079 let (h2, p2) = parse_first_frame(&f2);
1080 r.ingest(&h1, &p1).unwrap();
1081 let err = r.ingest(&h2, &p2).unwrap_err();
1082 assert!(matches!(err, H2ParseError::InterleavedFrame { .. }));
1083 }
1084
1085 #[test]
1086 fn ingest_data_frame_passes_through_with_no_block_returned() {
1087 let mut r = H2StreamReassembler::new();
1088 let f = data_frame_on_stream(b"hello", 1);
1089 let (h, p) = parse_first_frame(&f);
1090 assert!(r.ingest(&h, &p).unwrap().is_none());
1091 assert_eq!(r.pending_count(), 0);
1092 }
1093
1094 #[test]
1095 fn ingest_oversized_per_stream_block_returns_overflow() {
1096 let mut r = H2StreamReassembler::new();
1097 let chunk = vec![0u8; 16_383];
1102 let f1 = headers_frame_on_stream_no_end(&chunk, 1);
1103 let (h1, p1) = parse_first_frame(&f1);
1104 r.ingest(&h1, &p1).unwrap();
1105 for _ in 0..3 {
1109 let f = continuation_frame_on_stream(&chunk, 1, false);
1110 let (h, p) = parse_first_frame(&f);
1111 r.ingest(&h, &p).unwrap();
1112 }
1113 let f = continuation_frame_on_stream(&chunk, 1, false);
1114 let (h, p) = parse_first_frame(&f);
1115 let err = r.ingest(&h, &p).unwrap_err();
1116 assert!(matches!(
1117 err,
1118 H2ParseError::ReassemblerOverflow {
1119 kind: ReassemblerOverflowKind::PerStreamBlock
1120 }
1121 ));
1122 }
1123
1124 #[test]
1125 fn ingest_oversized_aggregate_returns_overflow() {
1126 let mut r = H2StreamReassembler::new();
1178 r.blocks.insert(1, vec![0u8; 60 * 1024]);
1182 r.total_in_flight = 60 * 1024;
1183 r.active_stream = Some(1);
1184 let chunk = vec![0u8; 16_383];
1189 let f = continuation_frame_on_stream(&chunk, 1, false);
1190 let (h, p) = parse_first_frame(&f);
1191 let err = r.ingest(&h, &p).unwrap_err();
1192 assert!(matches!(
1196 err,
1197 H2ParseError::ReassemblerOverflow {
1198 kind: ReassemblerOverflowKind::PerStreamBlock
1199 }
1200 ));
1201
1202 let mut r2 = H2StreamReassembler::new();
1207 r2.total_in_flight = REASSEMBLER_TOTAL_IN_FLIGHT_MAX - 100;
1208 let block = vec![0u8; 200];
1209 let f = headers_frame_on_stream_no_end(&block, 5);
1210 let (h, p) = parse_first_frame(&f);
1211 let err = r2.ingest(&h, &p).unwrap_err();
1212 assert!(matches!(
1213 err,
1214 H2ParseError::ReassemblerOverflow {
1215 kind: ReassemblerOverflowKind::TotalInFlight
1216 }
1217 ));
1218 }
1219
1220 #[test]
1221 fn ingest_too_many_concurrent_streams_returns_overflow() {
1222 let mut r = H2StreamReassembler::new();
1223 for i in 1..=REASSEMBLER_MAX_CONCURRENT_STREAMS as u32 {
1228 r.blocks.insert(i * 2 + 1, vec![]);
1229 }
1230 let block = hpack_literal_indexed_name(1, "api.example.com");
1232 let f = headers_frame_on_stream(&block, 999);
1233 let (h, p) = parse_first_frame(&f);
1234 let err = r.ingest(&h, &p).unwrap_err();
1235 assert!(matches!(
1236 err,
1237 H2ParseError::ReassemblerOverflow {
1238 kind: ReassemblerOverflowKind::ConcurrentStreams
1239 }
1240 ));
1241 }
1242
1243 #[test]
1248 fn feed_returns_decoded_authority_for_complete_headers_on_stream_1() {
1249 let mut d = H2ConnectionDecoder::new();
1250 let block = hpack_literal_indexed_name(1, "api.example.com");
1251 let f = headers_frame(&block);
1252 let (h, p) = parse_first_frame(&f);
1253 let out = d.feed_frame(&h, &p).unwrap().unwrap();
1254 assert_eq!(out.stream_id, 1);
1255 assert_eq!(out.authority.as_deref(), Some("api.example.com"));
1256 assert!(!out.via_dynamic_table);
1257 assert!(!out.via_huffman);
1258 }
1259
1260 #[test]
1261 fn feed_returns_two_decoded_blocks_for_two_streams_interleaved_via_continuation() {
1262 let mut d = H2ConnectionDecoder::new();
1263 let block_a = hpack_literal_indexed_name(1, "api.example.com");
1264 let block_b = hpack_literal_indexed_name(1, "api.other.com");
1265 let fa = headers_frame_on_stream(&block_a, 1);
1267 let (ha, pa) = parse_first_frame(&fa);
1268 let out_a = d.feed_frame(&ha, &pa).unwrap().unwrap();
1269 assert_eq!(out_a.stream_id, 1);
1270 assert_eq!(out_a.authority.as_deref(), Some("api.example.com"));
1271 let mid = block_b.len() / 2;
1273 let (b1, b2) = block_b.split_at(mid);
1274 let f1 = headers_frame_on_stream_no_end(b1, 3);
1275 let f2 = continuation_frame_on_stream(b2, 3, true);
1276 let (h1, p1) = parse_first_frame(&f1);
1277 let (h2, p2) = parse_first_frame(&f2);
1278 assert!(d.feed_frame(&h1, &p1).unwrap().is_none());
1279 let out_b = d.feed_frame(&h2, &p2).unwrap().unwrap();
1280 assert_eq!(out_b.stream_id, 3);
1281 assert_eq!(out_b.authority.as_deref(), Some("api.other.com"));
1282 }
1283
1284 #[test]
1285 fn feed_returns_provenance_when_huffman_used() {
1286 let mut d = H2ConnectionDecoder::new();
1287 let block = hpack_literal_indexed_name_huffman(1, "api.example.com");
1288 let f = headers_frame(&block);
1289 let (h, p) = parse_first_frame(&f);
1290 let out = d.feed_frame(&h, &p).unwrap().unwrap();
1291 assert!(out.via_huffman);
1292 }
1293
1294 #[test]
1295 fn feed_returns_provenance_when_dynamic_table_used() {
1296 let mut d = H2ConnectionDecoder::new();
1297 let block1 = hpack_literal_indexed_name(1, "api.example.com");
1300 let f1 = headers_frame_on_stream(&block1, 1);
1301 let (h1, p1) = parse_first_frame(&f1);
1302 let _ = d.feed_frame(&h1, &p1).unwrap();
1303 let block2 = vec![0x80 | 62];
1306 let f2 = headers_frame_on_stream(&block2, 3);
1307 let (h2, p2) = parse_first_frame(&f2);
1308 let out = d.feed_frame(&h2, &p2).unwrap().unwrap();
1309 assert!(out.via_dynamic_table);
1310 assert_eq!(out.authority.as_deref(), Some("api.example.com"));
1311 }
1312
1313 #[test]
1314 fn feed_returns_none_for_data_frame() {
1315 let mut d = H2ConnectionDecoder::new();
1316 let f = data_frame_on_stream(b"payload", 1);
1317 let (h, p) = parse_first_frame(&f);
1318 assert!(d.feed_frame(&h, &p).unwrap().is_none());
1319 }
1320
1321 #[test]
1322 fn feed_propagates_hpack_decoder_state_across_streams() {
1323 let mut d = H2ConnectionDecoder::new();
1327 let block1 = hpack_literal_indexed_name(1, "shared.example.com");
1328 let f1 = headers_frame_on_stream(&block1, 1);
1329 let (h1, p1) = parse_first_frame(&f1);
1330 let r1 = d.feed_frame(&h1, &p1).unwrap().unwrap();
1331 assert_eq!(r1.authority.as_deref(), Some("shared.example.com"));
1332 let block2 = vec![0x80 | 62];
1333 let f2 = headers_frame_on_stream(&block2, 5);
1334 let (h2, p2) = parse_first_frame(&f2);
1335 let r2 = d.feed_frame(&h2, &p2).unwrap().unwrap();
1336 assert_eq!(r2.authority.as_deref(), Some("shared.example.com"));
1337 assert_eq!(r2.stream_id, 5);
1338 }
1339
1340 #[test]
1341 fn feed_propagates_reassembler_overflow_per_stream() {
1342 let mut d = H2ConnectionDecoder::new();
1343 let chunk = vec![0u8; 16_383];
1344 let f1 = headers_frame_on_stream_no_end(&chunk, 1);
1345 let (h1, p1) = parse_first_frame(&f1);
1346 d.feed_frame(&h1, &p1).unwrap();
1347 for _ in 0..3 {
1348 let f = continuation_frame_on_stream(&chunk, 1, false);
1349 let (h, p) = parse_first_frame(&f);
1350 d.feed_frame(&h, &p).unwrap();
1351 }
1352 let f = continuation_frame_on_stream(&chunk, 1, false);
1353 let (h, p) = parse_first_frame(&f);
1354 let err = d.feed_frame(&h, &p).unwrap_err();
1355 assert!(matches!(
1356 err,
1357 H2ParseError::ReassemblerOverflow {
1358 kind: ReassemblerOverflowKind::PerStreamBlock
1359 }
1360 ));
1361 }
1362
1363 #[test]
1364 fn feed_handles_settings_frame_size_update_to_dynamic_table() {
1365 let mut d = H2ConnectionDecoder::new();
1372 let settings = empty_settings_frame();
1374 let (sh, sp) = parse_first_frame(&settings);
1375 assert!(d.feed_frame(&sh, &sp).unwrap().is_none());
1376 let block: Vec<u8> = vec![
1380 0x20, 0x80 | 2, ];
1383 let f = headers_frame_on_stream(&block, 1);
1384 let (h, p) = parse_first_frame(&f);
1385 let out = d.feed_frame(&h, &p).unwrap().unwrap();
1386 assert_eq!(out.stream_id, 1);
1387 assert!(out.authority.is_none());
1388 }
1389
1390 #[test]
1391 fn feed_returns_none_authority_when_headers_block_lacks_pseudo_header() {
1392 let mut d = H2ConnectionDecoder::new();
1393 let block = vec![0x80 | 2];
1395 let f = headers_frame_on_stream(&block, 1);
1396 let (h, p) = parse_first_frame(&f);
1397 let out = d.feed_frame(&h, &p).unwrap().unwrap();
1398 assert_eq!(out.stream_id, 1);
1399 assert!(out.authority.is_none());
1400 }
1401
1402 #[test]
1403 fn feed_returns_block_for_continuation_after_headers_without_end_headers() {
1404 let mut d = H2ConnectionDecoder::new();
1408 let block_full = hpack_literal_indexed_name(1, "api.example.com");
1409 let mid = block_full.len() / 2;
1410 let (a, b) = block_full.split_at(mid);
1411 let f1 = headers_frame_on_stream_no_end(a, 1);
1412 let f2 = continuation_frame_on_stream(b, 1, true);
1413 let (h1, p1) = parse_first_frame(&f1);
1414 let (h2, p2) = parse_first_frame(&f2);
1415 assert!(d.feed_frame(&h1, &p1).unwrap().is_none());
1416 let out = d.feed_frame(&h2, &p2).unwrap().unwrap();
1417 assert_eq!(out.stream_id, 1);
1418 assert_eq!(out.authority.as_deref(), Some("api.example.com"));
1419 }
1420}