zstd/dict.rs
1//! Train a dictionary from various sources.
2//!
3//! A dictionary can help improve the compression of small files.
4//! The dictionary must be present during decompression,
5//! but can be shared across multiple "similar" files.
6//!
7//! Creating a dictionary using the `zstd` C library,
8//! using the `zstd` command-line interface, using this library,
9//! or using the `train` binary provided, should give the same result,
10//! and are therefore completely compatible.
11//!
12//! To use, see [`Encoder::with_dictionary`] or [`Decoder::with_dictionary`].
13//!
14//! [`Encoder::with_dictionary`]: ../struct.Encoder.html#method.with_dictionary
15//! [`Decoder::with_dictionary`]: ../struct.Decoder.html#method.with_dictionary
16
17#[cfg(feature = "zdict_builder")]
18use std::io::{self, Read};
19
20pub use zstd_safe::{CDict, DDict};
21
22/// Prepared dictionary for compression
23///
24/// A dictionary can include its own copy of the data (if it is `'static`), or it can merely point
25/// to a separate buffer (if it has another lifetime).
26pub struct EncoderDictionary<'a> {
27 cdict: CDict<'a>,
28}
29
30/// The error for a dictionary zstd could not load.
31fn invalid_dictionary() -> std::io::Error {
32 std::io::Error::new(
33 std::io::ErrorKind::InvalidData,
34 "zstd could not load the dictionary",
35 )
36}
37
38impl EncoderDictionary<'static> {
39 /// Creates a prepared dictionary for compression.
40 ///
41 /// This will copy the dictionary internally.
42 ///
43 /// # Panics
44 ///
45 /// If zstd cannot load the dictionary - for instance, it starts with the
46 /// dictionary magic number but its header is corrupt. Use
47 /// [`EncoderDictionary::try_copy`] for a dictionary you did not make
48 /// yourself.
49 pub fn copy(dictionary: &[u8], level: i32) -> Self {
50 Self::try_copy(dictionary, level)
51 .expect("zstd returned null pointer when creating dict")
52 }
53
54 /// Creates a prepared dictionary for compression, or an error if zstd
55 /// cannot load it.
56 ///
57 /// This will copy the dictionary internally.
58 pub fn try_copy(dictionary: &[u8], level: i32) -> std::io::Result<Self> {
59 let cdict = zstd_safe::CDict::try_create(dictionary, level)
60 .ok_or_else(invalid_dictionary)?;
61 Ok(Self { cdict })
62 }
63}
64
65impl<'a> EncoderDictionary<'a> {
66 #[cfg(feature = "experimental")]
67 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
68 /// Create prepared dictionary for compression
69 ///
70 /// A level of `0` uses zstd's default (currently `3`).
71 ///
72 /// Only available with the `experimental` feature. Use `EncoderDictionary::copy` otherwise.
73 ///
74 /// # Panics
75 ///
76 /// If zstd cannot load the dictionary. Use [`EncoderDictionary::try_new`]
77 /// for a dictionary you did not make yourself.
78 pub fn new(dictionary: &'a [u8], level: i32) -> Self {
79 Self::try_new(dictionary, level).expect("zstd returned null pointer")
80 }
81
82 #[cfg(feature = "experimental")]
83 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
84 /// Create prepared dictionary for compression, or an error if zstd cannot
85 /// load it.
86 ///
87 /// Only available with the `experimental` feature. Use
88 /// `EncoderDictionary::try_copy` otherwise.
89 pub fn try_new(dictionary: &'a [u8], level: i32) -> std::io::Result<Self> {
90 let cdict =
91 zstd_safe::CDict::try_create_by_reference(dictionary, level)
92 .ok_or_else(invalid_dictionary)?;
93 Ok(Self { cdict })
94 }
95
96 /// Returns reference to `CDict` inner object
97 pub fn as_cdict(&self) -> &CDict<'a> {
98 &self.cdict
99 }
100}
101
102/// Prepared dictionary for decompression
103pub struct DecoderDictionary<'a> {
104 ddict: DDict<'a>,
105}
106
107impl DecoderDictionary<'static> {
108 /// Create a prepared dictionary for decompression.
109 ///
110 /// This will copy the dictionary internally.
111 ///
112 /// # Panics
113 ///
114 /// If zstd cannot load the dictionary - for instance, it starts with the
115 /// dictionary magic number but its header is corrupt. Use
116 /// [`DecoderDictionary::try_copy`] for a dictionary you did not make
117 /// yourself.
118 pub fn copy(dictionary: &[u8]) -> Self {
119 Self::try_copy(dictionary)
120 .expect("zstd returned null pointer when creating dict")
121 }
122
123 /// Create a prepared dictionary for decompression, or an error if zstd
124 /// cannot load it.
125 ///
126 /// This will copy the dictionary internally.
127 pub fn try_copy(dictionary: &[u8]) -> std::io::Result<Self> {
128 let ddict = zstd_safe::DDict::try_create(dictionary)
129 .ok_or_else(invalid_dictionary)?;
130 Ok(Self { ddict })
131 }
132}
133
134impl<'a> DecoderDictionary<'a> {
135 #[cfg(feature = "experimental")]
136 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
137 /// Create prepared dictionary for decompression
138 ///
139 /// Only available with the `experimental` feature. Use `DecoderDictionary::copy` otherwise.
140 ///
141 /// # Panics
142 ///
143 /// If zstd cannot load the dictionary. Use [`DecoderDictionary::try_new`]
144 /// for a dictionary you did not make yourself.
145 pub fn new(dict: &'a [u8]) -> Self {
146 Self::try_new(dict).expect("zstd returned null pointer")
147 }
148
149 #[cfg(feature = "experimental")]
150 #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
151 /// Create prepared dictionary for decompression, or an error if zstd
152 /// cannot load it.
153 ///
154 /// Only available with the `experimental` feature. Use
155 /// `DecoderDictionary::try_copy` otherwise.
156 pub fn try_new(dict: &'a [u8]) -> std::io::Result<Self> {
157 let ddict = zstd_safe::DDict::try_create_by_reference(dict)
158 .ok_or_else(invalid_dictionary)?;
159 Ok(Self { ddict })
160 }
161
162 /// Returns reference to `DDict` inner object
163 pub fn as_ddict(&self) -> &DDict<'a> {
164 &self.ddict
165 }
166}
167
168/// Train a dictionary from a big continuous chunk of data, with all samples
169/// contiguous in memory.
170///
171/// This is the most efficient way to train a dictionary,
172/// since this is directly fed into `zstd`.
173///
174/// * `sample_data` is the concatenation of all sample data.
175/// * `sample_sizes` is the size of each sample in `sample_data`.
176/// The sum of all `sample_sizes` should equal the length of `sample_data`.
177/// * `max_size` is the maximum size of the dictionary to generate.
178///
179/// The result is the dictionary data. You can, for example, feed it to [`CDict::create`].
180#[cfg(feature = "zdict_builder")]
181#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "zdict_builder")))]
182pub fn from_continuous(
183 sample_data: &[u8],
184 sample_sizes: &[usize],
185 max_size: usize,
186) -> io::Result<Vec<u8>> {
187 use crate::map_error_code;
188
189 // Complain if the lengths don't add up to the entire data.
190 // Add them with overflow detection: a wrapping sum would accept sizes
191 // reaching far outside of `sample_data`.
192 let total_size = sample_sizes
193 .iter()
194 .try_fold(0usize, |total, &size| total.checked_add(size));
195 if total_size != Some(sample_data.len()) {
196 return Err(io::Error::new(
197 io::ErrorKind::Other,
198 "sample sizes don't add up".to_string(),
199 ));
200 }
201
202 let mut result = Vec::with_capacity(max_size);
203 zstd_safe::train_from_buffer(&mut result, sample_data, sample_sizes)
204 .map_err(map_error_code)?;
205 Ok(result)
206}
207
208/// Train a dictionary from multiple samples.
209///
210/// The samples will internally be copied to a single continuous buffer,
211/// so make sure you have enough memory available.
212///
213/// If you need to stretch your system's limits,
214/// [`from_continuous`] directly uses the given slice.
215///
216/// [`from_continuous`]: ./fn.from_continuous.html
217///
218/// * `samples` is a list of individual samples to train on.
219/// * `max_size` is the maximum size of the dictionary to generate.
220///
221/// The result is the dictionary data. You can, for example, feed it to [`CDict::create`].
222#[cfg(feature = "zdict_builder")]
223#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "zdict_builder")))]
224pub fn from_samples<S: AsRef<[u8]>>(
225 samples: &[S],
226 max_size: usize,
227) -> io::Result<Vec<u8>> {
228 // Pre-allocate the entire required size.
229 let total_length: usize =
230 samples.iter().map(|sample| sample.as_ref().len()).sum();
231
232 let mut data = Vec::with_capacity(total_length);
233
234 // Copy every sample to a big chunk of memory
235 data.extend(samples.iter().flat_map(|s| s.as_ref()).cloned());
236
237 let sizes: Vec<_> = samples.iter().map(|s| s.as_ref().len()).collect();
238
239 from_continuous(&data, &sizes, max_size)
240}
241
242/// Train a dictionary from multiple samples.
243///
244/// Unlike [`from_samples`], this does not require having a list of all samples.
245/// It also allows running into an error when iterating through the samples.
246///
247/// They will still be copied to a continuous array and fed to [`from_continuous`].
248///
249/// * `samples` is an iterator of individual samples to train on.
250/// * `max_size` is the maximum size of the dictionary to generate.
251///
252/// The result is the dictionary data. You can, for example, feed it to [`CDict::create`].
253///
254/// # Examples
255///
256/// ```rust,no_run
257/// // Train from a couple of json files.
258/// let dict_buffer = zstd::dict::from_sample_iterator(
259/// ["file_a.json", "file_b.json"]
260/// .into_iter()
261/// .map(|filename| std::fs::File::open(filename)),
262/// 10_000, // 10kB dictionary
263/// ).unwrap();
264/// ```
265///
266/// ```rust,no_run
267/// use std::io::BufRead as _;
268/// // Treat each line from stdin as a separate sample.
269/// let dict_buffer = zstd::dict::from_sample_iterator(
270/// std::io::stdin().lock().lines().map(|line: std::io::Result<String>| {
271/// // Transform each line into a `Cursor<Vec<u8>>` so they implement Read.
272/// line.map(String::into_bytes)
273/// .map(std::io::Cursor::new)
274/// }),
275/// 10_000, // 10kB dictionary
276/// ).unwrap();
277/// ```
278#[cfg(feature = "zdict_builder")]
279#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "zdict_builder")))]
280pub fn from_sample_iterator<I, R>(
281 samples: I,
282 max_size: usize,
283) -> io::Result<Vec<u8>>
284where
285 I: IntoIterator<Item = io::Result<R>>,
286 R: Read,
287{
288 let mut data = Vec::new();
289 let mut sizes = Vec::new();
290
291 for sample in samples {
292 let mut sample = sample?;
293 let len = sample.read_to_end(&mut data)?;
294 sizes.push(len);
295 }
296
297 from_continuous(&data, &sizes, max_size)
298}
299
300/// Train a dict from a list of files.
301///
302/// * `filenames` is an iterator of files to load. Each file will be treated as an individual
303/// sample.
304/// * `max_size` is the maximum size of the dictionary to generate.
305///
306/// The result is the dictionary data. You can, for example, feed it to [`CDict::create`].
307#[cfg(feature = "zdict_builder")]
308#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "zdict_builder")))]
309pub fn from_files<I, P>(filenames: I, max_size: usize) -> io::Result<Vec<u8>>
310where
311 P: AsRef<std::path::Path>,
312 I: IntoIterator<Item = P>,
313{
314 from_sample_iterator(
315 filenames
316 .into_iter()
317 .map(|filename| std::fs::File::open(filename)),
318 max_size,
319 )
320}
321
322#[cfg(test)]
323#[cfg(feature = "zdict_builder")]
324mod tests {
325 use std::fs;
326 use std::io;
327 use std::io::Read;
328
329 use walkdir;
330
331 #[test]
332 fn test_dict_training() {
333 // Train a dictionary
334 let paths: Vec<_> = walkdir::WalkDir::new("src")
335 .into_iter()
336 .map(|entry| entry.unwrap())
337 .map(|entry| entry.into_path())
338 .filter(|path| path.to_str().unwrap().ends_with(".rs"))
339 .collect();
340
341 let dict = super::from_files(&paths, 4000).unwrap();
342
343 for path in paths {
344 let mut buffer = Vec::new();
345 let mut file = fs::File::open(path).unwrap();
346 let mut content = Vec::new();
347 file.read_to_end(&mut content).unwrap();
348 io::copy(
349 &mut &content[..],
350 &mut crate::stream::Encoder::with_dictionary(
351 &mut buffer,
352 1,
353 &dict,
354 )
355 .unwrap()
356 .auto_finish(),
357 )
358 .unwrap();
359
360 let mut result = Vec::new();
361 io::copy(
362 &mut crate::stream::Decoder::with_dictionary(
363 &buffer[..],
364 &dict[..],
365 )
366 .unwrap(),
367 &mut result,
368 )
369 .unwrap();
370
371 assert_eq!(&content, &result);
372 }
373 }
374
375 #[test]
376 fn test_from_continuous_overflowing_sizes() {
377 // These sizes only add up to the data length by wrapping around.
378 let data = [0u8; 100];
379 let sizes = [usize::MAX - 99, 200];
380 assert!(super::from_continuous(&data, &sizes, 4000).is_err());
381 }
382}