use super::HpackEncoder;
use crate::{
KnownHeaderName,
headers::{
Headers,
entry_name::EntryName,
field_section::{FieldLineValue, FieldSection, PseudoHeaders},
header_observer::{ConnectionAccumulator, HeaderObserver},
hpack::HpackDecoder,
},
};
use std::sync::Arc;
fn observer() -> Arc<HeaderObserver> {
Arc::default()
}
fn new_encoder(
observer: Arc<HeaderObserver>,
local_pref: usize,
recent_pairs: usize,
) -> HpackEncoder {
let mut enc = HpackEncoder::new(observer, local_pref, recent_pairs);
enc.set_protocol_max_size(usize::MAX);
enc
}
fn encode(enc: &mut HpackEncoder, section: &FieldSection<'_>) -> Vec<u8> {
let mut buf = Vec::new();
enc.encode(section, &mut buf);
buf
}
fn entries_len(enc: &HpackEncoder) -> usize {
enc.state.entries.len()
}
fn insert_count(enc: &HpackEncoder) -> u64 {
enc.state.insert_count
}
fn current_size(enc: &HpackEncoder) -> usize {
enc.state.current_size
}
#[test]
fn full_static_match_roundtrips() {
let mut enc = new_encoder(observer(), 4096, 16);
let headers =
Headers::new().with_inserted_header(KnownHeaderName::AcceptEncoding, "gzip, deflate");
let section = FieldSection::new(PseudoHeaders::default(), &headers);
let buf = encode(&mut enc, §ion);
let mut dec = HpackDecoder::new(4096);
let decoded = dec.decode(&buf).unwrap();
assert_eq!(
decoded.headers().get_str(KnownHeaderName::AcceptEncoding),
Some("gzip, deflate"),
);
}
#[test]
fn name_match_only_uses_literal_without_indexing_on_first_sight() {
let mut enc = new_encoder(observer(), 4096, 16);
let headers = Headers::new().with_inserted_header(KnownHeaderName::Server, "trillium");
let section = FieldSection::new(PseudoHeaders::default(), &headers);
let buf = encode(&mut enc, §ion);
let mut dec = HpackDecoder::new(4096);
let decoded = dec.decode(&buf).unwrap();
assert_eq!(
decoded.headers().get_str(KnownHeaderName::Server),
Some("trillium"),
);
assert_eq!(entries_len(&enc), 0);
}
#[test]
fn second_sighting_promotes_to_indexed_dynamic() {
let mut enc = new_encoder(observer(), 4096, 16);
let headers = Headers::new().with_inserted_header(KnownHeaderName::Server, "trillium");
let mut dec = HpackDecoder::new(4096);
for _ in 0..3 {
let section = FieldSection::new(PseudoHeaders::default(), &headers);
let buf = encode(&mut enc, §ion);
let decoded = dec.decode(&buf).unwrap();
assert_eq!(
decoded.headers().get_str(KnownHeaderName::Server),
Some("trillium"),
);
}
assert_eq!(entries_len(&enc), 1);
assert_eq!(insert_count(&enc), 1);
}
#[test]
fn observer_hot_promotes_on_first_connection_sighting() {
let observer = observer();
let mut accum = ConnectionAccumulator::default();
accum.observe(
&EntryName::Known(KnownHeaderName::Server),
&FieldLineValue::Static(b"trillium"),
);
observer.fold_connection(&accum);
let mut enc = new_encoder(observer, 4096, 16);
let headers = Headers::new().with_inserted_header(KnownHeaderName::Server, "trillium");
let section = FieldSection::new(PseudoHeaders::default(), &headers);
let buf = encode(&mut enc, §ion);
assert_eq!(entries_len(&enc), 1);
let mut dec = HpackDecoder::new(4096);
let decoded = dec.decode(&buf).unwrap();
assert_eq!(
decoded.headers().get_str(KnownHeaderName::Server),
Some("trillium"),
);
}
#[test]
fn oversized_entry_clears_table_per_4_4() {
let observer = observer();
{
let mut accum = ConnectionAccumulator::default();
accum.observe(
&EntryName::Known(KnownHeaderName::Server),
&FieldLineValue::Static(b"trillium"),
);
accum.observe(
&EntryName::UnknownStatic("x-large"),
&FieldLineValue::Static(b"BIG"),
);
observer.fold_connection(&accum);
}
let mut enc = new_encoder(observer, 64, 16);
let h1 = Headers::new().with_inserted_header(KnownHeaderName::Server, "trillium");
let s1 = FieldSection::new(PseudoHeaders::default(), &h1);
let buf1 = encode(&mut enc, &s1);
assert_eq!(entries_len(&enc), 1);
let big_value: String = "x".repeat(200);
let h2 = Headers::new().with_inserted_header(
crate::headers::HeaderName::from("x-large"),
big_value.clone(),
);
let s2 = FieldSection::new(PseudoHeaders::default(), &h2);
let buf2 = encode(&mut enc, &s2);
assert_eq!(entries_len(&enc), 0);
assert_eq!(current_size(&enc), 0);
let mut dec = HpackDecoder::new(64);
assert_eq!(
dec.decode(&buf1)
.unwrap()
.headers()
.get_str(KnownHeaderName::Server),
Some("trillium"),
);
let decoded2 = dec.decode(&buf2).unwrap();
assert_eq!(
decoded2
.headers()
.get_str(crate::headers::HeaderName::from("x-large")),
Some(big_value.as_str()),
);
}
#[test]
fn intra_block_eviction_resolved_inline() {
let observer = observer();
{
let mut accum = ConnectionAccumulator::default();
accum.observe(
&EntryName::Known(KnownHeaderName::Server),
&FieldLineValue::Static(b"trillium"),
);
accum.observe(
&EntryName::Known(KnownHeaderName::Etag),
&FieldLineValue::Static(b"abc123"),
);
observer.fold_connection(&accum);
}
let mut enc = new_encoder(observer, 50, 16);
let mut headers = Headers::new();
headers.append(KnownHeaderName::Server, "trillium");
headers.append(KnownHeaderName::Etag, "abc123");
let section = FieldSection::new(PseudoHeaders::default(), &headers);
let buf = encode(&mut enc, §ion);
let mut dec = HpackDecoder::new(50);
let decoded = dec.decode(&buf).unwrap();
assert_eq!(
decoded.headers().get_str(KnownHeaderName::Server),
Some("trillium"),
);
assert_eq!(
decoded.headers().get_str(KnownHeaderName::Etag),
Some("abc123"),
);
assert_eq!(entries_len(&enc), 1);
}
#[test]
fn pre_settings_encoder_never_inserts() {
let observer = observer();
let mut accum = ConnectionAccumulator::default();
accum.observe(
&EntryName::Known(KnownHeaderName::Server),
&FieldLineValue::Static(b"trillium"),
);
observer.fold_connection(&accum);
let mut enc = HpackEncoder::new(observer, 4096, 16);
let headers = Headers::new().with_inserted_header(KnownHeaderName::Server, "trillium");
let section = FieldSection::new(PseudoHeaders::default(), &headers);
let buf = encode(&mut enc, §ion);
assert_eq!(entries_len(&enc), 0);
let mut dec = HpackDecoder::new(4096);
let decoded = dec.decode(&buf).unwrap();
assert_eq!(
decoded.headers().get_str(KnownHeaderName::Server),
Some("trillium"),
);
}
#[test]
fn protocol_max_settings_emits_size_update() {
let mut enc = HpackEncoder::new(observer(), 4096, 16);
enc.set_protocol_max_size(4096);
let headers = Headers::new().with_inserted_header(KnownHeaderName::AcceptEncoding, "gzip");
let section = FieldSection::new(PseudoHeaders::default(), &headers);
let buf = encode(&mut enc, §ion);
assert_eq!(buf[0], 0x3F);
let mut dec = HpackDecoder::new(4096);
let decoded = dec.decode(&buf).unwrap();
assert_eq!(
decoded.headers().get_str(KnownHeaderName::AcceptEncoding),
Some("gzip"),
);
}
#[test]
fn protocol_max_caps_at_local_preferred() {
let observer = observer();
let mut accum = ConnectionAccumulator::default();
accum.observe(
&EntryName::Known(KnownHeaderName::Server),
&FieldLineValue::Static(b"trillium"),
);
observer.fold_connection(&accum);
let mut enc = HpackEncoder::new(observer, 1024, 16);
enc.set_protocol_max_size(8192);
let headers = Headers::new().with_inserted_header(KnownHeaderName::Server, "trillium");
let section = FieldSection::new(PseudoHeaders::default(), &headers);
let buf = encode(&mut enc, §ion);
let mut dec = HpackDecoder::new(1024);
let decoded = dec.decode(&buf).unwrap();
assert_eq!(
decoded.headers().get_str(KnownHeaderName::Server),
Some("trillium"),
);
assert_eq!(entries_len(&enc), 1);
}
#[test]
fn protocol_max_shrink_evicts_and_emits_size_update() {
let observer = observer();
{
let mut accum = ConnectionAccumulator::default();
accum.observe(
&EntryName::Known(KnownHeaderName::Server),
&FieldLineValue::Static(b"trillium"),
);
accum.observe(
&EntryName::Known(KnownHeaderName::Etag),
&FieldLineValue::Static(b"abc123"),
);
observer.fold_connection(&accum);
}
let mut enc = HpackEncoder::new(observer, 1024, 16);
enc.set_protocol_max_size(1024);
let mut h1 = Headers::new();
h1.append(KnownHeaderName::Server, "trillium");
h1.append(KnownHeaderName::Etag, "abc123");
let s1 = FieldSection::new(PseudoHeaders::default(), &h1);
let buf1 = encode(&mut enc, &s1);
assert_eq!(entries_len(&enc), 2);
enc.set_protocol_max_size(50);
assert_eq!(entries_len(&enc), 1);
let h2 = Headers::new().with_inserted_header(KnownHeaderName::AcceptEncoding, "gzip");
let s2 = FieldSection::new(PseudoHeaders::default(), &h2);
let buf2 = encode(&mut enc, &s2);
assert_eq!(buf2[0] & 0b1110_0000, 0b0010_0000);
let mut dec = HpackDecoder::new(1024);
let _ = dec.decode(&buf1).unwrap();
let _ = dec.decode(&buf2).unwrap();
}
#[test]
fn protocol_max_zero_clears_table() {
let observer = observer();
let mut accum = ConnectionAccumulator::default();
accum.observe(
&EntryName::Known(KnownHeaderName::Server),
&FieldLineValue::Static(b"trillium"),
);
observer.fold_connection(&accum);
let mut enc = HpackEncoder::new(observer, 4096, 16);
enc.set_protocol_max_size(4096);
let h1 = Headers::new().with_inserted_header(KnownHeaderName::Server, "trillium");
let s1 = FieldSection::new(PseudoHeaders::default(), &h1);
let buf1 = encode(&mut enc, &s1);
assert_eq!(entries_len(&enc), 1);
enc.set_protocol_max_size(0);
assert_eq!(entries_len(&enc), 0);
let buf2 = encode(&mut enc, &s1);
assert_eq!(buf2[0], 0x20);
let mut dec = HpackDecoder::new(4096);
let _ = dec.decode(&buf1).unwrap();
let _ = dec.decode(&buf2).unwrap();
}
#[test]
fn protocol_max_idempotent() {
let mut enc = HpackEncoder::new(observer(), 4096, 16);
enc.set_protocol_max_size(4096);
let headers = Headers::new().with_inserted_header(KnownHeaderName::AcceptEncoding, "gzip");
let section = FieldSection::new(PseudoHeaders::default(), &headers);
let _ = encode(&mut enc, §ion);
enc.set_protocol_max_size(4096); let buf = encode(&mut enc, §ion);
assert_ne!(buf[0] & 0b1110_0000, 0b0010_0000);
}
#[test]
fn never_indexed_round_trips_through_headers() {
use crate::HeaderValue;
let mut enc = new_encoder(observer(), 4096, 16);
let mut secret = HeaderValue::from("Bearer abc123");
secret.set_never_indexed(true);
let mut headers = Headers::new();
headers.insert(KnownHeaderName::Authorization, secret);
headers.insert(KnownHeaderName::ContentType, "application/json");
let section = FieldSection::new(PseudoHeaders::default(), &headers);
let buf = encode(&mut enc, §ion);
let mut dec = HpackDecoder::new(4096);
let decoded = dec.decode(&buf).unwrap();
let auth = decoded
.headers()
.get_values(KnownHeaderName::Authorization)
.expect("authorization present");
assert_eq!(
auth.one().and_then(HeaderValue::as_str),
Some("Bearer abc123")
);
assert!(
auth.iter().all(HeaderValue::is_never_indexed),
"N bit must survive HPACK round-trip on the secret value",
);
let ct = decoded
.headers()
.get_values(KnownHeaderName::ContentType)
.expect("content-type present");
assert!(
ct.iter().all(|v| !v.is_never_indexed()),
"non-secret value must not pick up the N bit",
);
assert_eq!(entries_len(&enc), 0);
}
#[test]
fn never_indexed_emits_section_6_2_3_for_static_full_match() {
use crate::HeaderValue;
let mut enc = new_encoder(observer(), 4096, 16);
let _ = encode(
&mut enc,
&FieldSection::new(PseudoHeaders::default(), &Headers::new()),
);
let mut value = HeaderValue::from("gzip, deflate");
value.set_never_indexed(true);
let headers = Headers::new().with_inserted_header(KnownHeaderName::AcceptEncoding, value);
let section = FieldSection::new(PseudoHeaders::default(), &headers);
let buf = encode(&mut enc, §ion);
assert_eq!(
buf[0] & 0b1111_0000,
0b0001_0000,
"expected §6.2.3 LiteralNeverIndexed (0001xxxx), got first byte {:#04x}",
buf[0],
);
let mut dec = HpackDecoder::new(4096);
let decoded = dec.decode(&buf).unwrap();
let v = decoded
.headers()
.get_values(KnownHeaderName::AcceptEncoding)
.expect("accept-encoding present");
assert!(v.iter().all(HeaderValue::is_never_indexed));
assert_eq!(entries_len(&enc), 0);
}