use crate::error::{Error, Result};
use crate::fse::{ncount, Norm};
use crate::huf::{weights, HufWeights};
use alloc::vec::Vec;
pub const DICT_MAGIC: u32 = 0xEC30_A437;
#[derive(Clone, Debug)]
pub struct DictTables {
pub huf: HufWeights,
pub of: Norm,
pub ml: Norm,
pub ll: Norm,
}
#[derive(Clone, Debug)]
pub struct Dictionary {
pub(crate) id: u32,
pub(crate) content: Vec<u8>,
pub(crate) tables: Option<DictTables>,
pub(crate) reps: [u32; 3],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DictionaryFormat {
Zstd,
Raw {
id: u32,
},
Detect,
}
impl Dictionary {
pub fn new(bytes: &[u8], format: DictionaryFormat) -> Result<Dictionary> {
let has_magic = bytes.len() >= 8 && u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) == DICT_MAGIC;
match format {
DictionaryFormat::Zstd if has_magic => Self::parse_zstd(bytes),
DictionaryFormat::Zstd => Err(Error::BadDictionary("missing zstd dictionary magic")),
DictionaryFormat::Raw { id } => Ok(Self::raw(bytes, id)),
DictionaryFormat::Detect if has_magic => Self::parse_zstd(bytes),
DictionaryFormat::Detect => Ok(Self::raw(bytes, 0)),
}
}
fn raw(bytes: &[u8], id: u32) -> Dictionary {
Dictionary { id, content: bytes.to_vec(), tables: None, reps: [1, 4, 8] }
}
fn parse_zstd(bytes: &[u8]) -> Result<Dictionary> {
let id = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
let mut at = 8;
let bad = |e: Error| match e {
Error::Corrupt(why) => Error::BadDictionary(why),
Error::Truncated => Error::BadDictionary("truncated entropy tables"),
other => other,
};
let (huf, used) = weights::read(&bytes[at..]).map_err(bad)?;
at += used;
let (of, used) = ncount::read(&bytes[at..], 32, 8).map_err(bad)?;
at += used;
let (ml, used) = ncount::read(&bytes[at..], 53, 9).map_err(bad)?;
at += used;
let (ll, used) = ncount::read(&bytes[at..], 36, 9).map_err(bad)?;
at += used;
let rep_bytes = bytes.get(at..at + 12).ok_or(Error::BadDictionary("truncated repeat offsets"))?;
let mut reps = [0u32; 3];
for (i, r) in reps.iter_mut().enumerate() {
let b = &rep_bytes[4 * i..4 * i + 4];
*r = u32::from_le_bytes([b[0], b[1], b[2], b[3]]);
}
let content = bytes[at + 12..].to_vec();
for &r in &reps {
if r == 0 || r as usize > content.len() {
return Err(Error::BadDictionary("repeat offset outside content"));
}
}
Ok(Dictionary { id, content, tables: Some(DictTables { huf, of, ml, ll }), reps })
}
pub fn id(&self) -> u32 {
self.id
}
pub fn content(&self) -> &[u8] {
&self.content
}
pub fn has_entropy_tables(&self) -> bool {
self.tables.is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn raw_and_detect() {
let d = Dictionary::new(b"hello world", DictionaryFormat::Detect).unwrap();
assert_eq!((d.id(), d.content(), d.has_entropy_tables()), (0, &b"hello world"[..], false));
assert!(Dictionary::new(b"hello world", DictionaryFormat::Zstd).is_err());
let r = Dictionary::new(b"x", DictionaryFormat::Raw { id: 9 }).unwrap();
assert_eq!(r.id(), 9);
}
#[test]
fn truncated_zstd_dict_is_error() {
let mut b = DICT_MAGIC.to_le_bytes().to_vec();
b.extend_from_slice(&[1, 0, 0, 0, 0x80]);
assert!(matches!(Dictionary::new(&b, DictionaryFormat::Zstd), Err(Error::BadDictionary(_))));
}
}