use std::borrow::Cow;
use std::collections::{HashMap, VecDeque};
use std::sync::LazyLock;
use crate::bytes::{Bytes, BytesMut};
use super::error::H2Error;
static HUFFMAN_DECODE_INDEX: LazyLock<HashMap<(u32, u8), Option<u8>>> = LazyLock::new(|| {
let mut map = HashMap::with_capacity(257);
for (sym, &(code, code_bits)) in HUFFMAN_TABLE.iter().enumerate() {
if code_bits >= 9 {
let value = if sym == 256 { None } else { Some(sym as u8) };
map.insert((code, code_bits), value);
}
}
map
});
static STATIC_EXACT_INDEX: LazyLock<HashMap<(&'static str, &'static str), usize>> =
LazyLock::new(|| {
STATIC_TABLE
.iter()
.enumerate()
.map(|(i, &(n, v))| ((n, v), i + 1))
.collect()
});
static STATIC_NAME_INDEX: LazyLock<HashMap<&'static str, usize>> = LazyLock::new(|| {
let mut map = HashMap::with_capacity(STATIC_TABLE.len());
for (i, &(name, _)) in STATIC_TABLE.iter().enumerate() {
map.entry(name).or_insert(i + 1);
}
map
});
const MAX_ALLOWED_TABLE_SIZE: usize = 1024 * 1024;
pub const DEFAULT_MAX_TABLE_SIZE: usize = 4096;
static STATIC_TABLE: &[(&str, &str)] = &[
(":authority", ""), (":method", "GET"), (":method", "POST"), (":path", "/"), (":path", "/index.html"), (":scheme", "http"), (":scheme", "https"), (":status", "200"), (":status", "204"), (":status", "206"), (":status", "304"), (":status", "400"), (":status", "404"), (":status", "500"), ("accept-charset", ""), ("accept-encoding", "gzip, deflate"), ("accept-language", ""), ("accept-ranges", ""), ("accept", ""), ("access-control-allow-origin", ""), ("age", ""), ("allow", ""), ("authorization", ""), ("cache-control", ""), ("content-disposition", ""), ("content-encoding", ""), ("content-language", ""), ("content-length", ""), ("content-location", ""), ("content-range", ""), ("content-type", ""), ("cookie", ""), ("date", ""), ("etag", ""), ("expect", ""), ("expires", ""), ("from", ""), ("host", ""), ("if-match", ""), ("if-modified-since", ""), ("if-none-match", ""), ("if-range", ""), ("if-unmodified-since", ""), ("last-modified", ""), ("link", ""), ("location", ""), ("max-forwards", ""), ("proxy-authenticate", ""), ("proxy-authorization", ""), ("range", ""), ("referer", ""), ("refresh", ""), ("retry-after", ""), ("server", ""), ("set-cookie", ""), ("strict-transport-security", ""), ("transfer-encoding", ""), ("user-agent", ""), ("vary", ""), ("via", ""), ("www-authenticate", ""), ];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Header {
pub name: String,
pub value: String,
}
impl Header {
#[must_use]
pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
Self {
name: name.into(),
value: value.into(),
}
}
#[must_use]
pub fn size(&self) -> usize {
self.name.len() + self.value.len() + 32
}
}
#[derive(Debug)]
pub struct DynamicTable {
entries: VecDeque<Header>,
size: usize,
max_size: usize,
}
impl DynamicTable {
#[must_use]
pub fn new() -> Self {
Self {
entries: VecDeque::new(),
size: 0,
max_size: DEFAULT_MAX_TABLE_SIZE,
}
}
#[must_use]
pub fn with_max_size(max_size: usize) -> Self {
Self {
entries: VecDeque::new(),
size: 0,
max_size: max_size.min(MAX_ALLOWED_TABLE_SIZE),
}
}
#[must_use]
pub fn size(&self) -> usize {
self.size
}
#[must_use]
pub fn max_size(&self) -> usize {
self.max_size
}
pub fn set_max_size(&mut self, max_size: usize) {
self.max_size = max_size.min(MAX_ALLOWED_TABLE_SIZE);
self.evict();
}
pub fn insert(&mut self, header: Header) {
let entry_size = header.size();
while self.size.saturating_add(entry_size) > self.max_size && !self.entries.is_empty() {
if let Some(evicted) = self.entries.pop_back() {
self.size = self.size.saturating_sub(evicted.size());
}
}
if entry_size <= self.max_size {
self.entries.push_front(header);
self.size = self.size.saturating_add(entry_size);
}
}
#[must_use]
pub fn get(&self, index: usize) -> Option<&Header> {
if index == 0 || index > self.entries.len() {
None
} else {
Some(&self.entries[index - 1])
}
}
#[must_use]
pub fn find(&self, name: &str, value: &str) -> Option<usize> {
for (i, entry) in self.entries.iter().enumerate() {
if entry.name == name && entry.value == value {
return Some(STATIC_TABLE.len() + i + 1);
}
}
None
}
#[must_use]
pub fn find_name(&self, name: &str) -> Option<usize> {
for (i, entry) in self.entries.iter().enumerate() {
if entry.name == name {
return Some(STATIC_TABLE.len() + i + 1);
}
}
None
}
fn evict(&mut self) {
while self.size > self.max_size && !self.entries.is_empty() {
if let Some(evicted) = self.entries.pop_back() {
self.size = self.size.saturating_sub(evicted.size());
}
}
}
}
impl Default for DynamicTable {
fn default() -> Self {
Self::new()
}
}
fn find_static(name: &str, value: &str) -> Option<usize> {
STATIC_EXACT_INDEX.get(&(name, value)).copied()
}
fn find_static_name(name: &str) -> Option<usize> {
STATIC_NAME_INDEX.get(name).copied()
}
fn get_static(index: usize) -> Option<(&'static str, &'static str)> {
if index == 0 || index > STATIC_TABLE.len() {
None
} else {
Some(STATIC_TABLE[index - 1])
}
}
#[derive(Debug)]
pub struct Encoder {
dynamic_table: DynamicTable,
use_huffman: bool,
min_size_update: Option<usize>,
pending_size_update: Option<usize>,
}
impl Encoder {
#[must_use]
pub fn new() -> Self {
Self {
dynamic_table: DynamicTable::new(),
use_huffman: true,
min_size_update: None,
pending_size_update: None,
}
}
#[must_use]
pub fn with_max_size(max_size: usize) -> Self {
Self {
dynamic_table: DynamicTable::with_max_size(max_size),
use_huffman: true,
min_size_update: None,
pending_size_update: None,
}
}
pub fn set_use_huffman(&mut self, use_huffman: bool) {
self.use_huffman = use_huffman;
}
pub fn set_max_table_size(&mut self, size: usize) {
let capped = size.min(MAX_ALLOWED_TABLE_SIZE);
self.dynamic_table.set_max_size(capped);
if let Some(min_so_far) = self.min_size_update {
self.min_size_update = Some(min_so_far.min(capped));
} else {
self.min_size_update = Some(capped);
}
self.pending_size_update = Some(capped);
}
#[must_use]
pub fn dynamic_table_size(&self) -> usize {
self.dynamic_table.size()
}
#[must_use]
pub fn dynamic_table_max_size(&self) -> usize {
self.dynamic_table.max_size()
}
pub fn encode(&mut self, headers: &[Header], dst: &mut BytesMut) {
self.emit_pending_size_update(dst);
for header in headers {
self.encode_header(header, dst, true);
}
}
pub fn encode_sensitive(&mut self, headers: &[Header], dst: &mut BytesMut) {
self.emit_pending_size_update(dst);
for header in headers {
self.encode_header(header, dst, false);
}
}
fn emit_pending_size_update(&mut self, dst: &mut BytesMut) {
if let Some(min_size) = self.min_size_update.take() {
if let Some(final_size) = self.pending_size_update.take() {
if min_size < final_size {
encode_integer(dst, min_size, 5, 0x20);
}
encode_integer(dst, final_size, 5, 0x20);
}
}
}
fn encode_header(&mut self, header: &Header, dst: &mut BytesMut, index: bool) {
let normalized_name = if header.name.bytes().any(|byte| byte.is_ascii_uppercase()) {
Cow::Owned(header.name.to_ascii_lowercase())
} else {
Cow::Borrowed(header.name.as_str())
};
let name = normalized_name.as_ref();
let value = header.value.as_str();
if index {
if let Some(idx) =
find_static(name, value).or_else(|| self.dynamic_table.find(name, value))
{
encode_integer(dst, idx, 7, 0x80);
return;
}
}
let name_idx = find_static_name(name).or_else(|| self.dynamic_table.find_name(name));
if index {
if let Some(idx) = name_idx {
encode_integer(dst, idx, 6, 0x40);
} else {
dst.put_u8(0x40);
encode_string(dst, name, self.use_huffman);
}
encode_string(dst, value, self.use_huffman);
let mut indexed_header = header.clone();
indexed_header.name = normalized_name.into_owned();
self.dynamic_table.insert(indexed_header);
} else {
if let Some(idx) = name_idx {
encode_integer(dst, idx, 4, 0x10);
} else {
dst.put_u8(0x10);
encode_string(dst, name, self.use_huffman);
}
encode_string(dst, value, self.use_huffman);
}
}
}
impl Default for Encoder {
fn default() -> Self {
Self::new()
}
}
const MAX_STRING_LENGTH: usize = 256 * 1024;
const MAX_SIZE_UPDATES: usize = 16;
#[derive(Debug)]
pub struct Decoder {
dynamic_table: DynamicTable,
max_header_list_size: usize,
allowed_table_size: usize,
}
impl Decoder {
#[must_use]
pub fn new() -> Self {
Self {
dynamic_table: DynamicTable::new(),
max_header_list_size: 16384,
allowed_table_size: 4096, }
}
#[must_use]
pub fn with_max_size(max_size: usize) -> Self {
let capped_size = max_size.min(MAX_ALLOWED_TABLE_SIZE);
Self {
dynamic_table: DynamicTable::with_max_size(capped_size),
max_header_list_size: 16384,
allowed_table_size: capped_size,
}
}
pub fn set_max_header_list_size(&mut self, size: usize) {
self.max_header_list_size = size;
}
pub fn set_allowed_table_size(&mut self, size: usize) {
self.allowed_table_size = size.min(MAX_ALLOWED_TABLE_SIZE);
}
#[must_use]
pub fn dynamic_table_size(&self) -> usize {
self.dynamic_table.size()
}
#[must_use]
pub fn dynamic_table_max_size(&self) -> usize {
self.dynamic_table.max_size()
}
#[must_use]
pub fn allowed_table_size(&self) -> usize {
self.allowed_table_size
}
pub fn decode(&mut self, src: &mut Bytes) -> Result<Vec<Header>, H2Error> {
let mut headers = Vec::with_capacity(8);
let mut total_size = 0;
let mut size_update_count = 0;
while !src.is_empty() && (src[0] & 0xe0 == 0x20) {
size_update_count += 1;
if size_update_count > MAX_SIZE_UPDATES {
return Err(H2Error::compression(
"too many consecutive dynamic table size updates",
));
}
let new_size = decode_integer(src, 5)?;
if new_size > self.allowed_table_size {
return Err(H2Error::compression(
"dynamic table size update exceeds allowed maximum",
));
}
self.dynamic_table.set_max_size(new_size);
}
while !src.is_empty() {
let header = self.decode_header(src)?;
total_size += header.size();
if total_size > self.max_header_list_size {
return Err(H2Error::compression("header list too large"));
}
headers.push(header);
}
Ok(headers)
}
fn decode_header(&mut self, src: &mut Bytes) -> Result<Header, H2Error> {
if src.is_empty() {
return Err(H2Error::compression("unexpected end of header block"));
}
let first = src[0];
if first & 0x80 != 0 {
let index = decode_integer(src, 7)?;
return self.get_indexed(index);
}
if first & 0x40 != 0 {
let (name, value) = self.decode_literal(src, 6)?;
let header = Header::new(name, value);
self.dynamic_table.insert(header.clone());
return Ok(header);
}
if first & 0x20 != 0 {
return Err(H2Error::compression(
"dynamic table size update after first header in block",
));
}
if first & 0x10 != 0 {
let (name, value) = self.decode_literal(src, 4)?;
return Ok(Header::new(name, value));
}
let (name, value) = self.decode_literal(src, 4)?;
Ok(Header::new(name, value))
}
fn decode_literal(
&self,
src: &mut Bytes,
prefix_bits: u8,
) -> Result<(String, String), H2Error> {
let index = decode_integer(src, prefix_bits)?;
let name = if index == 0 {
let n = decode_string(src)?;
validate_header_name(&n)?;
n
} else {
self.get_indexed_name(index)?
};
let value = decode_string(src)?;
validate_header_value(&value)?;
Ok((name, value))
}
fn get_indexed(&self, index: usize) -> Result<Header, H2Error> {
if index == 0 {
return Err(H2Error::compression("invalid index 0"));
}
if index <= STATIC_TABLE.len() {
let (name, value) =
get_static(index).ok_or_else(|| H2Error::compression("invalid static index"))?;
Ok(Header::new(name, value))
} else {
let dyn_index = index - STATIC_TABLE.len();
self.dynamic_table
.get(dyn_index)
.cloned()
.ok_or_else(|| H2Error::compression("invalid dynamic index"))
}
}
fn get_indexed_name(&self, index: usize) -> Result<String, H2Error> {
if index == 0 {
return Err(H2Error::compression("invalid index 0"));
}
if index <= STATIC_TABLE.len() {
let (name, _) =
get_static(index).ok_or_else(|| H2Error::compression("invalid static index"))?;
Ok(name.to_string())
} else {
let dyn_index = index - STATIC_TABLE.len();
self.dynamic_table
.get(dyn_index)
.map(|h| h.name.clone())
.ok_or_else(|| H2Error::compression("invalid dynamic index"))
}
}
}
impl Default for Decoder {
fn default() -> Self {
Self::new()
}
}
#[inline]
fn encode_integer(dst: &mut BytesMut, value: usize, prefix_bits: u8, prefix: u8) {
let max_first = (1 << prefix_bits) - 1;
if value < max_first {
dst.put_u8(prefix | value as u8);
} else {
dst.put_u8(prefix | max_first as u8);
let mut remaining = value - max_first;
while remaining >= 128 {
dst.put_u8((remaining & 0x7f) as u8 | 0x80);
remaining >>= 7;
}
dst.put_u8(remaining as u8);
}
}
fn decode_integer(src: &mut Bytes, prefix_bits: u8) -> Result<usize, H2Error> {
if src.is_empty() {
return Err(H2Error::compression("unexpected end of integer"));
}
let max_first = (1 << prefix_bits) - 1;
let first = src[0] & max_first as u8;
let _ = src.split_to(1);
if (first as usize) < max_first {
return Ok(first as usize);
}
let mut value = max_first;
let mut shift = 0;
loop {
if src.is_empty() {
return Err(H2Error::compression("unexpected end of integer"));
}
let byte = src[0];
let _ = src.split_to(1);
if shift > 28 {
return Err(H2Error::compression("integer too large"));
}
let multiplier = 1usize
.checked_shl(shift)
.ok_or_else(|| H2Error::compression("integer overflow in shift"))?;
let increment = ((byte & 0x7f) as usize)
.checked_mul(multiplier)
.ok_or_else(|| H2Error::compression("integer overflow in multiply"))?;
value = value
.checked_add(increment)
.ok_or_else(|| H2Error::compression("integer overflow in addition"))?;
shift += 7;
if byte & 0x80 == 0 {
break;
}
}
Ok(value)
}
const fn build_bit_masks() -> [u64; 65] {
let mut masks = [0u64; 65];
let mut i = 0usize;
while i <= 64 {
masks[i] = if i == 64 { u64::MAX } else { (1u64 << i) - 1 };
i += 1;
}
masks
}
const BIT_MASKS: [u64; 65] = build_bit_masks();
fn encode_huffman(src: &[u8]) -> Vec<u8> {
let mut dst = Vec::with_capacity(src.len());
let mut accumulator: u64 = 0;
let mut bits: u32 = 0;
for &byte in src {
let (code, code_bits) = HUFFMAN_TABLE[byte as usize];
let code_bits_u32 = u32::from(code_bits);
accumulator = (accumulator << code_bits_u32) | u64::from(code);
bits += code_bits_u32;
while bits >= 8 {
bits -= 8;
dst.push((accumulator >> bits) as u8);
accumulator &= BIT_MASKS[bits as usize];
}
}
if bits > 0 {
let padding = 8 - bits;
accumulator = (accumulator << padding) | BIT_MASKS[padding as usize];
dst.push(accumulator as u8);
}
dst
}
#[inline]
fn encode_string(dst: &mut BytesMut, value: &str, use_huffman: bool) {
if use_huffman {
let encoded = encode_huffman(value.as_bytes());
encode_integer(dst, encoded.len(), 7, 0x80);
dst.extend_from_slice(&encoded);
} else {
let bytes = value.as_bytes();
encode_integer(dst, bytes.len(), 7, 0x00);
dst.extend_from_slice(bytes);
}
}
fn validate_header_name(name: &str) -> Result<(), H2Error> {
if name.is_empty() {
return Err(H2Error::compression("empty header name"));
}
for (i, b) in name.bytes().enumerate() {
let valid = matches!(b,
b'a'..=b'z' | b'0'..=b'9'
| b'!' | b'#' | b'$' | b'%' | b'&' | b'\'' | b'*'
| b'+' | b'-' | b'.' | b'^' | b'_' | b'`' | b'|' | b'~'
) || (b == b':' && i == 0);
if !valid {
return Err(H2Error::compression(
"invalid character in header name (RFC 9113 Section 8.2.1)",
));
}
}
Ok(())
}
fn validate_header_value(value: &str) -> Result<(), H2Error> {
for b in value.bytes() {
if matches!(b, b'\0' | b'\r' | b'\n') {
return Err(H2Error::compression(
"invalid character in header value (RFC 9113 Section 8.2.1)",
));
}
}
Ok(())
}
fn decode_string(src: &mut Bytes) -> Result<String, H2Error> {
if src.is_empty() {
return Err(H2Error::compression("unexpected end of string"));
}
let huffman = src[0] & 0x80 != 0;
let length = decode_integer(src, 7)?;
if length > MAX_STRING_LENGTH {
return Err(H2Error::compression("string length exceeds maximum"));
}
if src.len() < length {
return Err(H2Error::compression("string length exceeds buffer"));
}
let data = src.split_to(length);
if huffman {
decode_huffman(&data)
} else {
String::from_utf8(data.to_vec())
.map_err(|_| H2Error::compression("invalid UTF-8 in header"))
}
}
#[rustfmt::skip]
#[allow(clippy::unreadable_literal)]
static HUFFMAN_TABLE: [(u32, u8); 257] = [
(0x1ff8, 13), (0x7fffd8, 23), (0xfffffe2, 28), (0xfffffe3, 28), (0xfffffe4, 28), (0xfffffe5, 28), (0xfffffe6, 28), (0xfffffe7, 28), (0xfffffe8, 28), (0xffffea, 24), (0x3ffffffc, 30), (0xfffffe9, 28), (0xfffffea, 28), (0x3ffffffd, 30), (0xfffffeb, 28), (0xfffffec, 28), (0xfffffed, 28), (0xfffffee, 28), (0xfffffef, 28), (0xffffff0, 28), (0xffffff1, 28), (0xffffff2, 28), (0x3ffffffe, 30), (0xffffff3, 28), (0xffffff4, 28), (0xffffff5, 28), (0xffffff6, 28), (0xffffff7, 28), (0xffffff8, 28), (0xffffff9, 28), (0xffffffa, 28), (0xffffffb, 28), (0x14, 6), (0x3f8, 10), (0x3f9, 10), (0xffa, 12), (0x1ff9, 13), (0x15, 6), (0xf8, 8), (0x7fa, 11), (0x3fa, 10), (0x3fb, 10), (0xf9, 8), (0x7fb, 11), (0xfa, 8), (0x16, 6), (0x17, 6), (0x18, 6), (0x0, 5), (0x1, 5), (0x2, 5), (0x19, 6), (0x1a, 6), (0x1b, 6), (0x1c, 6), (0x1d, 6), (0x1e, 6), (0x1f, 6), (0x5c, 7), (0xfb, 8), (0x7ffc, 15), (0x20, 6), (0xffb, 12), (0x3fc, 10), (0x1ffa, 13), (0x21, 6), (0x5d, 7), (0x5e, 7), (0x5f, 7), (0x60, 7), (0x61, 7), (0x62, 7), (0x63, 7), (0x64, 7), (0x65, 7), (0x66, 7), (0x67, 7), (0x68, 7), (0x69, 7), (0x6a, 7), (0x6b, 7), (0x6c, 7), (0x6d, 7), (0x6e, 7), (0x6f, 7), (0x70, 7), (0x71, 7), (0x72, 7), (0xfc, 8), (0x73, 7), (0xfd, 8), (0x1ffb, 13), (0x7fff0, 19), (0x1ffc, 13), (0x3ffc, 14), (0x22, 6), (0x7ffd, 15), (0x3, 5), (0x23, 6), (0x4, 5), (0x24, 6), (0x5, 5), (0x25, 6), (0x26, 6), (0x27, 6), (0x6, 5), (0x74, 7), (0x75, 7), (0x28, 6), (0x29, 6), (0x2a, 6), (0x7, 5), (0x2b, 6), (0x76, 7), (0x2c, 6), (0x8, 5), (0x9, 5), (0x2d, 6), (0x77, 7), (0x78, 7), (0x79, 7), (0x7a, 7), (0x7b, 7), (0x7ffe, 15), (0x7fc, 11), (0x3ffd, 14), (0x1ffd, 13), (0xffffffc, 28), (0xfffe6, 20), (0x3fffd2, 22), (0xfffe7, 20), (0xfffe8, 20), (0x3fffd3, 22), (0x3fffd4, 22), (0x3fffd5, 22), (0x7fffd9, 23), (0x3fffd6, 22), (0x7fffda, 23), (0x7fffdb, 23), (0x7fffdc, 23), (0x7fffdd, 23), (0x7fffde, 23), (0xffffeb, 24), (0x7fffdf, 23), (0xffffec, 24), (0xffffed, 24), (0x3fffd7, 22), (0x7fffe0, 23), (0xffffee, 24), (0x7fffe1, 23), (0x7fffe2, 23), (0x7fffe3, 23), (0x7fffe4, 23), (0x1fffdc, 21), (0x3fffd8, 22), (0x7fffe5, 23), (0x3fffd9, 22), (0x7fffe6, 23), (0x7fffe7, 23), (0xffffef, 24), (0x3fffda, 22), (0x1fffdd, 21), (0xfffe9, 20), (0x3fffdb, 22), (0x3fffdc, 22), (0x7fffe8, 23), (0x7fffe9, 23), (0x1fffde, 21), (0x7fffea, 23), (0x3fffdd, 22), (0x3fffde, 22), (0xfffff0, 24), (0x1fffdf, 21), (0x3fffdf, 22), (0x7fffeb, 23), (0x7fffec, 23), (0x1fffe0, 21), (0x1fffe1, 21), (0x3fffe0, 22), (0x1fffe2, 21), (0x7fffed, 23), (0x3fffe1, 22), (0x7fffee, 23), (0x7fffef, 23), (0xfffea, 20), (0x3fffe2, 22), (0x3fffe3, 22), (0x3fffe4, 22), (0x7ffff0, 23), (0x3fffe5, 22), (0x3fffe6, 22), (0x7ffff1, 23), (0x3ffffe0, 26), (0x3ffffe1, 26), (0xfffeb, 20), (0x7fff1, 19), (0x3fffe7, 22), (0x7ffff2, 23), (0x3fffe8, 22), (0x1ffffec, 25), (0x3ffffe2, 26), (0x3ffffe3, 26), (0x3ffffe4, 26), (0x7ffffde, 27), (0x7ffffdf, 27), (0x3ffffe5, 26), (0xfffff1, 24), (0x1ffffed, 25), (0x7fff2, 19), (0x1fffe3, 21), (0x3ffffe6, 26), (0x7ffffe0, 27), (0x7ffffe1, 27), (0x3ffffe7, 26), (0x7ffffe2, 27), (0xfffff2, 24), (0x1fffe4, 21), (0x1fffe5, 21), (0x3ffffe8, 26), (0x3ffffe9, 26), (0xffffffd, 28), (0x7ffffe3, 27), (0x7ffffe4, 27), (0x7ffffe5, 27), (0xfffec, 20), (0xfffff3, 24), (0xfffed, 20), (0x1fffe6, 21), (0x3fffe9, 22), (0x1fffe7, 21), (0x1fffe8, 21), (0x7ffff3, 23), (0x3fffea, 22), (0x3fffeb, 22), (0x1ffffee, 25), (0x1ffffef, 25), (0xfffff4, 24), (0xfffff5, 24), (0x3ffffea, 26), (0x7ffff4, 23), (0x3ffffeb, 26), (0x7ffffe6, 27), (0x3ffffec, 26), (0x3ffffed, 26), (0x7ffffe7, 27), (0x7ffffe8, 27), (0x7ffffe9, 27), (0x7ffffea, 27), (0x7ffffeb, 27), (0xffffffe, 28), (0x7ffffec, 27), (0x7ffffed, 27), (0x7ffffee, 27), (0x7ffffef, 27), (0x7fffff0, 27), (0x3ffffee, 26), (0x3fffffff, 30), ];
#[allow(clippy::too_many_lines)] fn decode_huffman(src: &Bytes) -> Result<String, H2Error> {
let estimated_symbols = src.len().saturating_mul(8).saturating_add(4) / 5;
let mut result = Vec::with_capacity(estimated_symbols);
let mut accumulator: u64 = 0;
let mut bits: u32 = 0;
for &byte in src.iter() {
accumulator = (accumulator << 8) | u64::from(byte);
bits += 8;
while bits >= 5 {
let high_5 = (accumulator >> (bits - 5)) as u32 & 0x1F;
if high_5 < 10 {
let sym = match high_5 {
0 => b'0',
1 => b'1',
2 => b'2',
3 => b'a',
4 => b'c',
5 => b'e',
6 => b'i',
7 => b'o',
8 => b's',
9 => b't',
_ => unreachable!(),
};
result.push(sym);
bits -= 5;
accumulator &= BIT_MASKS[bits as usize];
continue;
}
if bits >= 6 {
let high_6 = (accumulator >> (bits - 6)) as u32 & 0x3F;
let sym_6 = match high_6 {
0x14 => Some(b' '),
0x15 => Some(b'%'),
0x16 => Some(b'-'),
0x17 => Some(b'.'),
0x18 => Some(b'/'),
0x19 => Some(b'3'),
0x1a => Some(b'4'),
0x1b => Some(b'5'),
0x1c => Some(b'6'),
0x1d => Some(b'7'),
0x1e => Some(b'8'),
0x1f => Some(b'9'),
0x20 => Some(b'='),
0x21 => Some(b'A'),
0x22 => Some(b'_'),
0x23 => Some(b'b'),
0x24 => Some(b'd'),
0x25 => Some(b'f'),
0x26 => Some(b'g'),
0x27 => Some(b'h'),
0x28 => Some(b'l'),
0x29 => Some(b'm'),
0x2a => Some(b'n'),
0x2b => Some(b'p'),
0x2c => Some(b'r'),
0x2d => Some(b'u'),
_ => None,
};
if let Some(s) = sym_6 {
result.push(s);
bits -= 6;
accumulator &= BIT_MASKS[bits as usize];
continue;
}
}
if bits >= 7 {
let high_7 = (accumulator >> (bits - 7)) as u32 & 0x7F;
let sym_7 = match high_7 {
0x5c => Some(b':'),
0x5d => Some(b'B'),
0x5e => Some(b'C'),
0x5f => Some(b'D'),
0x60 => Some(b'E'),
0x61 => Some(b'F'),
0x62 => Some(b'G'),
0x63 => Some(b'H'),
0x64 => Some(b'I'),
0x65 => Some(b'J'),
0x66 => Some(b'K'),
0x67 => Some(b'L'),
0x68 => Some(b'M'),
0x69 => Some(b'N'),
0x6a => Some(b'O'),
0x6b => Some(b'P'),
0x6c => Some(b'Q'),
0x6d => Some(b'R'),
0x6e => Some(b'S'),
0x6f => Some(b'T'),
0x70 => Some(b'U'),
0x71 => Some(b'V'),
0x72 => Some(b'W'),
0x73 => Some(b'Y'),
0x74 => Some(b'j'),
0x75 => Some(b'k'),
0x76 => Some(b'q'),
0x77 => Some(b'v'),
0x78 => Some(b'w'),
0x79 => Some(b'x'),
0x7a => Some(b'y'),
0x7b => Some(b'z'),
_ => None,
};
if let Some(s) = sym_7 {
result.push(s);
bits -= 7;
accumulator &= BIT_MASKS[bits as usize];
continue;
}
}
if bits >= 8 {
let high_8 = (accumulator >> (bits - 8)) as u32 & 0xFF;
let sym_8 = match high_8 {
0xf8 => Some(b'&'),
0xf9 => Some(b'*'),
0xfa => Some(b','),
0xfb => Some(b';'),
0xfc => Some(b'X'),
0xfd => Some(b'Z'),
_ => None,
};
if let Some(s) = sym_8 {
result.push(s);
bits -= 8;
accumulator &= BIT_MASKS[bits as usize];
continue;
}
}
let mut decoded = false;
for code_len in 9u32..=30 {
if bits < code_len {
break;
}
let shift = bits - code_len;
let candidate = (accumulator >> shift) as u32;
let mask = (1u32 << code_len) - 1;
let candidate = candidate & mask;
if let Some(sym_opt) = HUFFMAN_DECODE_INDEX.get(&(candidate, code_len as u8)) {
match sym_opt {
None => {
return Err(H2Error::compression("invalid huffman code (EOS symbol)"));
}
Some(sym) => {
result.push(*sym);
bits = shift;
accumulator &= BIT_MASKS[bits as usize];
decoded = true;
}
}
break;
}
}
if !decoded {
if bits >= 30 {
return Err(H2Error::compression("invalid huffman code"));
}
break;
}
}
}
if bits >= 8 {
return Err(H2Error::compression("invalid huffman padding (overlong)"));
}
if bits > 0 && bits < 8 {
let mask = BIT_MASKS[bits as usize];
if accumulator != mask {
return Err(H2Error::compression(
"invalid Huffman padding (must be all 1s)",
));
}
}
String::from_utf8(result).map_err(|_| H2Error::compression("invalid UTF-8 in huffman"))
}
#[cfg(test)]
mod tests {
use super::super::error::ErrorCode;
use super::*;
fn assert_compression_error<T>(result: Result<T, H2Error>) {
match result {
Ok(_) => panic!("expected compression error"),
Err(err) => assert_eq!(err.code, ErrorCode::CompressionError),
}
}
#[test]
fn test_integer_encoding_small() {
let mut buf = BytesMut::new();
encode_integer(&mut buf, 10, 5, 0x00);
assert_eq!(buf.as_ref(), &[10]);
let mut src = buf.freeze();
let decoded = decode_integer(&mut src, 5).unwrap();
assert_eq!(decoded, 10);
}
#[test]
fn test_integer_encoding_large() {
let mut buf = BytesMut::new();
encode_integer(&mut buf, 1337, 5, 0x00);
assert_eq!(buf.as_ref(), &[31, 154, 10]);
let mut src = buf.freeze();
let decoded = decode_integer(&mut src, 5).unwrap();
assert_eq!(decoded, 1337);
}
#[test]
fn test_integer_decode_empty() {
let mut src = Bytes::new();
assert_compression_error(decode_integer(&mut src, 5));
}
#[test]
fn test_integer_decode_truncated() {
let mut src = Bytes::from_static(&[0x1f, 0x80]);
assert_compression_error(decode_integer(&mut src, 5));
}
#[test]
fn test_integer_decode_shift_overflow() {
let mut bytes = vec![0x1f];
bytes.extend_from_slice(&[0x80; 6]);
let mut src = Bytes::from(bytes);
assert_compression_error(decode_integer(&mut src, 5));
}
#[test]
fn test_string_encoding_literal() {
let mut buf = BytesMut::new();
encode_string(&mut buf, "hello", false);
let mut src = buf.freeze();
let decoded = decode_string(&mut src).unwrap();
assert_eq!(decoded, "hello");
}
#[test]
fn test_string_decode_length_exceeds_buffer() {
let mut src = Bytes::from_static(&[0x03, b'a', b'b']);
assert_compression_error(decode_string(&mut src));
}
#[test]
fn test_string_decode_invalid_utf8() {
let mut src = Bytes::from_static(&[0x01, 0xff]);
assert_compression_error(decode_string(&mut src));
}
#[test]
fn test_huffman_decode_invalid_padding() {
let mut src = Bytes::from_static(&[0x81, 0x00]);
assert_compression_error(decode_string(&mut src));
}
#[test]
fn test_indexed_header_zero_rejected() {
let mut decoder = Decoder::new();
let mut src = Bytes::from_static(&[0x80]); assert_compression_error(decoder.decode(&mut src));
}
#[test]
fn test_dynamic_table_size_update_exceeds_allowed() {
let mut decoder = Decoder::new();
decoder.set_allowed_table_size(1);
let mut buf = BytesMut::new();
encode_integer(&mut buf, 2, 5, 0x20);
let mut src = buf.freeze();
assert_compression_error(decoder.decode(&mut src));
}
#[test]
fn test_dynamic_table_size_update_without_header_is_accepted() {
let mut decoder = Decoder::new();
let mut buf = BytesMut::new();
encode_integer(&mut buf, 0, 5, 0x20);
let mut src = buf.freeze();
let headers = decoder.decode(&mut src).unwrap();
assert!(headers.is_empty());
assert_eq!(decoder.dynamic_table.max_size(), 0);
}
#[test]
fn test_multiple_size_updates_without_headers_apply_last_value() {
let mut decoder = Decoder::new();
let mut buf = BytesMut::new();
encode_integer(&mut buf, 1024, 5, 0x20);
encode_integer(&mut buf, 512, 5, 0x20);
let mut src = buf.freeze();
let headers = decoder.decode(&mut src).unwrap();
assert!(headers.is_empty());
assert_eq!(decoder.dynamic_table.max_size(), 512);
}
#[test]
fn test_dynamic_table_size_update_too_many() {
let mut decoder = Decoder::new();
let mut buf = BytesMut::new();
for _ in 0..17 {
encode_integer(&mut buf, 0, 5, 0x20);
}
let mut src = buf.freeze();
assert_compression_error(decoder.decode(&mut src));
}
#[test]
fn test_header_list_size_exceeded() {
let mut decoder = Decoder::new();
decoder.set_max_header_list_size(1);
let mut buf = BytesMut::new();
encode_integer(&mut buf, 0, 4, 0x00);
encode_string(&mut buf, "a", false);
encode_string(&mut buf, "b", false);
let mut src = buf.freeze();
assert_compression_error(decoder.decode(&mut src));
}
#[test]
fn test_decoder_caps_allowed_table_size() {
let decoder = Decoder::with_max_size(MAX_ALLOWED_TABLE_SIZE + 1);
assert_eq!(decoder.allowed_table_size, MAX_ALLOWED_TABLE_SIZE);
assert_eq!(decoder.dynamic_table.max_size(), MAX_ALLOWED_TABLE_SIZE);
}
#[test]
fn test_encoder_with_max_size_caps_to_allowed_maximum() {
let encoder = Encoder::with_max_size(MAX_ALLOWED_TABLE_SIZE + 1);
assert_eq!(encoder.dynamic_table_max_size(), MAX_ALLOWED_TABLE_SIZE);
}
#[test]
fn test_dynamic_table_set_max_size_caps_to_allowed_maximum() {
let mut table = DynamicTable::new();
table.set_max_size(MAX_ALLOWED_TABLE_SIZE + 1);
assert_eq!(table.max_size(), MAX_ALLOWED_TABLE_SIZE);
}
#[test]
fn test_set_allowed_table_size_caps() {
let mut decoder = Decoder::new();
decoder.set_allowed_table_size(MAX_ALLOWED_TABLE_SIZE + 1);
assert_eq!(decoder.allowed_table_size, MAX_ALLOWED_TABLE_SIZE);
}
#[test]
fn test_dynamic_table_insert() {
let mut table = DynamicTable::new();
table.insert(Header::new("custom-header", "custom-value"));
assert_eq!(
table.size(),
"custom-header".len() + "custom-value".len() + 32
);
assert!(table.get(1).is_some());
}
#[test]
fn test_dynamic_table_eviction() {
let mut table = DynamicTable::with_max_size(100);
table.insert(Header::new("header1", "value1")); table.insert(Header::new("header2", "value2"));
assert!(table.size() <= 100);
}
#[test]
fn test_encoder_decoder_roundtrip() {
let mut encoder = Encoder::new();
encoder.set_use_huffman(false);
let headers = vec![
Header::new(":method", "GET"),
Header::new(":path", "/"),
Header::new(":scheme", "https"),
Header::new(":authority", "example.com"),
Header::new("accept", "text/html"),
];
let mut encoded_block = BytesMut::new();
encoder.encode(&headers, &mut encoded_block);
let mut decoder = Decoder::new();
let mut src = encoded_block.freeze();
let decoded_headers = decoder.decode(&mut src).unwrap();
assert_eq!(decoded_headers.len(), headers.len());
for (orig, dec) in headers.iter().zip(decoded_headers.iter()) {
assert_eq!(orig.name, dec.name);
assert_eq!(orig.value, dec.value);
}
}
#[test]
fn test_static_table_indexed() {
let mut decoder = Decoder::new();
let mut src = Bytes::from_static(&[0x82]); let headers = decoder.decode(&mut src).unwrap();
assert_eq!(headers.len(), 1);
assert_eq!(headers[0].name, ":method");
assert_eq!(headers[0].value, "GET");
}
#[test]
fn test_huffman_encode_decode_roundtrip() {
let inputs = [
"www.example.com",
"no-cache",
"custom-key",
"custom-value",
"",
"a",
"Hello, World!",
];
for &input in &inputs {
let encoded = encode_huffman(input.as_bytes());
let encoded_bytes = Bytes::from(encoded);
let decoded = decode_huffman(&encoded_bytes).unwrap();
assert_eq!(decoded, input, "roundtrip failed for {input:?}");
}
}
#[test]
fn test_huffman_encoding_is_smaller() {
let input = b"www.example.com";
let encoded = encode_huffman(input);
assert!(
encoded.len() < input.len(),
"huffman should compress ASCII text: {} >= {}",
encoded.len(),
input.len()
);
}
#[test]
fn test_string_encoding_huffman_roundtrip() {
let mut buf = BytesMut::new();
encode_string(&mut buf, "hello", true);
assert_ne!(buf[0] & 0x80, 0, "huffman flag should be set");
let mut src = buf.freeze();
let decoded = decode_string(&mut src).unwrap();
assert_eq!(decoded, "hello");
}
#[test]
fn test_encoder_decoder_roundtrip_with_huffman() {
let mut encoder = Encoder::new();
encoder.set_use_huffman(true);
let headers = vec![
Header::new(":method", "GET"),
Header::new(":path", "/index.html"),
Header::new(":scheme", "https"),
Header::new(":authority", "www.example.com"),
Header::new("accept-encoding", "gzip, deflate"),
];
let mut encoded_block = BytesMut::new();
encoder.encode(&headers, &mut encoded_block);
let mut decoder = Decoder::new();
let mut src = encoded_block.freeze();
let decoded_headers = decoder.decode(&mut src).unwrap();
assert_eq!(decoded_headers.len(), headers.len());
for (orig, dec) in headers.iter().zip(decoded_headers.iter()) {
assert_eq!(orig.name, dec.name, "name mismatch for {:?}", orig.name);
assert_eq!(orig.value, dec.value, "value mismatch for {:?}", orig.name);
}
}
#[test]
fn test_rfc7541_c1_integer_representation() {
let mut buf = BytesMut::new();
encode_integer(&mut buf, 10, 5, 0x00);
assert_eq!(&buf[..], &[0x0a]);
buf.clear();
encode_integer(&mut buf, 1337, 5, 0x00);
assert_eq!(&buf[..], &[0x1f, 0x9a, 0x0a]);
buf.clear();
encode_integer(&mut buf, 42, 8, 0x00);
assert_eq!(&buf[..], &[0x2a]);
}
#[test]
fn test_rfc7541_integer_decode_roundtrip() {
for &(value, prefix_bits) in &[
(0_usize, 5_u8),
(1, 5),
(30, 5),
(31, 5),
(32, 5),
(127, 7),
(128, 7),
(255, 8),
(256, 8),
(1337, 5),
(65535, 8),
] {
let mut buf = BytesMut::new();
encode_integer(&mut buf, value, prefix_bits, 0x00);
let mut src = buf.freeze();
let decoded = decode_integer(&mut src, prefix_bits).unwrap();
assert_eq!(
decoded, value,
"roundtrip failed for {value} with {prefix_bits}-bit prefix"
);
}
}
#[test]
fn test_rfc7541_c2_header_field_indexed() {
let mut decoder = Decoder::new();
let mut src = Bytes::from_static(&[0x82]);
let headers = decoder.decode(&mut src).unwrap();
assert_eq!(headers.len(), 1);
assert_eq!(headers[0].name, ":method");
assert_eq!(headers[0].value, "GET");
}
#[test]
fn test_rfc7541_c3_request_without_huffman() {
let wire: &[u8] = &[
0x82, 0x86, 0x84, 0x41, 0x0f, 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',
b'm',
];
let mut decoder = Decoder::new();
let mut src = Bytes::copy_from_slice(wire);
let headers = decoder.decode(&mut src).unwrap();
assert_eq!(headers.len(), 4);
assert_eq!(headers[0].name, ":method");
assert_eq!(headers[0].value, "GET");
assert_eq!(headers[1].name, ":scheme");
assert_eq!(headers[1].value, "http");
assert_eq!(headers[2].name, ":path");
assert_eq!(headers[2].value, "/");
assert_eq!(headers[3].name, ":authority");
assert_eq!(headers[3].value, "www.example.com");
}
#[test]
fn test_rfc7541_c4_request_with_huffman() {
let mut enc = Encoder::new();
enc.set_use_huffman(true);
let headers = vec![
Header::new(":method", "GET"),
Header::new(":scheme", "http"),
Header::new(":path", "/"),
Header::new(":authority", "www.example.com"),
];
let mut buf = BytesMut::new();
enc.encode(&headers, &mut buf);
let mut dec = Decoder::new();
let mut src = buf.freeze();
let headers_out = dec.decode(&mut src).unwrap();
assert_eq!(headers_out.len(), 4);
assert_eq!(headers_out[3].value, "www.example.com");
}
#[test]
fn test_rfc7541_c5_response_without_huffman() {
let mut enc = Encoder::new();
enc.set_use_huffman(false);
let headers = vec![
Header::new(":status", "302"),
Header::new("cache-control", "private"),
Header::new("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
Header::new("location", "https://www.example.com"),
];
let mut buf = BytesMut::new();
enc.encode(&headers, &mut buf);
let mut dec = Decoder::new();
let mut src = buf.freeze();
let headers_out = dec.decode(&mut src).unwrap();
assert_eq!(headers_out.len(), 4);
assert_eq!(headers_out[0].name, ":status");
assert_eq!(headers_out[0].value, "302");
assert_eq!(headers_out[3].name, "location");
assert_eq!(headers_out[3].value, "https://www.example.com");
}
#[test]
fn test_rfc7541_c5_1_first_response_exact_wire_without_huffman() {
let headers = vec![
Header::new(":status", "302"),
Header::new("cache-control", "private"),
Header::new("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
Header::new("location", "https://www.example.com"),
];
let expected_wire: &[u8] = &[
0x48, 0x03, 0x33, 0x30, 0x32, 0x58, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65,
0x61, 0x1d, 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x31, 0x20, 0x4f, 0x63, 0x74, 0x20,
0x32, 0x30, 0x31, 0x33, 0x20, 0x32, 0x30, 0x3a, 0x31, 0x33, 0x3a, 0x32, 0x31, 0x20,
0x47, 0x4d, 0x54, 0x6e, 0x17, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77,
0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
];
let mut decoder = Decoder::new();
let mut src = Bytes::copy_from_slice(expected_wire);
let decoded = decoder.decode(&mut src).expect("RFC 7541 C.5.1 decode");
assert_eq!(decoded, headers);
let mut encoder = Encoder::new();
encoder.set_use_huffman(false);
let mut encoded = BytesMut::new();
encoder.encode(&headers, &mut encoded);
assert_eq!(
encoded.as_ref(),
expected_wire,
"RFC 7541 C.5.1 wire image must match the specification exactly"
);
}
#[test]
fn test_rfc7541_c6_1_first_response_exact_wire_with_huffman() {
let headers = vec![
Header::new(":status", "302"),
Header::new("cache-control", "private"),
Header::new("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
Header::new("location", "https://www.example.com"),
];
let expected_wire: &[u8] = &[
0x48, 0x82, 0x64, 0x02, 0x58, 0x85, 0xae, 0xc3, 0x77, 0x1a, 0x4b, 0x61, 0x96, 0xd0,
0x7a, 0xbe, 0x94, 0x10, 0x54, 0xd4, 0x44, 0xa8, 0x20, 0x05, 0x95, 0x04, 0x0b, 0x81,
0x66, 0xe0, 0x82, 0xa6, 0x2d, 0x1b, 0xff, 0x6e, 0x91, 0x9d, 0x29, 0xad, 0x17, 0x18,
0x63, 0xc7, 0x8f, 0x0b, 0x97, 0xc8, 0xe9, 0xae, 0x82, 0xae, 0x43, 0xd3,
];
let mut decoder = Decoder::new();
let mut src = Bytes::copy_from_slice(expected_wire);
let decoded = decoder.decode(&mut src).expect("RFC 7541 C.6.1 decode");
assert_eq!(decoded, headers);
let mut encoder = Encoder::new();
encoder.set_use_huffman(true);
let mut encoded = BytesMut::new();
encoder.encode(&headers, &mut encoded);
assert_eq!(
encoded.as_ref(),
expected_wire,
"RFC 7541 C.6.1 wire image must match the specification exactly"
);
}
#[test]
fn test_rfc7541_huffman_decode_www_example_com() {
let huffman_encoded: &[u8] = &[
0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab, 0x90, 0xf4, 0xff,
];
let decoded = decode_huffman(&Bytes::copy_from_slice(huffman_encoded)).unwrap();
assert_eq!(decoded, "www.example.com");
}
#[test]
fn test_dynamic_table_empty() {
let table = DynamicTable::new();
assert_eq!(table.size(), 0);
assert!(table.get(1).is_none());
assert!(table.get(0).is_none());
assert!(table.get(100).is_none());
}
#[test]
fn test_dynamic_table_single_entry() {
let mut table = DynamicTable::new();
table.insert(Header::new("x-custom", "value"));
let entry = table.get(1).unwrap();
assert_eq!(entry.name, "x-custom");
assert_eq!(entry.value, "value");
assert!(table.get(2).is_none());
}
#[test]
fn test_dynamic_table_fifo_order() {
let mut table = DynamicTable::new();
table.insert(Header::new("first", "1"));
table.insert(Header::new("second", "2"));
table.insert(Header::new("third", "3"));
assert_eq!(table.get(1).unwrap().name, "third");
assert_eq!(table.get(2).unwrap().name, "second");
assert_eq!(table.get(3).unwrap().name, "first");
}
#[test]
fn test_dynamic_table_size_calculation() {
let mut table = DynamicTable::new();
let header = Header::new("custom", "value"); table.insert(header);
assert_eq!(table.size(), 43);
table.insert(Header::new("a", "b")); assert_eq!(table.size(), 43 + 34);
}
#[test]
fn test_dynamic_table_max_size_zero() {
let mut table = DynamicTable::with_max_size(0);
table.insert(Header::new("header", "value"));
assert_eq!(table.size(), 0);
assert!(table.get(1).is_none());
}
#[test]
fn test_dynamic_table_exact_fit() {
let mut table = DynamicTable::with_max_size(43);
table.insert(Header::new("custom", "value"));
assert_eq!(table.size(), 43);
assert!(table.get(1).is_some());
table.insert(Header::new("newkey", "newva")); assert_eq!(table.size(), 43);
assert_eq!(table.get(1).unwrap().name, "newkey");
assert!(table.get(2).is_none()); }
#[test]
fn test_dynamic_table_cascade_eviction() {
let mut table = DynamicTable::with_max_size(100);
table.insert(Header::new("a", "1"));
table.insert(Header::new("b", "2"));
table.insert(Header::new("c", "3"));
assert_eq!(table.size(), 68);
assert!(table.size() <= 100);
}
#[test]
fn test_dynamic_table_set_max_size() {
let mut table = DynamicTable::new();
table.insert(Header::new("header1", "value1")); table.insert(Header::new("header2", "value2"));
let initial_size = table.size();
assert_eq!(initial_size, 90);
table.set_max_size(50);
assert!(table.size() <= 50);
}
#[test]
fn test_dynamic_table_resize_to_zero() {
let mut table = DynamicTable::new();
table.insert(Header::new("key", "val"));
assert!(table.size() > 0);
table.set_max_size(0);
assert_eq!(table.size(), 0);
assert!(table.get(1).is_none());
}
#[test]
fn test_encoder_dynamic_table_reuse() {
let mut encoder = Encoder::new();
encoder.set_use_huffman(false);
let headers1 = vec![Header::new("x-custom", "value1")];
let mut buf1 = BytesMut::new();
encoder.encode(&headers1, &mut buf1);
let headers2 = vec![Header::new("x-custom", "value2")];
let mut buf2 = BytesMut::new();
encoder.encode(&headers2, &mut buf2);
let mut decoder = Decoder::new();
let decoded1 = decoder.decode(&mut buf1.freeze()).unwrap();
let decoded2 = decoder.decode(&mut buf2.freeze()).unwrap();
assert_eq!(decoded1[0].name, "x-custom");
assert_eq!(decoded2[0].name, "x-custom");
}
#[test]
fn test_decoder_shared_state_across_blocks() {
let mut enc = Encoder::new();
enc.set_use_huffman(false);
let mut dec = Decoder::new();
let headers1 = vec![Header::new("x-custom", "initial")];
let mut buf1 = BytesMut::new();
enc.encode(&headers1, &mut buf1);
dec.decode(&mut buf1.freeze()).unwrap();
let headers2 = vec![Header::new("x-custom", "updated")];
let mut buf2 = BytesMut::new();
enc.encode(&headers2, &mut buf2);
let headers_out = dec.decode(&mut buf2.freeze()).unwrap();
assert_eq!(headers_out[0].value, "updated");
}
#[test]
fn test_decode_empty_input() {
let mut decoder = Decoder::new();
let mut src = Bytes::new();
let headers = decoder.decode(&mut src).unwrap();
assert!(headers.is_empty());
}
#[test]
fn test_decode_invalid_indexed_zero() {
let mut decoder = Decoder::new();
let mut src = Bytes::from_static(&[0x80]); let result = decoder.decode(&mut src);
assert!(result.is_err());
}
#[test]
fn test_decode_invalid_index_too_large() {
let mut decoder = Decoder::new();
let mut src = Bytes::from_static(&[0xff, 0xff, 0xff, 0x7f]); let result = decoder.decode(&mut src);
assert!(result.is_err());
}
#[test]
fn test_decode_truncated_integer() {
let mut decoder = Decoder::new();
let mut src = Bytes::from_static(&[0x1f]); let result = decoder.decode(&mut src);
assert!(result.is_err());
}
#[test]
fn test_decode_truncated_string() {
let mut decoder = Decoder::new();
let mut src = Bytes::from_static(&[
0x40, 0x0a, b'a', b'b', b'c', ]);
let result = decoder.decode(&mut src);
assert!(result.is_err());
}
#[test]
fn test_decode_huffman_invalid_eos() {
let invalid_huffman: &[u8] = &[0xff, 0xff, 0xff, 0xff]; let result = decode_huffman(&Bytes::copy_from_slice(invalid_huffman));
assert_compression_error(result);
}
#[test]
fn test_decode_integer_overflow_protection() {
let mut src =
Bytes::from_static(&[0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01]);
let result = decode_integer(&mut src, 7);
assert!(result.is_err());
}
#[test]
fn test_decode_literal_with_empty_name() {
let mut enc = Encoder::new();
enc.set_use_huffman(false);
let headers = vec![Header::new("", "value")];
let mut buf = BytesMut::new();
enc.encode(&headers, &mut buf);
let mut dec = Decoder::new();
let mut src = buf.freeze();
let result = dec.decode(&mut src);
assert_compression_error(result);
}
#[test]
fn test_decode_literal_with_empty_value() {
let mut enc = Encoder::new();
enc.set_use_huffman(false);
let headers = vec![Header::new("x-empty", "")];
let mut buf = BytesMut::new();
enc.encode(&headers, &mut buf);
let mut dec = Decoder::new();
let headers_out = dec.decode(&mut buf.freeze()).unwrap();
assert_eq!(headers_out[0].name, "x-empty");
assert_eq!(headers_out[0].value, "");
}
#[test]
fn test_static_table_all_entries_accessible() {
for idx in 1..=61usize {
let entry = get_static(idx);
assert!(entry.is_some(), "static table entry {idx} should exist");
}
assert!(get_static(62).is_none());
assert!(get_static(0).is_none());
}
#[test]
fn test_static_table_known_entries() {
let method_get = get_static(2).unwrap();
assert_eq!(method_get.0, ":method");
assert_eq!(method_get.1, "GET");
let method_post = get_static(3).unwrap();
assert_eq!(method_post.0, ":method");
assert_eq!(method_post.1, "POST");
let status_200 = get_static(8).unwrap();
assert_eq!(status_200.0, ":status");
assert_eq!(status_200.1, "200");
let status_404 = get_static(13).unwrap();
assert_eq!(status_404.0, ":status");
assert_eq!(status_404.1, "404");
}
#[test]
fn test_huffman_all_ascii_printable() {
let mut input = String::new();
for c in 32u8..=126 {
input.push(c as char);
}
let encoded = encode_huffman(input.as_bytes());
let decoded = decode_huffman(&Bytes::from(encoded)).unwrap();
assert_eq!(decoded, input);
}
#[test]
fn test_huffman_empty_string() {
let encoded = encode_huffman(b"");
assert!(encoded.is_empty());
let decoded = decode_huffman(&Bytes::new()).unwrap();
assert_eq!(decoded, "");
}
#[test]
fn test_sensitive_header_encoding() {
let mut enc = Encoder::new();
let mut dec = Decoder::new();
let headers = vec![
Header::new(":method", "GET"),
Header::new("authorization", "Bearer secret123"),
];
let mut buf = BytesMut::new();
enc.encode(&headers, &mut buf);
let headers_out = dec.decode(&mut buf.freeze()).unwrap();
assert_eq!(headers_out.len(), 2);
assert_eq!(headers_out[1].name, "authorization");
assert_eq!(headers_out[1].value, "Bearer secret123");
}
#[test]
fn test_large_header_value() {
let mut enc = Encoder::new();
enc.set_use_huffman(false);
let large_value: String = "x".repeat(4096);
let headers = vec![Header::new("x-large", &large_value)];
let mut buf = BytesMut::new();
enc.encode(&headers, &mut buf);
let mut dec = Decoder::new();
let headers_out = dec.decode(&mut buf.freeze()).unwrap();
assert_eq!(headers_out[0].value, large_value);
}
#[test]
fn test_many_headers() {
let mut enc = Encoder::new();
enc.set_use_huffman(true);
let headers: Vec<Header> = (0..100)
.map(|i| Header::new(format!("x-header-{i}"), format!("value-{i}")))
.collect();
let mut buf = BytesMut::new();
enc.encode(&headers, &mut buf);
let mut dec = Decoder::new();
let headers_out = dec.decode(&mut buf.freeze()).unwrap();
assert_eq!(headers_out.len(), 100);
for (i, header) in headers_out.iter().enumerate() {
assert_eq!(header.name, format!("x-header-{i}"));
assert_eq!(header.value, format!("value-{i}"));
}
}
#[test]
fn test_deterministic_encoding() {
let mut encoder1 = Encoder::new();
let mut encoder2 = Encoder::new();
let headers = vec![
Header::new(":method", "GET"),
Header::new(":path", "/api/test"),
Header::new("content-type", "application/json"),
];
let mut buf1 = BytesMut::new();
let mut buf2 = BytesMut::new();
encoder1.encode(&headers, &mut buf1);
encoder2.encode(&headers, &mut buf2);
assert_eq!(buf1, buf2, "encoding should be deterministic");
}
#[test]
fn stress_test_hpack_integer_malformed() {
for shift in 0..=40u8 {
let mut data = vec![0x7f_u8]; data.extend(std::iter::repeat_n(0xff, shift as usize));
data.push(0x00); let mut src = Bytes::from(data);
let _ = decode_integer(&mut src, 7);
}
for seed in 0..1000u16 {
let len = ((seed % 10) + 1) as usize;
let mut data = Vec::with_capacity(len);
for i in 0..len {
data.push(((seed.wrapping_mul(31).wrapping_add(i as u16)) & 0xff) as u8);
}
if !data.is_empty() {
data[0] |= 0x1f;
}
let mut src = Bytes::from(data);
let _ = decode_integer(&mut src, 5);
}
}
#[test]
fn stress_test_huffman_random_bytes() {
for seed in 0..2000u32 {
let len = ((seed % 200) + 1) as usize;
let mut data = Vec::with_capacity(len);
for i in 0..len {
data.push(((seed.wrapping_mul(97).wrapping_add(i as u32)) & 0xff) as u8);
}
let _ = decode_huffman(&Bytes::from(data));
}
}
#[test]
fn stress_test_dynamic_table_churn() {
let mut table = DynamicTable::new();
for i in 0..5000u32 {
if i % 3 == 0 {
table.set_max_size(0);
} else if i % 3 == 1 {
table.set_max_size(4096);
}
table.insert(Header::new(format!("x-churn-{i}"), format!("value-{i}")));
assert!(table.size() <= 4096);
}
}
#[test]
fn stress_test_decoder_malformed_blocks() {
for seed in 0..1000u32 {
let len = ((seed % 100) + 1) as usize;
let mut data = Vec::with_capacity(len);
for i in 0..len {
data.push(((seed.wrapping_mul(53).wrapping_add(i as u32 * 7)) & 0xff) as u8);
}
let mut decoder = Decoder::new();
let mut src = Bytes::from(data);
let _ = decoder.decode(&mut src);
}
}
#[test]
fn test_huffman_all_single_bytes() {
for byte in 0..=255u8 {
let input = [byte];
let encoded = encode_huffman(&input);
let result = decode_huffman(&Bytes::from(encoded));
if std::str::from_utf8(&input).is_ok() {
let decoded = result.unwrap_or_else(|e| {
panic!("decode failed for valid UTF-8 byte 0x{byte:02x}: {e:?}")
});
assert_eq!(
decoded.as_bytes(),
&input,
"roundtrip failed for byte 0x{byte:02x}"
);
} else {
let _ = result;
}
}
}
#[test]
fn test_huffman_long_code_symbols() {
let mut input = Vec::new();
for b in 0..=31u8 {
input.push(b);
}
let encoded = encode_huffman(&input);
let decoded = decode_huffman(&Bytes::from(encoded)).unwrap();
assert_eq!(decoded.as_bytes(), &input[..]);
}
#[test]
fn test_integer_max_valid_value() {
let value = 1_000_000_usize;
let mut buf = BytesMut::new();
encode_integer(&mut buf, value, 5, 0x00);
let mut src = buf.freeze();
let decoded = decode_integer(&mut src, 5).unwrap();
assert_eq!(decoded, value);
}
#[test]
fn test_integer_all_prefix_sizes() {
for prefix in [5_u8, 6, 7, 8] {
for &value in &[0_usize, 1, 30, 31, 127, 128, 255, 256, 65535] {
let mut buf = BytesMut::new();
encode_integer(&mut buf, value, prefix, 0x00);
let mut src = buf.freeze();
let decoded = decode_integer(&mut src, prefix).unwrap();
assert_eq!(decoded, value, "prefix={prefix}, value={value}");
}
}
}
#[test]
fn test_encoder_emits_size_update_on_wire() {
let mut encoder = Encoder::new();
encoder.set_use_huffman(false);
encoder.set_max_table_size(256);
let headers = vec![Header::new(":method", "GET")];
let mut buf = BytesMut::new();
encoder.encode(&headers, &mut buf);
assert_eq!(
buf[0] & 0xe0,
0x20,
"first byte should be dynamic table size update prefix"
);
let mut decoder = Decoder::new();
decoder.set_allowed_table_size(256);
let mut src = buf.freeze();
let decoded_headers = decoder.decode(&mut src).unwrap();
assert_eq!(decoded_headers.len(), 1);
assert_eq!(decoded_headers[0].name, ":method");
assert_eq!(decoded_headers[0].value, "GET");
}
#[test]
fn test_encoder_size_update_not_repeated() {
let mut encoder = Encoder::new();
encoder.set_use_huffman(false);
encoder.set_max_table_size(256);
let mut buf1 = BytesMut::new();
encoder.encode(&[Header::new(":method", "GET")], &mut buf1);
assert_eq!(buf1[0] & 0xe0, 0x20, "first block should have size update");
let mut buf2 = BytesMut::new();
encoder.encode(&[Header::new(":method", "POST")], &mut buf2);
assert_ne!(
buf2[0] & 0xe0,
0x20,
"second block should not repeat size update"
);
}
#[test]
fn test_encoder_size_update_roundtrip_full() {
let mut encoder = Encoder::new();
let mut decoder = Decoder::new();
encoder.set_use_huffman(false);
let headers1 = vec![Header::new("x-test", "value1")];
let mut buf1 = BytesMut::new();
encoder.encode(&headers1, &mut buf1);
let dec1 = decoder.decode(&mut buf1.freeze()).unwrap();
assert_eq!(dec1[0].value, "value1");
encoder.set_max_table_size(128);
decoder.set_allowed_table_size(128);
let headers2 = vec![Header::new("x-test", "value2")];
let mut buf2 = BytesMut::new();
encoder.encode(&headers2, &mut buf2);
let dec2 = decoder.decode(&mut buf2.freeze()).unwrap();
assert_eq!(dec2[0].value, "value2");
}
#[test]
fn test_encoder_emits_min_then_final_size_update_after_shrink_then_grow() {
let mut encoder = Encoder::new();
encoder.set_use_huffman(false);
encoder.set_max_table_size(128);
encoder.set_max_table_size(256);
let headers = vec![Header::new(":method", "GET")];
let mut buf = BytesMut::new();
encoder.encode(&headers, &mut buf);
assert_eq!(
buf[0] & 0xe0,
0x20,
"first instruction should be size update"
);
let mut src = buf.freeze();
let first_update = decode_integer(&mut src, 5).unwrap();
assert_eq!(first_update, 128);
assert_eq!(
src[0] & 0xe0,
0x20,
"second instruction should be size update"
);
let second_update = decode_integer(&mut src, 5).unwrap();
assert_eq!(second_update, 256);
let mut decoder = Decoder::new();
decoder.set_allowed_table_size(256);
let decoded_headers = decoder.decode(&mut src).unwrap();
assert_eq!(decoded_headers, headers);
}
#[test]
fn test_encoder_shrink_to_zero_does_not_reuse_dynamic_entries() {
let mut encoder = Encoder::new();
let mut decoder = Decoder::new();
encoder.set_use_huffman(false);
let headers = vec![
Header::new("cache-control", "gzip, deflate"),
Header::new("cache-control", "gzip, deflate"),
];
let mut initial = BytesMut::new();
encoder.encode(&headers, &mut initial);
let decoded_initial = decoder.decode(&mut initial.freeze()).unwrap();
assert_eq!(decoded_initial, headers);
assert!(encoder.dynamic_table_size() > 0);
assert!(decoder.dynamic_table_size() > 0);
encoder.set_max_table_size(0);
decoder.set_allowed_table_size(0);
let mut resized = BytesMut::new();
encoder.encode(&headers, &mut resized);
let decoded_resized = decoder.decode(&mut resized.freeze()).unwrap();
assert_eq!(decoded_resized, headers);
assert_eq!(encoder.dynamic_table_size(), 0);
assert_eq!(decoder.dynamic_table_size(), 0);
}
#[test]
fn test_integer_decode_checked_mul_overflow() {
let mut data = vec![0x1f_u8];
data.extend_from_slice(&[0xff, 0xff, 0xff, 0xff]);
data.push(0x7f); let mut src = Bytes::from(data);
let _ = decode_integer(&mut src, 5);
}
#[test]
fn test_size_update_before_first_header_accepted() {
let mut decoder = Decoder::new();
let mut buf = BytesMut::new();
encode_integer(&mut buf, 2048, 5, 0x20);
buf.put_u8(0x82);
let mut src = buf.freeze();
let headers = decoder.decode(&mut src).unwrap();
assert_eq!(headers.len(), 1);
assert_eq!(headers[0].name, ":method");
assert_eq!(headers[0].value, "GET");
}
#[test]
fn test_size_update_after_first_header_rejected() {
let mut decoder = Decoder::new();
let mut buf = BytesMut::new();
buf.put_u8(0x82);
encode_integer(&mut buf, 2048, 5, 0x20);
buf.put_u8(0x84);
let mut src = buf.freeze();
let result = decoder.decode(&mut src);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.code, ErrorCode::CompressionError);
}
#[test]
fn test_multiple_size_updates_then_header_ok() {
let mut decoder = Decoder::new();
let mut buf = BytesMut::new();
encode_integer(&mut buf, 1024, 5, 0x20);
encode_integer(&mut buf, 2048, 5, 0x20);
buf.put_u8(0x82);
let mut src = buf.freeze();
let headers = decoder.decode(&mut src).unwrap();
assert_eq!(headers.len(), 1);
assert_eq!(headers[0].name, ":method");
}
#[test]
fn test_size_updates_apply_intermediate_eviction_before_dynamic_lookup() {
let mut encoder = Encoder::new();
encoder.set_use_huffman(false);
let mut decoder = Decoder::new();
let seed_headers = vec![Header::new("x-evict-me", "value")];
let mut seeded = BytesMut::new();
encoder.encode(&seed_headers, &mut seeded);
let decoded = decoder.decode(&mut seeded.freeze()).unwrap();
assert_eq!(decoded, seed_headers);
assert!(decoder.dynamic_table_size() > 0);
let mut buf = BytesMut::new();
encode_integer(&mut buf, 0, 5, 0x20);
encode_integer(&mut buf, 256, 5, 0x20);
encode_integer(&mut buf, STATIC_TABLE.len() + 1, 7, 0x80);
let mut src = buf.freeze();
assert_compression_error(decoder.decode(&mut src));
assert_eq!(decoder.dynamic_table_size(), 0);
assert_eq!(decoder.dynamic_table_max_size(), 256);
}
#[test]
fn test_string_length_exceeds_maximum() {
let mut buf = BytesMut::new();
encode_integer(&mut buf, 300_000, 7, 0x00);
let mut src = buf.freeze();
let result = decode_string(&mut src);
assert!(result.is_err());
}
#[test]
fn test_string_length_at_maximum_boundary() {
let data = vec![b'x'; MAX_STRING_LENGTH];
let mut buf = BytesMut::new();
encode_integer(&mut buf, MAX_STRING_LENGTH, 7, 0x00);
buf.extend_from_slice(&data);
let mut src = buf.freeze();
let result = decode_string(&mut src);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), MAX_STRING_LENGTH);
}
#[test]
fn header_debug_clone_eq() {
let h = Header::new("content-type", "application/json");
let dbg = format!("{h:?}");
assert!(dbg.contains("content-type"));
assert!(dbg.contains("application/json"));
let h2 = h.clone();
assert_eq!(h, h2);
let h3 = Header::new("accept", "*/*");
assert_ne!(h, h3);
}
#[test]
fn static_index_exact_matches_linear_scan() {
for (i, &(name, value)) in STATIC_TABLE.iter().enumerate() {
let expected = i + 1;
assert_eq!(
find_static(name, value),
Some(expected),
"exact match failed for ({name}, {value}) at index {expected}"
);
}
assert_eq!(find_static("x-custom", "foo"), None);
assert_eq!(find_static(":method", "DELETE"), None);
}
#[test]
fn static_name_index_matches_first_occurrence() {
assert_eq!(find_static_name(":method"), Some(2)); assert_eq!(find_static_name(":path"), Some(4)); assert_eq!(find_static_name(":status"), Some(8)); assert_eq!(find_static_name(":scheme"), Some(6)); assert_eq!(find_static_name("content-type"), Some(31));
assert_eq!(find_static_name("x-nonexistent"), None);
}
#[test]
fn test_rfc7541_c2_1_literal_incremental_new_name() {
let mut decoder = Decoder::new();
let encoded = &[
0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x0d, 0x63,
0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72,
];
let mut bytes = Bytes::copy_from_slice(encoded);
let headers = decoder
.decode(&mut bytes)
.expect("C.2.1 decode should work");
assert_eq!(headers.len(), 1);
assert_eq!(headers[0].name, "custom-key");
assert_eq!(headers[0].value, "custom-header");
}
#[test]
fn test_rfc7541_c6_indexed_header_field() {
let mut decoder = Decoder::new();
let encoded = &[0x82];
let mut bytes = Bytes::copy_from_slice(encoded);
let headers = decoder.decode(&mut bytes).expect("C.6 decode should work");
assert_eq!(headers.len(), 1);
assert_eq!(headers[0].name, ":method");
assert_eq!(headers[0].value, "GET");
}
#[test]
fn test_rfc7541_c3_multiple_requests() {
let mut decoder = Decoder::new();
let encoded_1 = &[
0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x0d, 0x63,
0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72,
];
let mut bytes = Bytes::copy_from_slice(encoded_1);
let headers_1 = decoder
.decode(&mut bytes)
.expect("C.3.1 decode should work");
assert_eq!(headers_1.len(), 1);
assert_eq!(headers_1[0].name, "custom-key");
assert_eq!(headers_1[0].value, "custom-header");
let encoded_2 = &[0xbe];
let mut bytes = Bytes::copy_from_slice(encoded_2);
let headers_2 = decoder
.decode(&mut bytes)
.expect("C.3.2 decode should work");
assert_eq!(headers_2.len(), 1);
assert_eq!(headers_2[0].name, "custom-key");
assert_eq!(headers_2[0].value, "custom-header");
}
#[test]
fn test_rfc7541_c4_request_sequence() {
let mut decoder = Decoder::new();
let encoded_1 = &[
0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x0d, 0x63,
0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72,
];
let mut bytes = Bytes::copy_from_slice(encoded_1);
let headers_1 = decoder
.decode(&mut bytes)
.expect("C.4.1 decode should work");
assert_eq!(headers_1.len(), 1);
assert_eq!(headers_1[0].name, "custom-key");
assert_eq!(headers_1[0].value, "custom-header");
let encoded_2 = &[
0x40, 0x0a, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x6b, 0x65, 0x79, 0x0c, 0x63,
0x75, 0x73, 0x74, 0x6f, 0x6d, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65,
];
let mut bytes = Bytes::copy_from_slice(encoded_2);
let headers_2 = decoder
.decode(&mut bytes)
.expect("C.4.2 decode should work");
assert_eq!(headers_2.len(), 1);
assert_eq!(headers_2[0].name, "custom-key");
assert_eq!(headers_2[0].value, "custom-value");
let encoded_3 = &[0xbf, 0xbe];
let mut bytes = Bytes::copy_from_slice(encoded_3);
let headers_3 = decoder
.decode(&mut bytes)
.expect("C.4.3 decode should work");
assert_eq!(headers_3.len(), 2);
assert_eq!(headers_3[0].name, "custom-key");
assert_eq!(headers_3[0].value, "custom-header");
assert_eq!(headers_3[1].name, "custom-key");
assert_eq!(headers_3[1].value, "custom-value");
}
#[test]
fn test_rfc7541_round_trip_basic() {
let mut encoder = Encoder::new();
let mut decoder = Decoder::new();
encoder.set_use_huffman(false);
let headers = vec![
Header::new(":method", "GET"),
Header::new(":path", "/"),
Header::new("custom-key", "custom-value"),
];
let mut encoded = BytesMut::new();
encoder.encode(&headers, &mut encoded);
let mut src = encoded.freeze();
let decoded = decoder
.decode(&mut src)
.expect("Round-trip decode should work");
assert_eq!(decoded.len(), 3);
assert_eq!(decoded[0].name, ":method");
assert_eq!(decoded[0].value, "GET");
assert_eq!(decoded[1].name, ":path");
assert_eq!(decoded[1].value, "/");
assert_eq!(decoded[2].name, "custom-key");
assert_eq!(decoded[2].value, "custom-value");
}
#[test]
fn conformance_static_table_all_61_entries_encode_correctly() {
let mut encoder = Encoder::new();
let mut decoder = Decoder::new();
encoder.set_use_huffman(false);
for (i, &(name, value)) in STATIC_TABLE.iter().enumerate() {
let expected_index = i + 1;
let header = Header::new(name, value);
let mut encoded = BytesMut::new();
encoder.encode(std::slice::from_ref(&header), &mut encoded);
let expected_encoded = 0x80 | expected_index;
if expected_index <= 127 {
assert_eq!(
encoded[0], expected_encoded as u8,
"Static table entry {} ({}, {}) should encode as indexed 0x{:02x}",
expected_index, name, value, expected_encoded
);
} else {
assert_eq!(
encoded[0], 0xff,
"Static table entry {} should start with 0xff for multi-byte encoding",
expected_index
);
}
let mut src = encoded.freeze();
let decoded = decoder.decode(&mut src).expect(&format!(
"Failed to decode static table entry {}",
expected_index
));
assert_eq!(decoded.len(), 1);
assert_eq!(decoded[0].name, name);
assert_eq!(decoded[0].value, value);
}
}
#[test]
fn conformance_referenced_literal_vs_indexed_encoding() {
let mut encoder = Encoder::new();
let mut decoder = Decoder::new();
encoder.set_use_huffman(false);
let exact_match = Header::new(":method", "GET"); let mut encoded = BytesMut::new();
encoder.encode(&[exact_match], &mut encoded);
assert_eq!(
encoded[0], 0x82,
"Exact static table match should use indexed encoding"
);
let name_match = Header::new(":method", "DELETE"); let mut encoded = BytesMut::new();
encoder.encode(&[name_match], &mut encoded);
assert_eq!(
encoded[0], 0x42,
"Static table name match with different value should use literal with name reference"
);
let mut src = encoded.freeze();
let _ = src.split_to(1);
let value_str = decode_string(&mut src).expect("Should decode DELETE value");
assert_eq!(value_str, "DELETE");
let new_header = Header::new("x-custom", "test-value");
let mut encoded = BytesMut::new();
encoder.encode(&[new_header], &mut encoded);
assert_eq!(
encoded[0], 0x40,
"New header should use literal without name reference"
);
let mut src = encoded.freeze();
let decoded = decoder
.decode(&mut src)
.expect("Should decode custom header");
assert_eq!(decoded[0].name, "x-custom");
assert_eq!(decoded[0].value, "test-value");
}
#[test]
fn conformance_case_insensitive_name_matching() {
let mut encoder = Encoder::new();
encoder.set_use_huffman(false);
let test_cases = vec![
("Content-Type", ""), ("CONTENT-TYPE", ""), ("content-type", ""), ("Content-Length", ""), ("ACCEPT", ""), ];
for (mixed_case_name, value) in test_cases {
let header = Header::new(mixed_case_name, value);
let mut encoded = BytesMut::new();
let normalized_name = mixed_case_name.to_lowercase();
let expected_index = find_static_name(&normalized_name)
.expect(&format!("Should find static index for {}", normalized_name));
encoder.encode(&[header], &mut encoded);
let expected_encoded = 0x80 | expected_index;
assert_eq!(
encoded[0], expected_encoded as u8,
"Case-insensitive name {} should match static table index {}",
mixed_case_name, expected_index
);
}
}
#[test]
fn conformance_dynamic_table_eviction_static_first() {
let mut encoder = Encoder::new();
let mut decoder = Decoder::new();
encoder.set_use_huffman(false);
encoder.set_max_table_size(256); decoder.set_allowed_table_size(256);
let headers_1 = vec![
Header::new("x-custom-1", "value-1"), Header::new("x-custom-2", "value-2"), Header::new("x-custom-3", "value-3"), Header::new("x-custom-4", "value-4"), ];
let mut encoded = BytesMut::new();
encoder.encode(&headers_1, &mut encoded);
let mut src = encoded.freeze();
decoder.decode(&mut src).expect("Should decode first batch");
let evicting_header = Header::new("x-custom-5", "value-5");
let mut encoded = BytesMut::new();
encoder.encode(std::slice::from_ref(&evicting_header), &mut encoded);
let mut src = encoded.freeze();
decoder
.decode(&mut src)
.expect("Should decode evicting header");
let static_header = Header::new(":method", "GET");
let mut encoded = BytesMut::new();
encoder.encode(&[static_header], &mut encoded);
assert_eq!(
encoded[0], 0x82,
"Static table entry should remain accessible after dynamic table eviction"
);
let large_headers = vec![
Header::new("x-very-long-header-name-1", "very-long-value-1"),
Header::new("x-very-long-header-name-2", "very-long-value-2"),
];
let mut encoded = BytesMut::new();
encoder.encode(&large_headers, &mut encoded);
let mut src = encoded.freeze();
let decoded = decoder
.decode(&mut src)
.expect("Should handle dynamic table eviction properly");
assert_eq!(decoded.len(), 2);
}
#[test]
fn conformance_never_indexed_header_preservation() {
let mut encoder = Encoder::new();
let mut decoder = Decoder::new();
encoder.set_use_huffman(false);
let sensitive_headers = vec![
Header::new("authorization", "Bearer secret-token"),
Header::new("cookie", "sessionid=secret123"),
Header::new("x-api-key", "secret-api-key"),
];
let mut encoded = BytesMut::new();
encoder.encode_sensitive(&sensitive_headers, &mut encoded);
let mut src = encoded.clone().freeze();
let decoded = decoder
.decode(&mut src)
.expect("Should decode never-indexed headers");
assert_eq!(decoded.len(), 3);
assert_eq!(decoded[0].name, "authorization");
assert_eq!(decoded[0].value, "Bearer secret-token");
let mut encoded_again = BytesMut::new();
encoder.encode_sensitive(&sensitive_headers, &mut encoded_again);
for &byte in &encoded_again[0..3] {
let is_never_indexed = (byte & 0xF0) == 0x10;
let is_literal_no_index = (byte & 0xF0) == 0x00;
assert!(
is_never_indexed || is_literal_no_index,
"Never-indexed header should not use indexed representation, got 0x{:02x}",
byte
);
}
let mut src = encoded_again.freeze();
let decoded_again = decoder
.decode(&mut src)
.expect("Should decode never-indexed headers again");
assert_eq!(
decoded_again, decoded,
"Never-indexed headers should decode consistently"
);
let mut encoded_regular = BytesMut::new();
encoder.encode(&sensitive_headers[0..1], &mut encoded_regular);
let first_byte = encoded_regular[0];
let is_indexed = (first_byte & 0x80) != 0;
assert!(
!is_indexed,
"Never-indexed header should not be found in dynamic table on subsequent encode"
);
}
#[test]
fn sensitive_exact_static_match_never_uses_indexed_representation() {
let mut encoder = Encoder::new();
let mut decoder = Decoder::new();
encoder.set_use_huffman(false);
let header = Header::new(":method", "GET");
let mut encoded = BytesMut::new();
encoder.encode_sensitive(std::slice::from_ref(&header), &mut encoded);
assert_eq!(
encoded[0] & 0xF0,
0x10,
"sensitive headers must use the RFC 7541 never-indexed wire form, not indexed lookup"
);
assert_eq!(encoder.dynamic_table_size(), 0);
let mut src = encoded.freeze();
let decoded = decoder.decode(&mut src).expect("decode sensitive header");
assert_eq!(decoded, vec![header]);
assert_eq!(decoder.dynamic_table_size(), 0);
}
#[test]
fn sensitive_exact_dynamic_match_never_uses_indexed_representation() {
let mut encoder = Encoder::new();
let mut decoder = Decoder::new();
encoder.set_use_huffman(false);
let header = Header::new("authorization", "Bearer secret-token");
let mut indexed = BytesMut::new();
encoder.encode(std::slice::from_ref(&header), &mut indexed);
let mut indexed_src = indexed.freeze();
let decoded = decoder
.decode(&mut indexed_src)
.expect("decode indexed header");
assert_eq!(decoded, vec![header.clone()]);
let encoder_table_before = encoder.dynamic_table_size();
let decoder_table_before = decoder.dynamic_table_size();
let mut sensitive = BytesMut::new();
encoder.encode_sensitive(std::slice::from_ref(&header), &mut sensitive);
assert_eq!(
sensitive[0] & 0xF0,
0x10,
"sensitive exact matches must not collapse to indexed dynamic-table lookups"
);
let mut sensitive_src = sensitive.freeze();
let decoded_sensitive = decoder
.decode(&mut sensitive_src)
.expect("decode sensitive header");
assert_eq!(decoded_sensitive, vec![header]);
assert_eq!(encoder.dynamic_table_size(), encoder_table_before);
assert_eq!(decoder.dynamic_table_size(), decoder_table_before);
}
#[test]
fn conformance_static_table_bounds_checking() {
assert!(get_static(0).is_none(), "Index 0 should be invalid");
assert!(
get_static(1).is_some(),
"Index 1 should be valid (:authority)"
);
assert!(
get_static(61).is_some(),
"Index 61 should be valid (www-authenticate)"
);
assert!(
get_static(62).is_none(),
"Index 62 should be invalid (beyond static table)"
);
assert!(get_static(1000).is_none(), "Large index should be invalid");
assert_eq!(get_static(1).unwrap(), (":authority", ""));
assert_eq!(get_static(2).unwrap(), (":method", "GET"));
assert_eq!(get_static(61).unwrap(), ("www-authenticate", ""));
}
#[test]
fn conformance_static_table_completeness() {
assert_eq!(
STATIC_TABLE.len(),
61,
"Static table must have exactly 61 entries per RFC 7541 Appendix A"
);
assert_eq!(STATIC_TABLE[0], (":authority", "")); assert_eq!(STATIC_TABLE[1], (":method", "GET")); assert_eq!(STATIC_TABLE[2], (":method", "POST")); assert_eq!(STATIC_TABLE[3], (":path", "/")); assert_eq!(STATIC_TABLE[4], (":path", "/index.html")); assert_eq!(STATIC_TABLE[5], (":scheme", "http")); assert_eq!(STATIC_TABLE[6], (":scheme", "https")); assert_eq!(STATIC_TABLE[7], (":status", "200")); assert_eq!(STATIC_TABLE[60], ("www-authenticate", ""));
assert_eq!(
STATIC_EXACT_INDEX.len(),
61,
"Exact index should have 61 entries"
);
assert_eq!(find_static(":method", "GET"), Some(2));
assert_eq!(find_static(":method", "POST"), Some(3));
assert_eq!(find_static(":status", "200"), Some(8));
assert_eq!(find_static("content-type", ""), Some(31));
assert_eq!(find_static_name(":method"), Some(2)); assert_eq!(find_static_name(":path"), Some(4)); assert_eq!(find_static_name(":status"), Some(8)); }
}