use super::*;
#[inline]
fn hdr(name: &str, value: &str) -> (Bytes, Bytes) {
(
Bytes::copy_from_slice(name.as_bytes()),
Bytes::copy_from_slice(value.as_bytes()),
)
}
#[inline]
fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{b:02x}")).collect()
}
#[test]
fn decoder_stream_section_ack_classifies_high_stream_id() {
let mut enc = Encoder::new(256, false);
enc.encode_section(64, &[hdr("a", "b")]);
assert!(enc.pending_refs.iter().any(|(id, _)| *id == 64));
assert!(enc.feed_decoder_stream(&[0xC0]).is_ok());
assert!(!enc.pending_refs.iter().any(|(id, _)| *id == 64));
enc.encode_section(4, &[hdr("a", "b")]);
assert!(enc.pending_refs.iter().any(|(id, _)| *id == 4));
assert!(enc.feed_decoder_stream(&[0x44]).is_ok());
assert!(!enc.pending_refs.iter().any(|(id, _)| *id == 4));
let inserted = enc.dynamic.inserted();
assert!(inserted >= 1);
assert!(enc.feed_decoder_stream(&[0x01]).is_ok());
assert_eq!(enc.known_received, 1);
}
#[test]
fn stray_decoder_stream_instructions_are_benign() {
let mut enc = Encoder::new(256, false);
assert!(enc.feed_decoder_stream(&[0xC0]).is_ok());
assert!(enc.feed_decoder_stream(&[0x44]).is_ok());
enc.encode_section(4, &[hdr("a", "b")]);
assert!(enc.feed_decoder_stream(&[0x44]).is_ok());
assert!(enc.feed_decoder_stream(&[0x44]).is_ok());
}
#[test]
fn vector_b1_static_name_ref() {
let mut enc = Encoder::new(0, false);
let out = enc.encode_section(0, &[hdr(":path", "/index.html")]);
assert_eq!(hex(&out.block), "0000510b2f696e6465782e68746d6c");
assert!(out.encoder_stream.is_empty());
}
#[test]
fn vector_b2_dynamic_post_base() {
let mut enc = Encoder::new(220, false);
assert_eq!(hex(&enc.set_capacity(220).unwrap()), "3fbd01");
let authority = enc
.insert_with_name_ref(b":authority", b"www.example.com")
.unwrap();
assert_eq!(hex(&authority), "c00f7777772e6578616d706c652e636f6d");
let path = enc.insert_with_name_ref(b":path", b"/sample/path").unwrap();
assert_eq!(hex(&path), "c10c2f73616d706c652f70617468");
let block = enc.encode_section_with_base(
0,
&[
hdr(":authority", "www.example.com"),
hdr(":path", "/sample/path"),
],
0,
);
assert_eq!(hex(&block.block), "03811011");
}
#[test]
fn vector_b3_insert_literal_name() {
let mut enc = Encoder::new(220, false);
enc.set_capacity(220);
let ins = enc.insert_literal(b"custom-key", b"custom-value").unwrap();
assert_eq!(
hex(&ins),
"4a637573746f6d2d6b65790c637573746f6d2d76616c7565"
);
assert_eq!(enc.dynamic.len(), 1);
assert_eq!(
enc.dynamic.get_absolute(0),
Some((b"custom-key".as_slice(), b"custom-value".as_slice()))
);
}
#[test]
fn vector_b4_duplicate_and_relative() {
let mut enc = Encoder::new(220, false);
enc.set_capacity(220);
enc.insert_with_name_ref(b":authority", b"www.example.com");
enc.insert_with_name_ref(b":path", b"/sample/path");
enc.insert_literal(b"custom-key", b"custom-value");
assert_eq!(hex(&enc.duplicate(2).unwrap()), "02");
assert_eq!(enc.dynamic.len(), 4);
let block = enc.encode_section_with_base(0, &[hdr(":authority", "www.example.com")], 4);
assert_eq!(hex(&block.block), "050080");
}
#[test]
fn fresh_name_inserted_then_referenced() {
let mut enc = Encoder::new(220, false);
let out = enc.encode_section(0, &[hdr("custom-key", "custom-value")]);
assert_eq!(
hex(&out.encoder_stream),
"3fbd014a637573746f6d2d6b65790c637573746f6d2d76616c7565"
);
assert_eq!(hex(&out.block), "028010");
assert_eq!(enc.dynamic.len(), 1);
}
#[test]
fn indexed_static_full_match() {
let mut enc = Encoder::new(0, false);
let out = enc.encode_section(0, &[hdr(":method", "GET")]);
let idx = static_table::find(b":method", b"GET").unwrap() as u8;
assert_eq!(hex(&out.block), format!("0000{:02x}", 0xc0 | idx));
}
#[test]
fn relative_ref_from_fixed_base() {
let mut enc = Encoder::new(220, false);
enc.set_capacity(220);
enc.insert_with_name_ref(b":authority", b"www.example.com");
enc.insert_with_name_ref(b":path", b"/sample/path");
let out = enc.encode_section(0, &[hdr(":path", "/sample/path")]);
assert_eq!(hex(&out.block), "030080");
assert!(out.encoder_stream.is_empty());
}
#[test]
fn ack_base_uses_post_base_for_unacknowledged_entry() {
let mut enc = Encoder::new(220, false);
enc.set_capacity(220);
enc.insert_with_name_ref(b":authority", b"www.example.com");
enc.insert_with_name_ref(b":path", b"/sample/path");
let section = enc.encode_section_with_ack_base(0, &[hdr("x-novel", "value")]);
let body = §ion.block[2..];
assert_eq!(body.len(), 1, "exactly one field line");
assert_eq!(
body[0] & 0xF0,
0x10,
"unacknowledged entry must be referenced post-Base, not with a relative index"
);
assert!(
!section.encoder_stream.is_empty(),
"the newly inserted entry must travel on the encoder stream"
);
}
#[test]
fn ack_base_uses_relative_for_acknowledged_entry() {
let mut enc = Encoder::new(220, false);
enc.set_capacity(220);
enc.insert_with_name_ref(b":authority", b"www.example.com");
enc.insert_with_name_ref(b":path", b"/sample/path");
enc.insert_with_name_ref(b"x-foo", b"bar");
enc.feed_decoder_stream(&[0x02]).unwrap();
let section = enc.encode_section_with_ack_base(0, &[hdr(":authority", "www.example.com")]);
let body = §ion.block[2..];
assert_eq!(body.len(), 1, "exactly one field line");
assert_eq!(
body[0] & 0xC0,
0x80,
"acknowledged entry must be referenced with a relative index"
);
assert!(
section.encoder_stream.is_empty(),
"no new insert needed for an already-present entry"
);
}
#[test]
fn huffman_used_when_shorter() {
let mut enc = Encoder::new(0, true);
let out = enc.encode_section(
0,
&[hdr(":path", "www.example.com/aaaaaaaaaaaaaaaaaaaaaaaaa")],
);
let block = out.block.as_ref();
assert_eq!(&block[..2], &[0x00, 0x00]);
assert_eq!(block[2], 0x51);
assert_ne!(block[3] & 0x80, 0);
assert!(block[3] & 0x7f < 42);
let mut dec = Vec::new();
huffman::decode(&block[4..], &mut dec).unwrap();
assert_eq!(dec, b"www.example.com/aaaaaaaaaaaaaaaaaaaaaaaaa");
}
#[test]
fn sensitive_never_indexed() {
let mut enc = Encoder::new(220, false);
let out = enc.encode_section(0, &[hdr("authorization", "Bearer xyz")]);
assert!(out.encoder_stream.is_empty());
assert_ne!(out.block.as_ref()[2] & 0x10, 0);
assert_eq!(enc.dynamic.len(), 0);
}
#[test]
fn insert_skipped_when_it_would_evict_referenced_entry() {
let mut enc = Encoder::new(100, false);
enc.set_capacity(100);
enc.insert_with_name_ref(b":authority", b"www.example.com");
let out = enc.encode_section(
0,
&[
hdr(":authority", "www.example.com"),
hdr("x-custom", "0123456789012345678901234567890123456789"),
],
);
let block = out.block.as_ref();
assert_eq!(block[2], 0x80);
assert_eq!(block[3] & 0xe0, 0x20);
assert!(out.encoder_stream.is_empty());
assert_eq!(enc.dynamic.len(), 1);
}
#[test]
fn no_duplicate_set_capacity_instruction() {
let mut enc = Encoder::new(220, false);
enc.set_capacity(220);
let out = enc.encode_section(0, &[hdr(":path", "/sample/path")]);
assert!(out.encoder_stream.is_empty());
}
#[test]
fn empty_section() {
let mut enc = Encoder::new(220, false);
let out = enc.encode_section(0, &[]);
assert_eq!(hex(&out.block), "0000");
assert!(out.encoder_stream.is_empty());
}
#[test]
fn static_only_section_announces_zero_ric() {
let mut enc = Encoder::new(220, false);
enc.set_capacity(220);
enc.insert_with_name_ref(b":authority", b"www.example.com");
let out = enc.encode_section(0, &[hdr(":method", "GET")]);
assert_eq!(hex(&out.block), "0001d1");
assert!(out.encoder_stream.is_empty());
}
fn strict_decode_insert_literal_name(buf: &[u8]) -> Option<(Vec<u8>, Vec<u8>)> {
let mut off = 0usize;
let first = *buf.get(off)?;
if first & 0xC0 != 0x40 {
return None;
}
let name_huffman = first & 0x20 != 0;
let mut name_len = (first & 0x1F) as usize;
off += 1;
if name_len == 0x1F {
let mut shift = 0u32;
loop {
let b = *buf.get(off)?;
off += 1;
name_len += ((b & 0x7f) as usize) << shift;
if b & 0x80 == 0 {
break;
}
shift += 7;
}
}
let name_raw = buf.get(off..off + name_len)?;
off += name_len;
let name = if name_huffman {
let mut out = Vec::new();
crate::hpack::huffman::decode(name_raw, &mut out).ok()?;
out
} else {
name_raw.to_vec()
};
let first = *buf.get(off)?;
let value_huffman = first & 0x80 != 0;
let mut value_len = (first & 0x7f) as usize;
off += 1;
if value_len == 0x7f {
let mut shift = 0u32;
loop {
let b = *buf.get(off)?;
off += 1;
value_len += ((b & 0x7f) as usize) << shift;
if b & 0x80 == 0 {
break;
}
shift += 7;
}
}
let value_raw = buf.get(off..off + value_len)?;
let value = if value_huffman {
let mut out = Vec::new();
crate::hpack::huffman::decode(value_raw, &mut out).ok()?;
out
} else {
value_raw.to_vec()
};
Some((name, value))
}
#[test]
fn insert_with_literal_name_survives_strict_decode() {
let mut enc = Encoder::new(220, true);
enc.set_capacity(220);
let ins = enc
.insert_literal(b"x-custom-header", b"hello-world")
.unwrap();
let (name, value) = strict_decode_insert_literal_name(&ins)
.expect("strict QPACK decoder must parse the insert");
assert_eq!(name, b"x-custom-header");
assert_eq!(value, b"hello-world");
}
#[test]
fn neqo_ici_roundtrip_repro() {
use crate::h3::qpack::decoder::Decoder;
let mut enc = Encoder::new(4096, false);
let mut dec = Decoder::new(4096, 16);
for i in 0..64u64 {
let name = Bytes::from(format!("x-header-{i}"));
let value = Bytes::from(format!("value-{i}"));
let section = enc.encode_section_with_ack_base(2, &[(name, value)]);
let _ = dec
.feed_encoder_stream(§ion.encoder_stream)
.expect("client decoder ingests inserts");
let _ = dec
.decode_block(§ion.block, 2, 0)
.expect("client decoder decodes section")
.expect("section not blocked");
let client_dec = dec.take_decoder_stream();
enc.feed_decoder_stream(&client_dec)
.unwrap_or_else(|e| panic!("feed_decoder_stream failed on iter {i}: {e:?}"));
}
}
#[test]
fn split_decoder_stream_chunk_repro() {
let mut enc = Encoder::new(256, false);
enc.encode_section(128, &[hdr("a", "b")]);
assert!(
enc.feed_decoder_stream(&[0xFF]).is_ok(),
"partial instruction must not raise QPACK_DECODER_STREAM_ERROR"
);
assert!(enc.feed_decoder_stream(&[0x01]).is_ok());
}
#[test]
fn insert_with_literal_name_huffman_flag_at_bit_five() {
let mut enc = Encoder::new(220, true);
enc.set_capacity(220);
let ins = enc.insert_literal(b"x-custom-header", b"hello").unwrap();
let pos = ins
.iter()
.position(|&b| b & 0xC0 == 0x40)
.expect("insert instruction present");
assert_eq!(
ins[pos] & 0x20,
0x20,
"Huffman flag must be at bit 5 for an Insert With Literal Name"
);
}