use std::borrow::Cow;
use std::num::Wrapping;
use tracing::{debug, trace};
use super::huffman::HuffmanDecoder;
use super::huffman::HuffmanDecoderError;
use super::STATIC_TABLE;
use super::{HeaderTable, StaticTable};
fn decode_integer(buf: &[u8], prefix_size: u8) -> Result<(usize, usize), DecoderError> {
if !(1..=8).contains(&prefix_size) {
return Err(DecoderError::IntegerDecodingError(
IntegerDecodingError::InvalidPrefix,
));
}
if buf.is_empty() {
return Err(DecoderError::IntegerDecodingError(
IntegerDecodingError::NotEnoughOctets,
));
}
let Wrapping(mask) = if prefix_size == 8 {
Wrapping(0xFF)
} else {
Wrapping(1u8 << prefix_size) - Wrapping(1)
};
let mut value = (buf[0] & mask) as usize;
if value < (mask as usize) {
return Ok((value, 1));
}
let mut total = 1;
let mut m = 0;
let octet_limit = 5;
for &b in buf[1..].iter() {
total += 1;
value += ((b & 127) as usize) * (1 << m);
m += 7;
if b & 128 != 128 {
return Ok((value, total));
}
if total == octet_limit {
return Err(DecoderError::IntegerDecodingError(
IntegerDecodingError::TooManyOctets,
));
}
}
Err(DecoderError::IntegerDecodingError(
IntegerDecodingError::NotEnoughOctets,
))
}
fn decode_string(buf: &[u8]) -> Result<(Cow<'_, [u8]>, usize), DecoderError> {
let (len, consumed) = decode_integer(buf, 7)?;
debug!("decode_string: Consumed = {}, len = {}", consumed, len);
if consumed + len > buf.len() {
return Err(DecoderError::StringDecodingError(
StringDecodingError::NotEnoughOctets,
));
}
let raw_string = &buf[consumed..consumed + len];
if buf[0] & 128 == 128 {
debug!("decode_string: Using the Huffman code");
let mut decoder = HuffmanDecoder::new();
let decoded = match decoder.decode(raw_string) {
Err(e) => {
return Err(DecoderError::StringDecodingError(
StringDecodingError::HuffmanDecoderError(e),
));
}
Ok(res) => res,
};
Ok((Cow::Owned(decoded), consumed + len))
} else {
debug!("decode_string: Raw octet string received");
Ok((Cow::Borrowed(raw_string), consumed + len))
}
}
enum FieldRepresentation {
Indexed,
LiteralWithIncrementalIndexing,
SizeUpdate,
LiteralNeverIndexed,
LiteralWithoutIndexing,
}
impl FieldRepresentation {
fn new(octet: u8) -> FieldRepresentation {
if octet & 128 == 128 {
FieldRepresentation::Indexed
} else if octet & 64 == 64 {
FieldRepresentation::LiteralWithIncrementalIndexing
} else if octet & 32 == 32 {
FieldRepresentation::SizeUpdate
} else if octet & 16 == 16 {
FieldRepresentation::LiteralNeverIndexed
} else {
FieldRepresentation::LiteralWithoutIndexing
}
}
}
#[derive(PartialEq, Copy, Clone, Debug)]
pub enum IntegerDecodingError {
TooManyOctets,
ValueTooLarge,
NotEnoughOctets,
InvalidPrefix,
}
#[derive(PartialEq, Copy, Clone, Debug)]
pub enum StringDecodingError {
NotEnoughOctets,
HuffmanDecoderError(HuffmanDecoderError),
}
#[derive(PartialEq, Copy, Clone, Debug)]
pub enum DecoderError {
HeaderIndexOutOfBounds,
IntegerDecodingError(IntegerDecodingError),
StringDecodingError(StringDecodingError),
InvalidMaxDynamicSize,
SizeUpdateAtEnd,
}
pub type DecoderResult = Result<Vec<(Vec<u8>, Vec<u8>)>, DecoderError>;
pub struct Decoder<'a> {
header_table: HeaderTable<'a>,
max_allowed_table_size: Option<usize>,
#[cfg(test)]
pub(crate) allow_trailing_size_updates: bool,
}
impl Default for Decoder<'_> {
fn default() -> Decoder<'static> {
Decoder::with_static_table(STATIC_TABLE)
}
}
type DecodedLiteralCow<'a> = ((Cow<'a, [u8]>, Cow<'a, [u8]>), usize);
type DecodedLiteralSlice<'a> = ((&'a [u8], &'a [u8]), usize);
impl<'a> Decoder<'a> {
pub fn new() -> Decoder<'a> {
Default::default()
}
fn with_static_table(static_table: StaticTable<'a>) -> Decoder<'a> {
Decoder {
header_table: HeaderTable::with_static_table(static_table),
max_allowed_table_size: None,
#[cfg(test)]
allow_trailing_size_updates: false,
}
}
pub fn set_max_table_size(&mut self, new_max_size: usize) {
if let Some(max_allowed_size) = self.max_allowed_table_size {
assert!(
new_max_size <= max_allowed_size,
"new_max_size ({}) > max_allowed_size ({})",
new_max_size,
max_allowed_size
);
}
self.header_table
.dynamic_table
.set_max_table_size(new_max_size);
}
pub fn set_max_allowed_table_size(&mut self, max_allowed_size: usize) {
self.max_allowed_table_size = Some(max_allowed_size);
}
pub fn decode_with_cb<F>(&mut self, buf: &[u8], mut cb: F) -> Result<(), DecoderError>
where
F: FnMut(Cow<[u8]>, Cow<[u8]>),
{
let mut current_octet_index = 0;
let mut last_was_size_update = false;
while current_octet_index < buf.len() {
let initial_octet = buf[current_octet_index];
let buffer_leftover = &buf[current_octet_index..];
let field_representation = FieldRepresentation::new(initial_octet);
last_was_size_update = matches!(field_representation, FieldRepresentation::SizeUpdate);
let consumed = match field_representation {
FieldRepresentation::Indexed => {
let ((name, value), consumed) = self.decode_indexed(buffer_leftover)?;
cb(Cow::Borrowed(name), Cow::Borrowed(value));
consumed
}
FieldRepresentation::LiteralWithIncrementalIndexing => {
let ((name, value), consumed) = {
let ((name, value), consumed) =
self.decode_literal(buffer_leftover, true)?;
cb(Cow::Borrowed(&name), Cow::Borrowed(&value));
let name = name.into_owned();
let value = value.into_owned();
((name, value), consumed)
};
self.header_table.add_header(name, value);
consumed
}
FieldRepresentation::LiteralWithoutIndexing => {
let ((name, value), consumed) = self.decode_literal(buffer_leftover, false)?;
cb(name, value);
consumed
}
FieldRepresentation::LiteralNeverIndexed => {
let ((name, value), consumed) = self.decode_literal(buffer_leftover, false)?;
cb(name, value);
consumed
}
FieldRepresentation::SizeUpdate => {
self.update_max_dynamic_size(buffer_leftover)?
}
};
current_octet_index += consumed;
}
if last_was_size_update {
#[cfg(test)]
if self.allow_trailing_size_updates {
return Ok(());
}
return Err(DecoderError::SizeUpdateAtEnd);
}
Ok(())
}
pub fn decode(&mut self, buf: &[u8]) -> DecoderResult {
let mut header_list = Vec::new();
self.decode_with_cb(buf, |n, v| {
header_list.push((n.into_owned(), v.into_owned()))
})?;
Ok(header_list)
}
fn decode_indexed(&self, buf: &[u8]) -> Result<DecodedLiteralSlice<'_>, DecoderError> {
let (index, consumed) = decode_integer(buf, 7)?;
debug!(
"Decoding indexed: index = {}, consumed = {}",
index, consumed
);
let (name, value) = self.get_from_table(index)?;
Ok(((name, value), consumed))
}
fn get_from_table(&self, index: usize) -> Result<(&[u8], &[u8]), DecoderError> {
self.header_table
.get_from_table(index)
.ok_or(DecoderError::HeaderIndexOutOfBounds)
}
fn decode_literal<'b>(
&'b self,
buf: &'b [u8],
index: bool,
) -> Result<DecodedLiteralCow<'b>, DecoderError> {
let prefix = if index { 6 } else { 4 };
let (table_index, mut consumed) = decode_integer(buf, prefix)?;
let name = if table_index == 0 {
let (name, name_len) = decode_string(&buf[consumed..])?;
consumed += name_len;
name
} else {
let (name, _) = self.get_from_table(table_index)?;
Cow::Borrowed(name)
};
let (value, value_len) = decode_string(&buf[consumed..])?;
consumed += value_len;
Ok(((name, value), consumed))
}
fn update_max_dynamic_size(&mut self, buf: &[u8]) -> Result<usize, DecoderError> {
let (new_size, consumed) = decode_integer(buf, 5).ok().unwrap();
if let Some(max_size) = self.max_allowed_table_size {
if new_size > max_size {
return Err(DecoderError::InvalidMaxDynamicSize);
}
}
self.header_table.dynamic_table.set_max_table_size(new_size);
trace!(
"Decoder changed max table size from {} to {}",
self.header_table.dynamic_table.get_size(),
new_size
);
Ok(consumed)
}
}
#[cfg(test)]
mod tests {
use super::decode_integer;
use std::borrow::Cow;
use super::super::encoder::encode_integer;
use super::super::huffman::HuffmanDecoderError;
use super::decode_string;
use super::Decoder;
use super::FieldRepresentation;
use super::{DecoderError, DecoderResult};
use super::{IntegerDecodingError, StringDecodingError};
#[test]
fn test_decode_integer() {
assert_eq!((10, 1), decode_integer(&[10], 5).ok().unwrap());
assert_eq!((1337, 3), decode_integer(&[31, 154, 10], 5).ok().unwrap());
assert_eq!(
(1337, 3),
decode_integer(&[31 + 32, 154, 10], 5).ok().unwrap()
);
assert_eq!(
(1337, 3),
decode_integer(&[31 + 64, 154, 10], 5).ok().unwrap()
);
assert_eq!(
(1337, 3),
decode_integer(&[31, 154, 10, 111, 22], 5).ok().unwrap()
);
assert_eq!((127, 2), decode_integer(&[255, 0], 7).ok().unwrap());
assert_eq!((127, 2), decode_integer(&[127, 0], 7).ok().unwrap());
assert_eq!((255, 3), decode_integer(&[127, 128, 1], 7).ok().unwrap());
assert_eq!((255, 2), decode_integer(&[255, 0], 8).unwrap());
assert_eq!((254, 1), decode_integer(&[254], 8).unwrap());
assert_eq!((1, 1), decode_integer(&[1], 8).unwrap());
assert_eq!((0, 1), decode_integer(&[0], 8).unwrap());
assert_eq!(
(268435710, 5),
decode_integer(&[0xFF, 0xFF, 0xFF, 0xFF, 0xFF - 128], 8)
.ok()
.unwrap()
);
}
macro_rules! assert_integer_err (
($err_type:expr, $decoder_result:expr) => (
assert_eq!($err_type, match $decoder_result {
Err(DecoderError::IntegerDecodingError(e)) => e,
_ => panic!("Expected a decoding error"),
});
);
);
#[test]
fn test_decode_integer_errors() {
assert_integer_err!(
IntegerDecodingError::NotEnoughOctets,
decode_integer(&[], 5)
);
assert_integer_err!(
IntegerDecodingError::NotEnoughOctets,
decode_integer(&[0xFF, 0xFF], 5)
);
assert_integer_err!(
IntegerDecodingError::TooManyOctets,
decode_integer(
&[0xFF, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80],
1
)
);
assert_integer_err!(
IntegerDecodingError::TooManyOctets,
decode_integer(&[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0], 8)
);
assert_integer_err!(
IntegerDecodingError::InvalidPrefix,
decode_integer(&[10], 0)
);
assert_integer_err!(
IntegerDecodingError::InvalidPrefix,
decode_integer(&[10], 9)
);
}
#[test]
fn test_detect_literal_without_indexing() {
assert!(matches!(
FieldRepresentation::new(0),
FieldRepresentation::LiteralWithoutIndexing
));
assert!(matches!(
FieldRepresentation::new((1 << 4) - 1),
FieldRepresentation::LiteralWithoutIndexing
));
assert!(matches!(
FieldRepresentation::new(2),
FieldRepresentation::LiteralWithoutIndexing
));
}
#[test]
fn test_detect_literal_never_indexed() {
assert!(matches!(
FieldRepresentation::new(1 << 4),
FieldRepresentation::LiteralNeverIndexed
));
assert!(matches!(
FieldRepresentation::new((1 << 4) + 15),
FieldRepresentation::LiteralNeverIndexed
));
}
#[test]
fn test_detect_literal_incremental_indexing() {
assert!(matches!(
FieldRepresentation::new(1 << 6),
FieldRepresentation::LiteralWithIncrementalIndexing
));
assert!(matches!(
FieldRepresentation::new((1 << 6) + (1 << 4)),
FieldRepresentation::LiteralWithIncrementalIndexing
));
assert!(matches!(
FieldRepresentation::new((1 << 7) - 1),
FieldRepresentation::LiteralWithIncrementalIndexing
));
}
#[test]
fn test_detect_indexed() {
assert!(matches!(
FieldRepresentation::new(1 << 7),
FieldRepresentation::Indexed
));
assert!(matches!(
FieldRepresentation::new((1 << 7) + (1 << 4)),
FieldRepresentation::Indexed
));
assert!(matches!(
FieldRepresentation::new((1 << 7) + (1 << 5)),
FieldRepresentation::Indexed
));
assert!(matches!(
FieldRepresentation::new((1 << 7) + (1 << 6)),
FieldRepresentation::Indexed
));
assert!(matches!(
FieldRepresentation::new(255),
FieldRepresentation::Indexed
));
}
#[test]
fn test_detect_dynamic_table_size_update() {
assert!(matches!(
FieldRepresentation::new(1 << 5),
FieldRepresentation::SizeUpdate
));
assert!(matches!(
FieldRepresentation::new((1 << 5) + (1 << 4)),
FieldRepresentation::SizeUpdate
));
assert!(matches!(
FieldRepresentation::new((1 << 6) - 1),
FieldRepresentation::SizeUpdate
));
}
#[test]
fn test_decode_string_no_huffman() {
fn assert_borrowed_eq(expected: (&[u8], usize), result: (Cow<'_, [u8]>, usize)) {
let (expected_str, expected_len) = expected;
let (actual_str, actual_len) = result;
assert_eq!(expected_len, actual_len);
match actual_str {
Cow::Borrowed(actual) => assert_eq!(actual, expected_str),
_ => panic!("Expected the result to be borrowed!"),
};
}
assert_eq!(
(Cow::Borrowed(&b"abc"[..]), 4),
decode_string(&[3, b'a', b'b', b'c']).ok().unwrap()
);
assert_eq!(
(Cow::Borrowed(&b"a"[..]), 2),
decode_string(&[1, b'a']).ok().unwrap()
);
assert_eq!(
(Cow::Borrowed(&b""[..]), 1),
decode_string(&[0, b'a']).ok().unwrap()
);
assert_borrowed_eq(
(&b"abc"[..], 4),
decode_string(&[3, b'a', b'b', b'c']).ok().unwrap(),
);
assert_borrowed_eq((&b"a"[..], 2), decode_string(&[1, b'a']).ok().unwrap());
assert_borrowed_eq((&b""[..], 1), decode_string(&[0, b'a']).ok().unwrap());
assert_eq!(
StringDecodingError::NotEnoughOctets,
match decode_string(&[3, b'a', b'b']) {
Err(DecoderError::StringDecodingError(e)) => e,
_ => panic!("Expected NotEnoughOctets error!"),
}
);
}
#[test]
fn test_decode_string_no_huffman_long() {
{
let full_string: Vec<u8> = (0u8..200).collect();
let mut encoded = encode_integer(full_string.len(), 7);
encoded.extend(full_string.clone().into_iter());
assert_eq!(
(Cow::Owned(full_string), encoded.len()),
decode_string(&encoded).ok().unwrap()
);
}
{
let full_string: Vec<u8> = (0u8..127).collect();
let mut encoded = encode_integer(full_string.len(), 7);
encoded.extend(full_string.clone().into_iter());
assert_eq!(
(Cow::Owned(full_string), encoded.len()),
decode_string(&encoded).ok().unwrap()
);
}
}
#[test]
fn test_decode_fully_in_static_table() {
let mut decoder = Decoder::new();
let header_list = decoder.decode(&[0x82]).ok().unwrap();
assert_eq!(vec![(b":method".to_vec(), b"GET".to_vec())], header_list);
}
#[test]
fn test_decode_multiple_fully_in_static_table() {
let mut decoder = Decoder::new();
let header_list = decoder.decode(&[0x82, 0x86, 0x84]).ok().unwrap();
assert_eq!(
header_list,
[
(b":method".to_vec(), b"GET".to_vec()),
(b":scheme".to_vec(), b"http".to_vec()),
(b":path".to_vec(), b"/".to_vec()),
]
);
}
#[test]
fn test_decode_literal_indexed_name() {
let mut decoder = Decoder::new();
let hex_dump = [
0x04, 0x0c, 0x2f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68,
];
let header_list = decoder.decode(&hex_dump).ok().unwrap();
assert_eq!(
header_list,
[(b":path".to_vec(), b"/sample/path".to_vec()),]
);
assert_eq!(decoder.header_table.dynamic_table.len(), 0);
}
#[test]
fn test_decode_literal_both() {
let mut decoder = Decoder::new();
let hex_dump = [
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 header_list = decoder.decode(&hex_dump).ok().unwrap();
assert_eq!(
header_list,
[(b"custom-key".to_vec(), b"custom-header".to_vec()),]
);
assert_eq!(decoder.header_table.dynamic_table.len(), 1);
let expected_table = vec![(b"custom-key".to_vec(), b"custom-header".to_vec())];
let actual = decoder.header_table.dynamic_table.to_vec();
assert_eq!(actual, expected_table);
}
#[test]
fn test_decode_literal_name_in_dynamic() {
let mut decoder = Decoder::new();
{
let hex_dump = [
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 header_list = decoder.decode(&hex_dump).ok().unwrap();
assert_eq!(
header_list,
[(b"custom-key".to_vec(), b"custom-header".to_vec()),]
);
assert_eq!(decoder.header_table.dynamic_table.len(), 1);
let expected_table = vec![(b"custom-key".to_vec(), b"custom-header".to_vec())];
let actual = decoder.header_table.dynamic_table.to_vec();
assert_eq!(actual, expected_table);
}
{
let hex_dump = [
0x40 + 62, 0x0e,
0x63,
0x75,
0x73,
0x74,
0x6f,
0x6d,
0x2d,
0x68,
0x65,
0x61,
0x64,
0x65,
0x72,
0x2d,
];
let header_list = decoder.decode(&hex_dump).ok().unwrap();
assert_eq!(
header_list,
[(b"custom-key".to_vec(), b"custom-header-".to_vec()),]
);
assert_eq!(decoder.header_table.dynamic_table.len(), 2);
let expected_table = vec![
(b"custom-key".to_vec(), b"custom-header-".to_vec()),
(b"custom-key".to_vec(), b"custom-header".to_vec()),
];
let actual = decoder.header_table.dynamic_table.to_vec();
assert_eq!(actual, expected_table);
}
}
#[test]
fn test_decode_literal_field_never_indexed() {
let mut decoder = Decoder::new();
let hex_dump = [
0x10, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x06, 0x73, 0x65, 0x63,
0x72, 0x65, 0x74,
];
let header_list = decoder.decode(&hex_dump).ok().unwrap();
assert_eq!(header_list, [(b"password".to_vec(), b"secret".to_vec()),]);
assert_eq!(decoder.header_table.dynamic_table.len(), 0);
}
#[test]
fn test_request_sequence_no_huffman() {
let mut decoder = Decoder::new();
{
let hex_dump = [
0x82, 0x86, 0x84, 0x41, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70,
0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
];
let header_list = decoder.decode(&hex_dump).ok().unwrap();
assert_eq!(
header_list,
[
(b":method".to_vec(), b"GET".to_vec()),
(b":scheme".to_vec(), b"http".to_vec()),
(b":path".to_vec(), b"/".to_vec()),
(b":authority".to_vec(), b"www.example.com".to_vec()),
]
);
assert_eq!(decoder.header_table.dynamic_table.len(), 1);
let expected_table = vec![(b":authority".to_vec(), b"www.example.com".to_vec())];
let actual = decoder.header_table.dynamic_table.to_vec();
assert_eq!(actual, expected_table);
}
{
let hex_dump = [
0x82, 0x86, 0x84, 0xbe, 0x58, 0x08, 0x6e, 0x6f, 0x2d, 0x63, 0x61, 0x63, 0x68, 0x65,
];
let header_list = decoder.decode(&hex_dump).ok().unwrap();
assert_eq!(
header_list,
[
(b":method".to_vec(), b"GET".to_vec()),
(b":scheme".to_vec(), b"http".to_vec()),
(b":path".to_vec(), b"/".to_vec()),
(b":authority".to_vec(), b"www.example.com".to_vec()),
(b"cache-control".to_vec(), b"no-cache".to_vec()),
]
);
let expected_table = vec![
(b"cache-control".to_vec(), b"no-cache".to_vec()),
(b":authority".to_vec(), b"www.example.com".to_vec()),
];
let actual = decoder.header_table.dynamic_table.to_vec();
assert_eq!(actual, expected_table);
}
{
let hex_dump = [
0x82, 0x87, 0x85, 0xbf, 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 header_list = decoder.decode(&hex_dump).ok().unwrap();
assert_eq!(
header_list,
[
(b":method".to_vec(), b"GET".to_vec()),
(b":scheme".to_vec(), b"https".to_vec()),
(b":path".to_vec(), b"/index.html".to_vec()),
(b":authority".to_vec(), b"www.example.com".to_vec()),
(b"custom-key".to_vec(), b"custom-value".to_vec()),
]
);
let expected_table = vec![
(b"custom-key".to_vec(), b"custom-value".to_vec()),
(b"cache-control".to_vec(), b"no-cache".to_vec()),
(b":authority".to_vec(), b"www.example.com".to_vec()),
];
let actual = decoder.header_table.dynamic_table.to_vec();
assert_eq!(actual, expected_table);
}
}
#[test]
fn response_sequence_no_huffman() {
let mut decoder = Decoder::new();
decoder.set_max_table_size(256);
{
let hex_dump = [
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 header_list = decoder.decode(&hex_dump).ok().unwrap();
assert_eq!(
header_list,
[
(b":status".to_vec(), b"302".to_vec()),
(b"cache-control".to_vec(), b"private".to_vec()),
(b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()),
(b"location".to_vec(), b"https://www.example.com".to_vec()),
]
);
let expected_table = vec![
(b"location".to_vec(), b"https://www.example.com".to_vec()),
(b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()),
(b"cache-control".to_vec(), b"private".to_vec()),
(b":status".to_vec(), b"302".to_vec()),
];
let actual = decoder.header_table.dynamic_table.to_vec();
assert_eq!(actual, expected_table);
}
{
let hex_dump = [0x48, 0x03, 0x33, 0x30, 0x37, 0xc1, 0xc0, 0xbf];
let header_list = decoder.decode(&hex_dump).ok().unwrap();
assert_eq!(
header_list,
[
(b":status".to_vec(), b"307".to_vec()),
(b"cache-control".to_vec(), b"private".to_vec()),
(b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()),
(b"location".to_vec(), b"https://www.example.com".to_vec()),
]
);
let expected_table = vec![
(b":status".to_vec(), b"307".to_vec()),
(b"location".to_vec(), b"https://www.example.com".to_vec()),
(b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()),
(b"cache-control".to_vec(), b"private".to_vec()),
];
let actual = decoder.header_table.dynamic_table.to_vec();
assert_eq!(actual, expected_table);
}
{
let hex_dump = [
0x88, 0xc1, 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,
0x32, 0x20, 0x47, 0x4d, 0x54, 0xc0, 0x5a, 0x04, 0x67, 0x7a, 0x69, 0x70, 0x77, 0x38,
0x66, 0x6f, 0x6f, 0x3d, 0x41, 0x53, 0x44, 0x4a, 0x4b, 0x48, 0x51, 0x4b, 0x42, 0x5a,
0x58, 0x4f, 0x51, 0x57, 0x45, 0x4f, 0x50, 0x49, 0x55, 0x41, 0x58, 0x51, 0x57, 0x45,
0x4f, 0x49, 0x55, 0x3b, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, 0x3d, 0x33,
0x36, 0x30, 0x30, 0x3b, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x31,
];
let header_list = decoder.decode(&hex_dump).ok().unwrap();
let expected_header_list = [
(b":status".to_vec(), b"200".to_vec()),
(b"cache-control".to_vec(), b"private".to_vec()),
(b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:22 GMT".to_vec()),
(b"location".to_vec(), b"https://www.example.com".to_vec()),
(b"content-encoding".to_vec(), b"gzip".to_vec()),
(
b"set-cookie".to_vec(),
b"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1".to_vec(),
),
];
assert_eq!(header_list, expected_header_list);
let expected_table = vec![
(
b"set-cookie".to_vec(),
b"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1".to_vec(),
),
(b"content-encoding".to_vec(), b"gzip".to_vec()),
(b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:22 GMT".to_vec()),
];
let actual = decoder.header_table.dynamic_table.to_vec();
assert_eq!(actual, expected_table);
}
}
#[test]
fn test_decoder_clear_dynamic_table() {
let mut decoder = Decoder::new();
decoder.allow_trailing_size_updates = true;
{
let hex_dump = [
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 header_list = decoder.decode(&hex_dump).ok().unwrap();
assert_eq!(
header_list,
[
(b":status".to_vec(), b"302".to_vec()),
(b"cache-control".to_vec(), b"private".to_vec()),
(b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()),
(b"location".to_vec(), b"https://www.example.com".to_vec()),
]
);
let expected_table = vec![
(b"location".to_vec(), b"https://www.example.com".to_vec()),
(b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()),
(b"cache-control".to_vec(), b"private".to_vec()),
(b":status".to_vec(), b"302".to_vec()),
];
let actual = decoder.header_table.dynamic_table.to_vec();
assert_eq!(actual, expected_table);
}
{
let hex_dump = [
0x48, 0x03, 0x33, 0x30, 0x37, 0xc1, 0xc0, 0xbf,
0x20,
];
let header_list = decoder.decode(&hex_dump).ok().unwrap();
assert_eq!(
header_list,
[
(b":status".to_vec(), b"307".to_vec()),
(b"cache-control".to_vec(), b"private".to_vec()),
(b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()),
(b"location".to_vec(), b"https://www.example.com".to_vec()),
]
);
let expected_table = vec![];
let actual = decoder.header_table.dynamic_table.to_vec();
assert_eq!(actual, expected_table);
assert_eq!(0, decoder.header_table.dynamic_table.get_max_table_size());
}
}
#[test]
fn request_sequence_huffman() {
let mut decoder = Decoder::new();
{
let hex_dump = [
0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab,
0x90, 0xf4, 0xff,
];
let header_list = decoder.decode(&hex_dump).ok().unwrap();
assert_eq!(
header_list,
[
(b":method".to_vec(), b"GET".to_vec()),
(b":scheme".to_vec(), b"http".to_vec()),
(b":path".to_vec(), b"/".to_vec()),
(b":authority".to_vec(), b"www.example.com".to_vec()),
]
);
assert_eq!(decoder.header_table.dynamic_table.len(), 1);
let expected_table = vec![(b":authority".to_vec(), b"www.example.com".to_vec())];
let actual = decoder.header_table.dynamic_table.to_vec();
assert_eq!(actual, expected_table);
}
{
let hex_dump = [
0x82, 0x86, 0x84, 0xbe, 0x58, 0x86, 0xa8, 0xeb, 0x10, 0x64, 0x9c, 0xbf,
];
let header_list = decoder.decode(&hex_dump).ok().unwrap();
assert_eq!(
header_list,
[
(b":method".to_vec(), b"GET".to_vec()),
(b":scheme".to_vec(), b"http".to_vec()),
(b":path".to_vec(), b"/".to_vec()),
(b":authority".to_vec(), b"www.example.com".to_vec()),
(b"cache-control".to_vec(), b"no-cache".to_vec()),
]
);
let expected_table = vec![
(b"cache-control".to_vec(), b"no-cache".to_vec()),
(b":authority".to_vec(), b"www.example.com".to_vec()),
];
let actual = decoder.header_table.dynamic_table.to_vec();
assert_eq!(actual, expected_table);
}
{
let hex_dump = [
0x82, 0x87, 0x85, 0xbf, 0x40, 0x88, 0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xa9, 0x7d, 0x7f,
0x89, 0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xb8, 0xe8, 0xb4, 0xbf,
];
let header_list = decoder.decode(&hex_dump).ok().unwrap();
assert_eq!(
header_list,
[
(b":method".to_vec(), b"GET".to_vec()),
(b":scheme".to_vec(), b"https".to_vec()),
(b":path".to_vec(), b"/index.html".to_vec()),
(b":authority".to_vec(), b"www.example.com".to_vec()),
(b"custom-key".to_vec(), b"custom-value".to_vec()),
]
);
let expected_table = vec![
(b"custom-key".to_vec(), b"custom-value".to_vec()),
(b"cache-control".to_vec(), b"no-cache".to_vec()),
(b":authority".to_vec(), b"www.example.com".to_vec()),
];
let actual = decoder.header_table.dynamic_table.to_vec();
assert_eq!(actual, expected_table);
}
}
#[test]
fn response_sequence_huffman() {
let mut decoder = Decoder::new();
decoder.set_max_table_size(256);
{
let hex_dump = [
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 header_list = decoder.decode(&hex_dump).ok().unwrap();
assert_eq!(
header_list,
[
(b":status".to_vec(), b"302".to_vec()),
(b"cache-control".to_vec(), b"private".to_vec()),
(b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()),
(b"location".to_vec(), b"https://www.example.com".to_vec()),
]
);
let expected_table = vec![
(b"location".to_vec(), b"https://www.example.com".to_vec()),
(b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()),
(b"cache-control".to_vec(), b"private".to_vec()),
(b":status".to_vec(), b"302".to_vec()),
];
let actual = decoder.header_table.dynamic_table.to_vec();
assert_eq!(actual, expected_table);
}
{
let hex_dump = [0x48, 0x83, 0x64, 0x0e, 0xff, 0xc1, 0xc0, 0xbf];
let header_list = decoder.decode(&hex_dump).ok().unwrap();
assert_eq!(
header_list,
[
(b":status".to_vec(), b"307".to_vec()),
(b"cache-control".to_vec(), b"private".to_vec()),
(b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()),
(b"location".to_vec(), b"https://www.example.com".to_vec()),
]
);
let expected_table = vec![
(b":status".to_vec(), b"307".to_vec()),
(b"location".to_vec(), b"https://www.example.com".to_vec()),
(b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:21 GMT".to_vec()),
(b"cache-control".to_vec(), b"private".to_vec()),
];
let actual = decoder.header_table.dynamic_table.to_vec();
assert_eq!(actual, expected_table);
}
{
let hex_dump = [
0x88, 0xc1, 0x61, 0x96, 0xd0, 0x7a, 0xbe, 0x94, 0x10, 0x54, 0xd4, 0x44, 0xa8, 0x20,
0x05, 0x95, 0x04, 0x0b, 0x81, 0x66, 0xe0, 0x84, 0xa6, 0x2d, 0x1b, 0xff, 0xc0, 0x5a,
0x83, 0x9b, 0xd9, 0xab, 0x77, 0xad, 0x94, 0xe7, 0x82, 0x1d, 0xd7, 0xf2, 0xe6, 0xc7,
0xb3, 0x35, 0xdf, 0xdf, 0xcd, 0x5b, 0x39, 0x60, 0xd5, 0xaf, 0x27, 0x08, 0x7f, 0x36,
0x72, 0xc1, 0xab, 0x27, 0x0f, 0xb5, 0x29, 0x1f, 0x95, 0x87, 0x31, 0x60, 0x65, 0xc0,
0x03, 0xed, 0x4e, 0xe5, 0xb1, 0x06, 0x3d, 0x50, 0x07,
];
let header_list = decoder.decode(&hex_dump).ok().unwrap();
let expected_header_list = [
(b":status".to_vec(), b"200".to_vec()),
(b"cache-control".to_vec(), b"private".to_vec()),
(b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:22 GMT".to_vec()),
(b"location".to_vec(), b"https://www.example.com".to_vec()),
(b"content-encoding".to_vec(), b"gzip".to_vec()),
(
b"set-cookie".to_vec(),
b"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1".to_vec(),
),
];
assert_eq!(header_list, expected_header_list);
let expected_table = vec![
(
b"set-cookie".to_vec(),
b"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1".to_vec(),
),
(b"content-encoding".to_vec(), b"gzip".to_vec()),
(b"date".to_vec(), b"Mon, 21 Oct 2013 20:13:22 GMT".to_vec()),
];
let actual = decoder.header_table.dynamic_table.to_vec();
assert_eq!(actual, expected_table);
}
}
fn is_decoder_error(err: &DecoderError, result: &DecoderResult) -> bool {
match *result {
Err(ref e) => e == err,
_ => false,
}
}
#[test]
fn test_index_out_of_bounds() {
let mut decoder = Decoder::new();
let raw_messages = [
vec![0x80],
vec![0xbe],
vec![126, 1, 65],
];
for raw_message in raw_messages.iter() {
assert!(
is_decoder_error(
&DecoderError::HeaderIndexOutOfBounds,
&decoder.decode(raw_message)
),
"Expected index out of bounds"
);
}
}
#[test]
fn test_invalid_literal_huffman_string() {
let mut decoder = Decoder::new();
let hex_dump = [
0x82, 0x86, 0x84, 0x41, 0x8c, 0xf1, 0xe3, 0xc2, 0xe5, 0xf2, 0x3a, 0x6b, 0xa0, 0xab,
0x90, 0xf4, 0xfe,
];
assert!(matches!(
decoder.decode(&hex_dump),
Err(DecoderError::StringDecodingError(
StringDecodingError::HuffmanDecoderError(HuffmanDecoderError::InvalidPadding,)
))
));
}
#[test]
fn test_literal_header_key_incomplete() {
let mut decoder = Decoder::new();
let hex_dump = [
0x40, 0x0a, b'c', b'u', b's', b't', b'o', b'm', b'-', b'k', b'e',
];
let result = decoder.decode(&hex_dump);
assert!(matches!(
result,
Err(DecoderError::StringDecodingError(
StringDecodingError::NotEnoughOctets
))
));
}
#[test]
fn test_literal_header_missing_value() {
let mut decoder = Decoder::new();
let hex_dump = [
0x40, 0x0a, b'c', b'u', b's', b't', b'o', b'm', b'-', b'k', b'e', b'y',
];
let result = decoder.decode(&hex_dump);
assert!(matches!(
result,
Err(DecoderError::IntegerDecodingError(
IntegerDecodingError::NotEnoughOctets
))
));
}
}
#[cfg(test)]
#[cfg(feature = "interop-tests")]
mod interop_tests {
use std::fs;
use std::path::{Path, PathBuf};
use std::{borrow::Cow, collections::HashMap};
use serde::Deserialize;
use tracing::debug;
use super::Decoder;
#[derive(Deserialize)]
struct RawTestStory<'a> {
cases: Vec<RawTestFixture<'a>>,
}
#[derive(Deserialize)]
struct RawTestFixture<'a> {
wire: Cow<'a, str>,
headers: Vec<HashMap<Cow<'a, str>, Cow<'a, str>>>,
}
struct TestFixture {
wire_bytes: Vec<u8>,
headers: Vec<(Vec<u8>, Vec<u8>)>,
}
struct TestStory {
cases: Vec<TestFixture>,
}
#[derive(Debug, thiserror::Error)]
enum DecodeError {
#[error("Failed to parse hex-encoded bytes: {0}")]
FromHex(#[from] hex::FromHexError),
}
impl TryFrom<RawTestStory<'_>> for TestStory {
type Error = DecodeError;
fn try_from(raw_story: RawTestStory) -> Result<Self, Self::Error> {
let mut cases = Vec::with_capacity(raw_story.cases.len());
for raw_case in raw_story.cases {
let wire_bytes = hex::decode(raw_case.wire.as_bytes())?;
let headers: Vec<_> = raw_case
.headers
.into_iter()
.flat_map(|h| {
h.into_iter().map(|(k, v)| {
(k.into_owned().into_bytes(), v.into_owned().into_bytes())
})
})
.collect();
cases.push(TestFixture {
wire_bytes,
headers,
});
}
Ok(TestStory { cases })
}
}
#[test]
fn test_story_parser_sanity_check() {
let raw_json = r#"
{
"cases": [
{
"seqno": 0,
"wire": "82864188f439ce75c875fa5784",
"headers": [
{
":method": "GET"
},
{
":scheme": "http"
},
{
":authority": "yahoo.co.jp"
},
{
":path": "/"
}
]
},
{
"seqno": 1,
"wire": "8286418cf1e3c2fe8739ceb90ebf4aff84",
"headers": [
{
":method": "GET"
},
{
":scheme": "http"
},
{
":authority": "www.yahoo.co.jp"
},
{
":path": "/"
}
]
}
],
"draft": 9
}
"#;
let decoded: RawTestStory = serde_json::from_str(raw_json).unwrap();
let decoded = TestStory::try_from(decoded).unwrap();
assert_eq!(decoded.cases.len(), 2);
assert_eq!(
decoded.cases[0].wire_bytes,
vec![0x82, 0x86, 0x41, 0x88, 0xf4, 0x39, 0xce, 0x75, 0xc8, 0x75, 0xfa, 0x57, 0x84]
);
assert_eq!(
decoded.cases[0].headers,
vec![
(b":method".to_vec(), b"GET".to_vec()),
(b":scheme".to_vec(), b"http".to_vec()),
(b":authority".to_vec(), b"yahoo.co.jp".to_vec()),
(b":path".to_vec(), b"/".to_vec()),
]
);
}
fn test_story(story_file_name: PathBuf) {
let story: TestStory = {
let buf = std::fs::read_to_string(&story_file_name);
let raw_story: RawTestStory = serde_json::from_str(&buf.unwrap()).unwrap();
raw_story.try_into().unwrap()
};
let mut decoder = Decoder::new();
for case in story.cases.iter() {
let decoded = decoder.decode(&case.wire_bytes).unwrap();
assert_eq!(decoded, case.headers);
}
}
fn test_fixture_set(fixture_dir: &str) {
let files = fs::read_dir(Path::new(fixture_dir)).unwrap();
for fixture in files {
let file_name = fixture.unwrap().path();
debug!("Testing fixture: {:?}", file_name);
test_story(file_name);
}
}
#[test]
fn test_nghttp2_interop() {
test_fixture_set("fixtures/hpack/interop/nghttp2");
}
#[test]
fn test_nghttp2_change_table_size_interop() {
test_fixture_set("fixtures/hpack/interop/nghttp2-change-table-size");
}
#[test]
fn test_go_hpack_interop() {
test_fixture_set("fixtures/hpack/interop/go-hpack");
}
#[test]
fn test_node_http2_hpack_interop() {
test_fixture_set("fixtures/hpack/interop/node-http2-hpack");
}
#[test]
fn test_haskell_http2_linear_huffman() {
test_fixture_set("fixtures/hpack/interop/haskell-http2-linear-huffman");
}
}