Skip to main content

libzstd_bitexact_rs/
dictionary.rs

1//! Dictionary parsing (`ZSTD_loadDEntropy` / raw-content dictionaries).
2//!
3//! A Zstandard dictionary is either:
4//!
5//! * **Formatted** (the `ZDICT` / trained format): the magic number
6//!   `0xEC30A437`, a 4-byte dictionary ID, the entropy tables (one Huffman
7//!   table, then three FSE tables in offset / match-length / literal-length
8//!   order), three 32-bit repeat offsets, and finally the dictionary content.
9//! * **Raw content**: any other buffer, used verbatim as window history with
10//!   no precomputed entropy tables and the default repeat offsets.
11//!
12//! The entropy tables and repeat offsets seed the per-frame decoding context
13//! exactly as `ZSTD_decompress_insertDictionary` does in the C sources, so the
14//! first block of a dictionary-compressed frame may use `Repeat_Mode` tables
15//! and dictionary-relative repeat offsets.
16
17use crate::block::{LL_SPEC, ML_SPEC, OF_SPEC};
18use crate::error::Error;
19use crate::fse::{self, FseTable};
20use crate::huffman::{self, HuffmanTable};
21
22/// `ZSTD_MAGIC_DICTIONARY`.
23const DICT_MAGIC: u32 = 0xEC30_A437;
24
25/// A parsed Zstandard dictionary, ready to seed decompression.
26///
27/// Build one with [`Dictionary::new`] (which auto-detects the formatted and
28/// raw-content cases) or [`Dictionary::raw_content`]. Pass it to
29/// [`DecodeOptions::dictionary`](crate::DecodeOptions::dictionary).
30pub struct Dictionary {
31    id: u32,
32    content: Vec<u8>,
33    huffman: Option<HuffmanTable>,
34    ll: Option<FseTable>,
35    of: Option<FseTable>,
36    ml: Option<FseTable>,
37    rep: [u64; 3],
38}
39
40impl Dictionary {
41    /// Parse a dictionary buffer.
42    ///
43    /// A buffer that begins with the `ZDICT` magic number is parsed as a
44    /// formatted dictionary; anything else is treated as raw content, matching
45    /// the C library's `ZSTD_isFrameMagic`/raw-content fallback. A buffer that
46    /// begins with the magic but is malformed yields [`Error::DictionaryCorrupted`].
47    pub fn new(bytes: &[u8]) -> Result<Self, Error> {
48        if bytes.len() >= 8 && u32::from_le_bytes(bytes[..4].try_into().unwrap()) == DICT_MAGIC {
49            Self::parse_formatted(bytes)
50        } else {
51            Ok(Self::raw_content(bytes))
52        }
53    }
54
55    /// Treat `content` as a raw-content dictionary: window history with the
56    /// default repeat offsets and no precomputed entropy tables. The
57    /// dictionary ID is zero.
58    pub fn raw_content(content: &[u8]) -> Self {
59        Dictionary {
60            id: 0,
61            content: content.to_vec(),
62            huffman: None,
63            ll: None,
64            of: None,
65            ml: None,
66            // `repStartValue`, as for a frame with no dictionary.
67            rep: [1, 4, 8],
68        }
69    }
70
71    /// The dictionary ID, or zero for a raw-content dictionary (or a formatted
72    /// dictionary that declares ID zero).
73    pub fn id(&self) -> u32 {
74        self.id
75    }
76
77    fn parse_formatted(bytes: &[u8]) -> Result<Self, Error> {
78        let corrupt = Error::DictionaryCorrupted;
79        let id = u32::from_le_bytes(bytes[4..8].try_into().unwrap());
80        let mut pos = 8usize;
81
82        // Entropy tables, in the order `ZSTD_loadDEntropy` reads them: one
83        // Huffman table for literals, then the offset, match-length, and
84        // literal-length FSE tables.
85        let (huffman, used) =
86            huffman::read_table(bytes.get(pos..).ok_or(corrupt)?).map_err(|_| corrupt)?;
87        pos += used;
88        let of = read_dict_fse(bytes, &mut pos, &OF_SPEC)?;
89        let ml = read_dict_fse(bytes, &mut pos, &ML_SPEC)?;
90        let ll = read_dict_fse(bytes, &mut pos, &LL_SPEC)?;
91
92        // Three 32-bit repeat offsets precede the content.
93        let reps = bytes.get(pos..pos + 12).ok_or(corrupt)?;
94        let content = bytes[pos + 12..].to_vec();
95        let mut rep = [0u64; 3];
96        for (i, slot) in rep.iter_mut().enumerate() {
97            let r = u32::from_le_bytes(reps[4 * i..4 * i + 4].try_into().unwrap()) as u64;
98            // `if (rep==0 || rep > dictContentSize) corrupted` in the C loader.
99            if r == 0 || r > content.len() as u64 {
100                return Err(corrupt);
101            }
102            *slot = r;
103        }
104
105        Ok(Dictionary {
106            id,
107            content,
108            huffman: Some(huffman),
109            ll: Some(ll),
110            of: Some(of),
111            ml: Some(ml),
112            rep,
113        })
114    }
115
116    pub(crate) fn content(&self) -> &[u8] {
117        &self.content
118    }
119
120    pub(crate) fn huffman(&self) -> Option<&HuffmanTable> {
121        self.huffman.as_ref()
122    }
123
124    pub(crate) fn ll(&self) -> Option<&FseTable> {
125        self.ll.as_ref()
126    }
127
128    pub(crate) fn of(&self) -> Option<&FseTable> {
129        self.of.as_ref()
130    }
131
132    pub(crate) fn ml(&self) -> Option<&FseTable> {
133        self.ml.as_ref()
134    }
135
136    pub(crate) fn rep(&self) -> [u64; 3] {
137        self.rep
138    }
139}
140
141/// Read one FSE table description from a dictionary's entropy section,
142/// advancing `pos`. Maps any parse failure to [`Error::DictionaryCorrupted`],
143/// since a formatted dictionary that fails here is malformed rather than a
144/// truncated frame.
145fn read_dict_fse(
146    bytes: &[u8],
147    pos: &mut usize,
148    spec: &crate::block::SeqTableSpec,
149) -> Result<FseTable, Error> {
150    let corrupt = Error::DictionaryCorrupted;
151    let nc = fse::read_ncount(
152        bytes.get(*pos..).ok_or(corrupt)?,
153        spec.max_symbol,
154        spec.max_log,
155    )
156    .map_err(|_| corrupt)?;
157    let table = fse::build_dtable(&nc.counts, nc.table_log).map_err(|_| corrupt)?;
158    *pos += nc.bytes_consumed;
159    Ok(table)
160}