use crate::Error;
use crate::hpack::dynamic::{DynamicTable, lookup};
use crate::hpack::huffman;
use crate::hpack::static_table;
pub const MAX_STRING: usize = 512;
pub struct Header {
pub name: heapless::String<MAX_STRING>,
pub value: heapless::String<MAX_STRING>,
}
pub struct Decoder {
table: DynamicTable,
}
impl Decoder {
pub fn new(table_size: usize) -> Self {
Self {
table: DynamicTable::new(table_size),
}
}
pub fn table(&self) -> &DynamicTable {
&self.table
}
pub fn decode(
&mut self,
mut input: &[u8],
mut on_header: impl FnMut(&str, &str),
) -> Result<(), Error> {
while !input.is_empty() {
let first = input[0];
if first & 0x80 != 0 {
let (index, rest) = decode_integer(input, 7)?;
input = rest;
let (name, value) = lookup(&self.table, index as usize)?;
on_header(name, value);
continue;
}
if first & 0xe0 == 0x20 {
let (size, rest) = decode_integer(input, 5)?;
input = rest;
self.table.set_capacity(size as usize);
continue;
}
let (prefix, indexing) = if first & 0xc0 == 0x40 {
(6, true)
} else {
(4, false)
};
let (index, rest) = decode_integer(input, prefix)?;
input = rest;
let mut name = heapless::String::<MAX_STRING>::new();
if index == 0 {
input = decode_string(input, &mut name)?;
} else {
let (existing, _) = lookup(&self.table, index as usize)?;
name.push_str(existing).map_err(|_| Error::BufferTooSmall)?;
}
let mut value = heapless::String::<MAX_STRING>::new();
input = decode_string(input, &mut value)?;
on_header(&name, &value);
if indexing {
self.table.insert(&name, &value);
}
}
Ok(())
}
}
pub fn decode_integer(input: &[u8], prefix_bits: u32) -> Result<(u64, &[u8]), Error> {
let mask = (1u64 << prefix_bits) - 1;
let first = *input.first().ok_or(Error::Incomplete)? as u64;
let value = first & mask;
if value < mask {
return Ok((value, &input[1..]));
}
let mut value = mask;
let mut shift = 0;
let mut rest = &input[1..];
loop {
let byte = *rest.first().ok_or(Error::Incomplete)?;
rest = &rest[1..];
if shift > 63 {
return Err(Error::Hpack);
}
value = value
.checked_add(((byte & 0x7f) as u64) << shift)
.ok_or(Error::Hpack)?;
if byte & 0x80 == 0 {
return Ok((value, rest));
}
shift += 7;
}
}
fn decode_string<'a>(
input: &'a [u8],
out: &mut heapless::String<MAX_STRING>,
) -> Result<&'a [u8], Error> {
let huffman_coded = input.first().ok_or(Error::Incomplete)? & 0x80 != 0;
let (len, rest) = decode_integer(input, 7)?;
let len = len as usize;
let bytes = rest.get(..len).ok_or(Error::Incomplete)?;
let mut buffer = [0u8; MAX_STRING];
let decoded: &[u8] = if huffman_coded {
let n = huffman::decode(bytes, &mut buffer)?;
&buffer[..n]
} else {
bytes
};
let text = core::str::from_utf8(decoded).map_err(|_| Error::Hpack)?;
out.push_str(text).map_err(|_| Error::BufferTooSmall)?;
Ok(&rest[len..])
}
pub use static_table::{DYNAMIC_BASE, ENTRIES as STATIC_ENTRIES};
#[cfg(test)]
mod tests {
use super::*;
fn hex(text: &str) -> heapless::Vec<u8, 256> {
let mut bytes = heapless::Vec::new();
for pair in text.as_bytes().chunks(2) {
bytes
.push(u8::from_str_radix(core::str::from_utf8(pair).unwrap(), 16).unwrap())
.unwrap();
}
bytes
}
fn decode_all(decoder: &mut Decoder, block: &str) -> heapless::Vec<heapless::String<256>, 16> {
let mut headers = heapless::Vec::new();
decoder
.decode(&hex(block), |name, value| {
let mut entry = heapless::String::<256>::new();
entry.push_str(name).unwrap();
entry.push_str(": ").unwrap();
entry.push_str(value).unwrap();
headers.push(entry).unwrap();
})
.unwrap();
headers
}
#[test]
fn decodes_integers_as_the_rfc_section_5_1_examples_do() {
assert_eq!(decode_integer(&[0x0a], 5).unwrap().0, 10);
assert_eq!(decode_integer(&[0x1f, 0x9a, 0x0a], 5).unwrap().0, 1337);
assert_eq!(decode_integer(&[0x2a], 8).unwrap().0, 42);
assert_eq!(decode_integer(&[0x1f], 5), Err(Error::Incomplete));
}
#[test]
fn decodes_the_rfc_appendix_c_3_request_sequence() {
let mut decoder = Decoder::new(4096);
let first = decode_all(&mut decoder, "828684410f7777772e6578616d706c652e636f6d");
assert_eq!(first[0].as_str(), ":method: GET");
assert_eq!(first[1].as_str(), ":scheme: http");
assert_eq!(first[2].as_str(), ":path: /");
assert_eq!(first[3].as_str(), ":authority: www.example.com");
assert_eq!(decoder.table().len(), 1);
let second = decode_all(&mut decoder, "828684be58086e6f2d6361636865");
assert_eq!(second[3].as_str(), ":authority: www.example.com");
assert_eq!(second[4].as_str(), "cache-control: no-cache");
assert_eq!(decoder.table().len(), 2);
let third = decode_all(
&mut decoder,
"828785bf400a637573746f6d2d6b65790c637573746f6d2d76616c7565",
);
assert_eq!(third[3].as_str(), ":authority: www.example.com");
assert_eq!(third[4].as_str(), "custom-key: custom-value");
assert_eq!(decoder.table().len(), 3);
}
#[test]
fn decodes_the_rfc_appendix_c_4_huffman_request_sequence() {
let mut decoder = Decoder::new(4096);
let first = decode_all(&mut decoder, "828684418cf1e3c2e5f23a6ba0ab90f4ff");
assert_eq!(first[3].as_str(), ":authority: www.example.com");
let second = decode_all(&mut decoder, "828684be5886a8eb10649cbf");
assert_eq!(second[4].as_str(), "cache-control: no-cache");
let third = decode_all(
&mut decoder,
"828785bf408825a849e95ba97d7f8925a849e95bb8e8b4bf",
);
assert_eq!(third[4].as_str(), "custom-key: custom-value");
}
#[test]
fn decodes_the_rfc_appendix_c_5_response_sequence_with_eviction() {
let mut decoder = Decoder::new(256);
let first = decode_all(
&mut decoder,
"4803333032580770726976617465611d4d6f6e2c203231204f637420323031332032303a31333a323120474d546e1768747470733a2f2f7777772e6578616d706c652e636f6d",
);
assert_eq!(first[0].as_str(), ":status: 302");
assert_eq!(first[3].as_str(), "location: https://www.example.com");
let second = decode_all(&mut decoder, "4803333037c1c0bf");
assert_eq!(second[0].as_str(), ":status: 307");
assert_eq!(second[1].as_str(), "cache-control: private");
assert_eq!(second[3].as_str(), "location: https://www.example.com");
let third = decode_all(
&mut decoder,
"88c1611d4d6f6e2c203231204f637420323031332032303a31333a323220474d54c05a04677a69707738666f6f3d4153444a4b48514b425a584f5157454f50495541585157454f49553b206d61782d6167653d333630303b2076657273696f6e3d31",
);
assert_eq!(third[0].as_str(), ":status: 200");
assert_eq!(third[4].as_str(), "content-encoding: gzip");
assert!(
third[5]
.as_str()
.starts_with("set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU")
);
}
#[test]
fn a_dynamic_table_size_update_is_applied_and_is_not_a_header() {
let mut decoder = Decoder::new(4096);
let headers = decode_all(&mut decoder, "2082");
assert_eq!(headers.len(), 1);
assert_eq!(headers[0].as_str(), ":method: GET");
assert_eq!(decoder.table().capacity(), 0);
}
#[test]
fn an_index_the_peer_never_defined_is_an_error() {
let mut decoder = Decoder::new(4096);
assert_eq!(decoder.decode(&hex("be"), |_, _| {}), Err(Error::Hpack));
assert_eq!(decoder.decode(&hex("ff00"), |_, _| {}), Err(Error::Hpack));
}
#[test]
fn a_truncated_block_is_incomplete_rather_than_a_short_header() {
let mut decoder = Decoder::new(4096);
assert_eq!(
decoder.decode(&hex("400a637573746f6d"), |_, _| {}),
Err(Error::Incomplete)
);
}
#[test]
fn never_indexed_headers_do_not_enter_the_table() {
let mut decoder = Decoder::new(4096);
let headers = decode_all(&mut decoder, "10012d0131");
assert_eq!(headers[0].as_str(), "-: 1");
assert!(
decoder.table().is_empty(),
"a never-indexed header must not be remembered"
);
}
}