Skip to main content

libzstd_rs_sys/lib/decompress/
zstd_ddict.rs

1use core::mem::MaybeUninit;
2use core::ptr::NonNull;
3use libc::size_t;
4
5use crate::lib::common::allocations::{ZSTD_customFree, ZSTD_customMalloc};
6use crate::lib::common::error_private::{ERR_isError, Error};
7use crate::lib::decompress::huf_decompress::DTableDesc;
8use crate::lib::decompress::zstd_decompress::ZSTD_loadDEntropy;
9use crate::lib::decompress::{ZSTD_DCtx, ZSTD_entropyDTables_t};
10use crate::lib::zstd::{
11    ZSTD_customMem, ZSTD_dct_auto, ZSTD_dct_fullDict, ZSTD_dct_rawContent, ZSTD_dictContentType_e,
12    ZSTD_dictLoadMethod_e, ZSTD_dlm_byCopy, ZSTD_dlm_byRef, ZSTD_MAGIC_DICTIONARY,
13};
14
15#[repr(u32)]
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum MultipleDDicts {
18    Single = 0,
19    Multiple = 1,
20}
21
22impl TryFrom<u32> for MultipleDDicts {
23    type Error = ();
24
25    fn try_from(value: u32) -> Result<Self, Self::Error> {
26        match value {
27            0 => Ok(Self::Single),
28            1 => Ok(Self::Multiple),
29            _ => Err(()),
30        }
31    }
32}
33
34#[repr(C)]
35pub struct ZSTD_DDictHashSet {
36    pub ddictPtrTable: *mut *const ZSTD_DDict,
37    pub ddictPtrTableSize: size_t,
38    pub ddictPtrCount: size_t,
39}
40
41impl ZSTD_DDictHashSet {
42    pub fn as_slice(&mut self) -> &[*const ZSTD_DDict] {
43        unsafe { core::slice::from_raw_parts(self.ddictPtrTable, self.ddictPtrCount) }
44    }
45}
46
47#[repr(C)]
48pub struct ZSTD_DDict {
49    dictBuffer: *mut core::ffi::c_void,
50    dictContent: *const core::ffi::c_void,
51    dictSize: size_t,
52    entropy: ZSTD_entropyDTables_t,
53    pub(crate) dictID: u32,
54    entropyPresent: u32,
55    cMem: ZSTD_customMem,
56}
57
58impl ZSTD_DDict {
59    pub fn as_slice(&self) -> &[u8] {
60        if self.dictContent.is_null() {
61            debug_assert_eq!(self.dictSize, 0);
62            &[]
63        } else {
64            unsafe { core::slice::from_raw_parts(self.dictContent.cast::<u8>(), self.dictSize) }
65        }
66    }
67}
68
69/// This enum represents [`ZSTD_dictLoadMethod_e`].
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71enum DictLoadMethod {
72    ByCopy = ZSTD_dlm_byCopy as _,
73    ByRef = ZSTD_dlm_byRef as _,
74}
75
76pub fn ZSTD_DDict_dictContent(ddict: &ZSTD_DDict) -> *const core::ffi::c_void {
77    ddict.dictContent
78}
79
80pub fn ZSTD_DDict_dictSize(ddict: &ZSTD_DDict) -> size_t {
81    ddict.dictSize
82}
83
84pub fn ZSTD_copyDDictParameters(dctx: &mut MaybeUninit<ZSTD_DCtx>, ddict: &ZSTD_DDict) {
85    let dctx = dctx.as_mut_ptr();
86
87    // SAFETY: we only write to the raw pointer, never read from it. The types guarantee that the
88    // writes are in-bounds and that we are allowed to write to this memory.
89    unsafe {
90        (*dctx).dictID = ddict.dictID;
91        (*dctx).prefixStart = ddict.dictContent;
92        (*dctx).virtualStart = ddict.dictContent;
93        (*dctx).dictEnd = (ddict.dictContent).wrapping_byte_add(ddict.dictSize);
94        (*dctx).previousDstEnd = (*dctx).dictEnd;
95
96        if ddict.entropyPresent != 0 {
97            (*dctx).litEntropy = true;
98            (*dctx).fseEntropy = true;
99            (*dctx).LLTptr = NonNull::new((&raw const ddict.entropy.LLTable).cast_mut());
100            (*dctx).MLTptr = NonNull::new((&raw const ddict.entropy.MLTable).cast_mut());
101            (*dctx).OFTptr = NonNull::new((&raw const ddict.entropy.OFTable).cast_mut());
102            (*dctx).HUFptr = NonNull::new((&raw const ddict.entropy.hufTable).cast_mut());
103            (*dctx).entropy.rep = ddict.entropy.rep;
104        } else {
105            (*dctx).litEntropy = false;
106            (*dctx).fseEntropy = false;
107        }
108    }
109}
110
111fn ZSTD_loadEntropy_intoDDict(
112    ddict: &mut ZSTD_DDict,
113    dictContentType: ZSTD_dictContentType_e,
114) -> Result<(), Error> {
115    ddict.dictID = 0;
116    ddict.entropyPresent = 0;
117
118    if dictContentType == ZSTD_dct_rawContent {
119        return Ok(());
120    }
121
122    let dict = if ddict.dictContent.is_null() {
123        &[]
124    } else {
125        unsafe { core::slice::from_raw_parts(ddict.dictContent.cast::<u8>(), ddict.dictSize) }
126    };
127
128    let ([magic, dict_id, ..], _) = dict.as_chunks::<4>() else {
129        if dictContentType == ZSTD_dct_fullDict {
130            return Err(Error::dictionary_corrupted);
131        }
132
133        return Ok(()); // pure content mode
134    };
135
136    let magic = u32::from_le_bytes(*magic);
137    if magic != ZSTD_MAGIC_DICTIONARY {
138        if dictContentType == ZSTD_dct_fullDict {
139            return Err(Error::dictionary_corrupted);
140        }
141
142        return Ok(()); // pure content mode
143    }
144
145    ddict.dictID = u32::from_le_bytes(*dict_id);
146
147    let ret = ZSTD_loadDEntropy(&mut ddict.entropy, dict);
148
149    if ERR_isError(ret) {
150        return Err(Error::dictionary_corrupted);
151    }
152
153    ddict.entropyPresent = 1;
154
155    Ok(())
156}
157
158fn ZSTD_initDDict_internal(
159    ddict: &mut ZSTD_DDict,
160    dict: *const core::ffi::c_void,
161    mut dictSize: size_t,
162    dictLoadMethod: ZSTD_dictLoadMethod_e,
163    dictContentType: ZSTD_dictContentType_e,
164) -> Result<(), Error> {
165    if dictLoadMethod == DictLoadMethod::ByRef as ZSTD_dictLoadMethod_e
166        || dict.is_null()
167        || dictSize == 0
168    {
169        ddict.dictBuffer = core::ptr::null_mut();
170        ddict.dictContent = dict;
171        if dict.is_null() {
172            dictSize = 0;
173        }
174    } else {
175        unsafe {
176            let internalBuffer = ZSTD_customMalloc(dictSize, ddict.cMem);
177            ddict.dictBuffer = internalBuffer;
178            ddict.dictContent = internalBuffer;
179            if internalBuffer.is_null() {
180                return Err(Error::dictionary_corrupted);
181            }
182            core::ptr::copy_nonoverlapping(dict, internalBuffer, dictSize);
183        }
184    }
185
186    ddict.dictSize = dictSize;
187    ddict.entropy.hufTable.description = DTableDesc::default();
188
189    ZSTD_loadEntropy_intoDDict(ddict, dictContentType)?;
190
191    Ok(())
192}
193
194#[cfg_attr(feature = "export-symbols", export_name = crate::prefix!(ZSTD_createDDict_advanced))]
195pub unsafe extern "C" fn ZSTD_createDDict_advanced(
196    dict: *const core::ffi::c_void,
197    dictSize: size_t,
198    dictLoadMethod: ZSTD_dictLoadMethod_e,
199    dictContentType: ZSTD_dictContentType_e,
200    customMem: ZSTD_customMem,
201) -> *mut ZSTD_DDict {
202    let ddict = ZSTD_customMalloc(size_of::<ZSTD_DDict>(), customMem) as *mut ZSTD_DDict;
203
204    if ddict.is_null() {
205        return core::ptr::null_mut();
206    }
207
208    (*ddict).cMem = customMem;
209    if ZSTD_initDDict_internal(
210        ddict.as_mut().unwrap(),
211        dict,
212        dictSize,
213        dictLoadMethod,
214        dictContentType,
215    )
216    .is_err()
217    {
218        ZSTD_freeDDict(ddict);
219        return core::ptr::null_mut();
220    }
221
222    ddict
223}
224
225/// Create a digested dictionary, to start decompression without startup delay.
226///
227/// The `dict`'s content is copied inside the [`ZSTD_DDict`], so `dict` can be released after
228/// [`ZSTD_DDict`] creation.
229///
230/// The [`ZSTD_DDict`] can be freed using [`ZSTD_freeDDict`].
231///
232/// # Returns
233///
234/// - a [`ZSTD_DDict`] if it was successfully created
235/// - NULL if there was an error creating the dictionary
236#[cfg_attr(feature = "export-symbols", export_name = crate::prefix!(ZSTD_createDDict))]
237pub unsafe extern "C" fn ZSTD_createDDict(
238    dict: *const core::ffi::c_void,
239    dictSize: size_t,
240) -> *mut ZSTD_DDict {
241    ZSTD_createDDict_advanced(
242        dict,
243        dictSize,
244        ZSTD_dlm_byCopy,
245        ZSTD_dct_auto,
246        ZSTD_customMem::default(),
247    )
248}
249
250/// Create a digested dictionary, to start decompression without startup delay.
251///
252/// Dictionary content is simply referenced, it will be accessed during decompression.
253/// `dictBuffer` must outlive [`ZSTD_DDict`] ([`ZSTD_DDict`] must be freed before `dictBuffer`)
254#[cfg_attr(feature = "export-symbols", export_name = crate::prefix!(ZSTD_createDDict_byReference))]
255pub unsafe extern "C" fn ZSTD_createDDict_byReference(
256    dictBuffer: *const core::ffi::c_void,
257    dictSize: size_t,
258) -> *mut ZSTD_DDict {
259    ZSTD_createDDict_advanced(
260        dictBuffer,
261        dictSize,
262        ZSTD_dlm_byRef,
263        ZSTD_dct_auto,
264        ZSTD_customMem::default(),
265    )
266}
267
268#[cfg_attr(feature = "export-symbols", export_name = crate::prefix!(ZSTD_initStaticDDict))]
269pub unsafe extern "C" fn ZSTD_initStaticDDict(
270    sBuffer: *mut core::ffi::c_void,
271    sBufferSize: size_t,
272    mut dict: *const core::ffi::c_void,
273    dictSize: size_t,
274    dictLoadMethod: ZSTD_dictLoadMethod_e,
275    dictContentType: ZSTD_dictContentType_e,
276) -> *const ZSTD_DDict {
277    debug_assert!(!sBuffer.is_null());
278    debug_assert!(!dict.is_null());
279
280    // sBuffer should be 8-aligned
281    if sBuffer as usize & 0b111 != 0 {
282        return core::ptr::null_mut();
283    }
284
285    if sBufferSize < ZSTD_estimateDDictSize(dictSize, dictLoadMethod) {
286        return core::ptr::null_mut();
287    }
288
289    let ddict = sBuffer as *mut ZSTD_DDict;
290    if dictLoadMethod == DictLoadMethod::ByCopy as ZSTD_dictLoadMethod_e {
291        core::ptr::copy_nonoverlapping(dict.cast::<u8>(), ddict.add(1).cast::<u8>(), dictSize); // local copy
292        dict = ddict.add(1) as *const core::ffi::c_void;
293    }
294
295    if ZSTD_initDDict_internal(
296        ddict.as_mut().unwrap(),
297        dict,
298        dictSize,
299        DictLoadMethod::ByRef as _,
300        dictContentType,
301    )
302    .is_err()
303    {
304        return core::ptr::null_mut();
305    }
306
307    ddict
308}
309
310/// Free the memory allocated with [`ZSTD_createDDict`].
311///
312/// If a NULL pointer is passed, no operation is performed.
313#[cfg_attr(feature = "export-symbols", export_name = crate::prefix!(ZSTD_freeDDict))]
314pub unsafe extern "C" fn ZSTD_freeDDict(ddict: *mut ZSTD_DDict) -> size_t {
315    if ddict.is_null() {
316        return 0;
317    }
318    let cMem = (*ddict).cMem;
319    ZSTD_customFree((*ddict).dictBuffer, (*ddict).dictSize, cMem);
320    ZSTD_customFree(
321        ddict as *mut core::ffi::c_void,
322        size_of::<ZSTD_DDict>(),
323        cMem,
324    );
325    0
326}
327
328/// Estimate amount of memory that will be needed to create a dictionary for decompression.
329///
330/// Note: dictionary created by reference using [`ZSTD_dlm_byRef`] are smaller
331#[cfg_attr(feature = "export-symbols", export_name = crate::prefix!(ZSTD_estimateDDictSize))]
332pub const extern "C" fn ZSTD_estimateDDictSize(
333    dict_size: size_t,
334    dict_load_method: ZSTD_dictLoadMethod_e,
335) -> size_t {
336    if dict_load_method == ZSTD_dlm_byRef as ZSTD_dictLoadMethod_e {
337        size_of::<ZSTD_DDict>()
338    } else {
339        size_of::<ZSTD_DDict>() + dict_size
340    }
341}
342
343/// Get the _current_ memory usage of the [`ZSTD_DDict`]
344///
345/// # Returns
346///
347/// - the size of the [`ZSTD_DDict`], including the size of the [`ZSTD_DDict`]'s `dictBuffer` if present
348/// - 0 if the `ddict` is NULL
349#[cfg_attr(feature = "export-symbols", export_name = crate::prefix!(ZSTD_sizeof_DDict))]
350pub unsafe extern "C" fn ZSTD_sizeof_DDict(ddict: *const ZSTD_DDict) -> size_t {
351    if ddict.is_null() {
352        return 0;
353    }
354    (::core::mem::size_of::<ZSTD_DDict>()).wrapping_add(if !((*ddict).dictBuffer).is_null() {
355        (*ddict).dictSize
356    } else {
357        0
358    })
359}
360
361/// Provides the `dictID` of the dictionary loaded into [`ZSTD_DDict`].
362///
363/// If it returns 0, the dictionary is not conformant to Zstandard specification, or empty.
364/// Non-conformant dictionaries can still be loaded, but as content-only dictionaries.
365#[cfg_attr(feature = "export-symbols", export_name = crate::prefix!(ZSTD_getDictID_fromDDict))]
366pub unsafe extern "C" fn ZSTD_getDictID_fromDDict(ddict: *const ZSTD_DDict) -> core::ffi::c_uint {
367    if ddict.is_null() {
368        return 0;
369    }
370    (*ddict).dictID
371}
372
373#[cfg(test)]
374mod tests {
375    use super::*;
376
377    #[test]
378    fn test_estimate_ddict_size() {
379        assert_eq!(
380            ZSTD_estimateDDictSize(1234, ZSTD_dlm_byCopy),
381            size_of::<ZSTD_DDict>() + 1234
382        );
383        assert_eq!(
384            ZSTD_estimateDDictSize(1234, ZSTD_dlm_byRef),
385            size_of::<ZSTD_DDict>()
386        );
387    }
388}