use ::core::fmt;
use super::fse::SeqTable;
use super::huffman::HuffmanTable;
use super::sequences::{SymbolType, read_table};
use crate::decompressor::InvalidDataKind;
use crate::shared::format::REP_NUM;
const HEADER_SIZE: usize = 8;
#[derive(Clone)]
pub(crate) struct DecoderEntropy {
pub(crate) huffman: HuffmanTable,
pub(crate) literal_lengths: SeqTable,
pub(crate) offsets: SeqTable,
pub(crate) match_lengths: SeqTable,
pub(crate) reps: [u32; REP_NUM],
}
impl fmt::Debug for DecoderEntropy {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("DecoderEntropy")
.field("reps", &self.reps)
.finish_non_exhaustive()
}
}
impl DecoderEntropy {
pub(crate) fn load(dictionary: &[u8]) -> Result<(Self, usize), InvalidDataKind> {
if dictionary.len() <= HEADER_SIZE {
return Err(InvalidDataKind::HuffmanTable);
}
let mut position = HEADER_SIZE;
let rest = |position: usize| dictionary.get(position..).unwrap_or_default();
let mut huffman = HuffmanTable::default();
position += huffman.read(rest(position))?;
huffman.build_shared();
let (offsets, used) = read_table(SymbolType::Offsets, rest(position))?;
position += used;
let (match_lengths, used) = read_table(SymbolType::MatchLengths, rest(position))?;
position += used;
let (literal_lengths, used) = read_table(SymbolType::LiteralLengths, rest(position))?;
position += used;
let (reps, content) = rest(position)
.split_first_chunk::<{ 4 * REP_NUM }>()
.ok_or(InvalidDataKind::Offset)?;
let reps: [u32; REP_NUM] = ::core::array::from_fn(|index| {
let bytes = [
reps[4 * index],
reps[4 * index + 1],
reps[4 * index + 2],
reps[4 * index + 3],
];
u32::from_le_bytes(bytes)
});
let valid = |rep: &u32| *rep != 0 && *rep as usize <= content.len();
if !reps.iter().all(valid) {
return Err(InvalidDataKind::Offset);
}
let entropy = Self {
huffman,
literal_lengths,
offsets,
match_lengths,
reps,
};
Ok((entropy, position + 4 * REP_NUM))
}
pub(crate) fn retained_bytes(&self) -> usize {
self.huffman.retained_bytes()
+ self.literal_lengths.retained_bytes()
+ self.offsets.retained_bytes()
+ self.match_lengths.retained_bytes()
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct DictView<'a> {
pub(crate) content: &'a [u8],
pub(crate) id: u32,
pub(crate) entropy: Option<&'a DecoderEntropy>,
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use alloc::vec::Vec;
pub(crate) fn dictionary(reps: [u32; 3], content: &[u8]) -> Vec<u8> {
let mut bytes = 0xEC30_A437u32.to_le_bytes().to_vec();
bytes.extend_from_slice(&7u32.to_le_bytes());
bytes.extend_from_slice(&[128, 0x10]);
for _ in 0..3 {
bytes.extend_from_slice(&fse_single_symbol());
}
for rep in reps {
bytes.extend_from_slice(&rep.to_le_bytes());
}
bytes.extend_from_slice(content);
bytes
}
fn fse_single_symbol() -> Vec<u8> {
let bits: u32 = 63 << 4;
alloc::vec![bits as u8, (bits >> 8) as u8]
}
#[test]
fn a_minimal_dictionary_loads_its_tables_and_offsets() {
let bytes = dictionary([1, 4, 8], b"content!");
let (entropy, end) = DecoderEntropy::load(&bytes).expect("valid");
assert_eq!(end, bytes.len() - 8);
assert_eq!(entropy.reps, [1, 4, 8]);
assert_eq!(entropy.offsets.log(), 5);
assert!(entropy.retained_bytes() > 0);
assert!(alloc::format!("{entropy:?}").contains("reps"));
}
#[test]
fn repeat_offsets_must_be_non_zero_and_within_the_content() {
for reps in [[0, 1, 1], [1, 9, 1], [1, 1, u32::MAX]] {
let bytes = dictionary(reps, b"content!");
assert_eq!(
DecoderEntropy::load(&bytes).map(|_| ()),
Err(InvalidDataKind::Offset),
"{reps:?}"
);
}
let bytes = dictionary([8, 8, 8], b"content!");
assert!(DecoderEntropy::load(&bytes).is_ok());
}
#[test]
fn truncated_dictionaries_are_rejected() {
let bytes = dictionary([1, 1, 1], b"x");
for len in 0..bytes.len() - 1 {
assert!(DecoderEntropy::load(&bytes[..len]).is_err(), "length {len}");
}
assert!(DecoderEntropy::load(&bytes).is_ok());
}
}