mod decoder;
mod encoder;
pub mod huffman;
mod table;
#[cfg(test)]
mod tests;
pub use decoder::Decoder;
pub use encoder::{Encoder, Representation};
pub use huffman::HuffmanError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum DecoderError {
#[error("header block truncated")]
Truncated,
#[error("integer encoded on more than five octets")]
IntegerOverflow,
#[error("index outside the static and dynamic tables")]
InvalidIndex,
#[error("invalid Huffman string: {0}")]
Huffman(HuffmanError),
#[error("dynamic table size update above SETTINGS_HEADER_TABLE_SIZE")]
TableSizeTooLarge,
#[error("dynamic table size update after a header field")]
SizeUpdateNotAtStart,
#[error("more than two dynamic table size updates")]
TooManySizeUpdates,
#[error("dynamic table size update at the end of a header block")]
SizeUpdateAtEnd,
}
const MAX_INTEGER_OCTETS: usize = 5;
fn decode_integer(input: &[u8], prefix_bits: u8) -> Result<(usize, usize), DecoderError> {
debug_assert!(
(1..=8).contains(&prefix_bits),
"an HPACK prefix is 1 to 8 bits"
);
let Some(&first) = input.first() else {
return Err(DecoderError::Truncated);
};
let mask = ((1u16 << prefix_bits) - 1) as u8;
let mut value = usize::from(first & mask);
if value < usize::from(mask) {
return Ok((value, 1));
}
for (position, &octet) in input.iter().enumerate().skip(1) {
if position >= MAX_INTEGER_OCTETS {
return Err(DecoderError::IntegerOverflow);
}
value += usize::from(octet & 0x7f) << (7 * (position - 1));
if octet & 0x80 == 0 {
debug_assert!(position < MAX_INTEGER_OCTETS, "the octet bound holds");
return Ok((value, position + 1));
}
}
Err(DecoderError::Truncated)
}
pub fn encode_integer(value: usize, prefix_bits: u8, flags: u8, out: &mut Vec<u8>) {
debug_assert!(
(1..=8).contains(&prefix_bits),
"an HPACK prefix is 1 to 8 bits"
);
let mask = ((1u16 << prefix_bits) - 1) as u8;
debug_assert!(flags & mask == 0, "flags do not overlap the prefix");
if value < usize::from(mask) {
out.push(flags | value as u8);
return;
}
out.push(flags | mask);
let mut rest = value - usize::from(mask);
while rest >= 0x80 {
out.push((rest & 0x7f) as u8 | 0x80);
rest >>= 7;
}
out.push(rest as u8);
}