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}