use std::borrow::Cow;
use super::{
DecoderError, decode_integer, huffman,
table::{DEFAULT_MAX_SIZE, DYNAMIC_TABLE_START, DynamicTable, STATIC_TABLE},
};
const MAX_LEADING_SIZE_UPDATES: usize = 2;
#[derive(Clone, Copy)]
enum Source {
Input(usize, usize),
Scratch(usize, usize),
Static(&'static [u8]),
}
#[derive(Clone, Copy)]
enum Name {
Source(Source),
Dynamic(usize),
}
#[derive(Debug)]
pub struct Decoder {
table: DynamicTable,
max_allowed_size: usize,
scratch: Vec<u8>,
}
impl Default for Decoder {
fn default() -> Self {
Self::new()
}
}
impl Decoder {
pub const fn new() -> Self {
Decoder {
table: DynamicTable::new(DEFAULT_MAX_SIZE),
max_allowed_size: usize::MAX,
scratch: Vec::new(),
}
}
pub fn set_max_allowed_table_size(&mut self, max_allowed_size: usize) {
self.max_allowed_size = max_allowed_size;
}
pub fn set_max_table_size(&mut self, max_size: usize) {
self.table.set_max_size(max_size);
}
#[cfg(test)]
pub(super) fn table(&self) -> &DynamicTable {
&self.table
}
pub fn shrink_scratch(&mut self, retain: usize) {
if self.scratch.capacity() > retain {
self.scratch = Vec::new();
}
}
pub fn decode_with_cb<F>(&mut self, block: &[u8], mut callback: F) -> Result<(), DecoderError>
where
F: FnMut(Cow<'_, [u8]>, Cow<'_, [u8]>),
{
let mut position = 0;
let mut fields = 0usize;
let mut leading_size_updates = 0;
let mut last_was_size_update = false;
while position < block.len() {
let first = block[position];
last_was_size_update = false;
if first & 0x80 != 0 {
let (index, consumed) = decode_integer(&block[position..], 7)?;
let (name, value) = self.lookup(index)?;
callback(Cow::Borrowed(name), Cow::Borrowed(value));
position += consumed;
fields += 1;
} else if first & 0x40 != 0 {
position = self.literal(block, position, 6, true, &mut callback)?;
fields += 1;
} else if first & 0x20 != 0 {
if fields > 0 {
return Err(DecoderError::SizeUpdateNotAtStart);
}
leading_size_updates += 1;
if leading_size_updates > MAX_LEADING_SIZE_UPDATES {
return Err(DecoderError::TooManySizeUpdates);
}
let (size, consumed) = decode_integer(&block[position..], 5)?;
if size > self.max_allowed_size {
return Err(DecoderError::TableSizeTooLarge);
}
self.table.set_max_size(size);
position += consumed;
last_was_size_update = true;
} else {
position = self.literal(block, position, 4, false, &mut callback)?;
fields += 1;
}
}
if last_was_size_update {
return Err(DecoderError::SizeUpdateAtEnd);
}
Ok(())
}
fn lookup(&self, index: usize) -> Result<(&[u8], &[u8]), DecoderError> {
match index {
0 => Err(DecoderError::InvalidIndex),
1..DYNAMIC_TABLE_START => Ok(STATIC_TABLE[index - 1]),
_ => self
.table
.get(index - DYNAMIC_TABLE_START)
.ok_or(DecoderError::InvalidIndex),
}
}
fn literal<F>(
&mut self,
block: &[u8],
position: usize,
prefix: u8,
indexing: bool,
callback: &mut F,
) -> Result<usize, DecoderError>
where
F: FnMut(Cow<'_, [u8]>, Cow<'_, [u8]>),
{
self.scratch.clear();
let (index, consumed) = decode_integer(&block[position..], prefix)?;
let mut position = position + consumed;
let name = match index {
0 => {
let (name, next) = decode_string(block, position, &mut self.scratch)?;
position = next;
Name::Source(name)
}
1..DYNAMIC_TABLE_START => Name::Source(Source::Static(STATIC_TABLE[index - 1].0)),
_ => {
let dynamic = index - DYNAMIC_TABLE_START;
let (name, _) = self.table.get(dynamic).ok_or(DecoderError::InvalidIndex)?;
if indexing {
self.scratch.extend_from_slice(name);
Name::Source(Source::Scratch(0, self.scratch.len()))
} else {
Name::Dynamic(dynamic)
}
}
};
let (value, position) = decode_string(block, position, &mut self.scratch)?;
let value = resolve(value, block, &self.scratch);
match name {
Name::Source(name) => {
let name = resolve(name, block, &self.scratch);
callback(Cow::Borrowed(name), Cow::Borrowed(value));
if indexing {
self.table.insert(name, value);
}
}
Name::Dynamic(index) => {
let name = self
.table
.get(index)
.map(|(name, _)| name)
.ok_or(DecoderError::InvalidIndex)?;
callback(Cow::Borrowed(name), Cow::Borrowed(value));
}
}
Ok(position)
}
}
fn decode_string(
block: &[u8],
position: usize,
scratch: &mut Vec<u8>,
) -> Result<(Source, usize), DecoderError> {
let rest = &block[position..];
let Some(&first) = rest.first() else {
return Err(DecoderError::Truncated);
};
let (length, consumed) = decode_integer(rest, 7)?;
if length > rest.len() - consumed {
return Err(DecoderError::Truncated);
}
let start = position + consumed;
let end = start + length;
if first & 0x80 == 0 {
return Ok((Source::Input(start, end), end));
}
let scratch_start = scratch.len();
huffman::decode(&block[start..end], scratch).map_err(DecoderError::Huffman)?;
Ok((Source::Scratch(scratch_start, scratch.len()), end))
}
fn resolve<'a>(source: Source, block: &'a [u8], scratch: &'a [u8]) -> &'a [u8] {
match source {
Source::Input(start, end) => &block[start..end],
Source::Scratch(start, end) => &scratch[start..end],
Source::Static(name) => name,
}
}