--- /dev/null
+#![no_std]
+//! Minimal safe wrapper around zstd-sys.
+//!
+//! This crates provides a minimal translation of the [zstd-sys] methods.
+//! For a more comfortable high-level library, see the [zstd] crate.
+//!
+//! [zstd-sys]: https://crates.io/crates/zstd-sys
+//! [zstd]: https://crates.io/crates/zstd
+//!
+//! Most of the functions here map 1-for-1 to a function from
+//! [the C zstd library][zstd-c] mentioned in their descriptions.
+//! Check the [source documentation][doc] for more information on their
+//! behaviour.
+//!
+//! [doc]: https://facebook.github.io/zstd/zstd_manual.html
+//! [zstd-c]: https://facebook.github.io/zstd/
+//!
+//! Features denoted as experimental in the C library are hidden behind an
+//! `experimental` feature.
+#![cfg_attr(feature = "doc-cfg", feature(doc_cfg))]
+
+#[cfg(feature = "std")]
+extern crate std;
+
+#[cfg(test)]
+mod tests;
+
+// Re-export zstd-sys
+pub use zstd_sys;
+
+/// How to compress data.
+pub use zstd_sys::ZSTD_strategy as Strategy;
+
+/// Reset directive.
+// pub use zstd_sys::ZSTD_ResetDirective as ResetDirective;
+
+#[cfg(feature = "std")]
+use std::os::raw::{c_char, c_int, c_ulonglong, c_void};
+
+#[cfg(not(feature = "std"))]
+use libc::{c_char, c_int, c_ulonglong, c_void};
+
+use core::marker::PhantomData;
+use core::num::{NonZeroU32, NonZeroU64};
+use core::ops::{Deref, DerefMut};
+use core::ptr::NonNull;
+use core::str;
+
+include!("constants.rs");
+
+#[cfg(feature = "experimental")]
+include!("constants_experimental.rs");
+
+/// Represents the compression level used by zstd.
+pub type CompressionLevel = i32;
+
+/// Represents a possible error from the zstd library.
+pub type ErrorCode = usize;
+
+/// Wrapper result around most zstd functions.
+///
+/// Either a success code (usually number of bytes written), or an error code.
+pub type SafeResult = Result<usize, ErrorCode>;
+
+/// Indicates an error happened when parsing the frame content size.
+///
+/// The stream may be corrupted, or the given frame prefix was too small.
+#[derive(Debug)]
+pub struct ContentSizeError;
+
+impl core::fmt::Display for ContentSizeError {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ f.write_str("Could not get content size")
+ }
+}
+
+/// Returns true if code represents error.
+fn is_error(code: usize) -> bool {
+ // Safety: Just FFI
+ unsafe { zstd_sys::ZSTD_isError(code) != 0 }
+}
+
+/// Parse the result code
+///
+/// Returns the number of bytes written if the code represents success,
+/// or the error message code otherwise.
+fn parse_code(code: usize) -> SafeResult {
+ if !is_error(code) {
+ Ok(code)
+ } else {
+ Err(code)
+ }
+}
+
+/// Parse a content size value.
+///
+/// zstd uses 2 special content size values to indicate either unknown size or parsing error.
+fn parse_content_size(
+ content_size: u64,
+) -> Result<Option<u64>, ContentSizeError> {
+ match content_size {
+ CONTENTSIZE_ERROR => Err(ContentSizeError),
+ CONTENTSIZE_UNKNOWN => Ok(None),
+ other => Ok(Some(other)),
+ }
+}
+
+fn ptr_void(src: &[u8]) -> *const c_void {
+ src.as_ptr() as *const c_void
+}
+
+fn ptr_mut_void(dst: &mut (impl WriteBuf + ?Sized)) -> *mut c_void {
+ dst.as_mut_ptr() as *mut c_void
+}
+
+/// Returns the ZSTD version.
+///
+/// Returns `major * 10_000 + minor * 100 + patch`.
+/// So 1.5.3 would be returned as `10_503`.
+pub fn version_number() -> u32 {
+ // Safety: Just FFI
+ unsafe { zstd_sys::ZSTD_versionNumber() as u32 }
+}
+
+/// Returns a string representation of the ZSTD version.
+///
+/// For example "1.5.3".
+pub fn version_string() -> &'static str {
+ // Safety: Assumes `ZSTD_versionString` returns a valid utf8 string.
+ unsafe { c_char_to_str(zstd_sys::ZSTD_versionString()) }
+}
+
+/// Returns the minimum (fastest) compression level supported.
+///
+/// This is likely going to be a _very_ large negative number.
+pub fn min_c_level() -> CompressionLevel {
+ // Safety: Just FFI
+ unsafe { zstd_sys::ZSTD_minCLevel() as CompressionLevel }
+}
+
+/// Returns the maximum (slowest) compression level supported.
+pub fn max_c_level() -> CompressionLevel {
+ // Safety: Just FFI
+ unsafe { zstd_sys::ZSTD_maxCLevel() as CompressionLevel }
+}
+
+/// Wraps the `ZSTD_compress` function.
+///
+/// This will try to compress `src` entirely and write the result to `dst`, returning the number of
+/// bytes written.
+///
+/// For streaming operations that don't require to store the entire input/ouput in memory, see
+/// `compress_stream`.
+pub fn compress<C: WriteBuf + ?Sized>(
+ dst: &mut C,
+ src: &[u8],
+ compression_level: CompressionLevel,
+) -> SafeResult {
+ // Safety: ZSTD_compress indeed returns how many bytes have been written.
+ unsafe {
+ dst.write_from(|buffer, capacity| {
+ parse_code(zstd_sys::ZSTD_compress(
+ buffer,
+ capacity,
+ ptr_void(src),
+ src.len(),
+ compression_level,
+ ))
+ })
+ }
+}
+
+/// Wraps the `ZSTD_decompress` function.
+pub fn decompress<C: WriteBuf + ?Sized>(
+ dst: &mut C,
+ src: &[u8],
+) -> SafeResult {
+ // Safety: ZSTD_decompress indeed returns how many bytes have been written.
+ unsafe {
+ dst.write_from(|buffer, capacity| {
+ parse_code(zstd_sys::ZSTD_decompress(
+ buffer,
+ capacity,
+ ptr_void(src),
+ src.len(),
+ ))
+ })
+ }
+}
+
+/// Wraps the `ZSTD_getDecompressedSize` function.
+///
+/// Returns `None` if the size could not be found, or if the content is actually empty.
+#[deprecated(note = "Use ZSTD_getFrameContentSize instead")]
+pub fn get_decompressed_size(src: &[u8]) -> Option<NonZeroU64> {
+ // Safety: Just FFI
+ NonZeroU64::new(unsafe {
+ zstd_sys::ZSTD_getDecompressedSize(ptr_void(src), src.len()) as u64
+ })
+}
+
+/// Maximum compressed size in worst case single-pass scenario
+pub fn compress_bound(src_size: usize) -> usize {
+ // Safety: Just FFI
+ unsafe { zstd_sys::ZSTD_compressBound(src_size) }
+}
+
+/// Compression context
+///
+/// It is recommended to allocate a single context per thread and re-use it
+/// for many compression operations.
+pub struct CCtx<'a>(NonNull<zstd_sys::ZSTD_CCtx>, PhantomData<&'a ()>);
+
+impl Default for CCtx<'_> {
+ fn default() -> Self {
+ CCtx::create()
+ }
+}
+
+impl<'a> CCtx<'a> {
+ /// Tries to create a new context.
+ ///
+ /// Returns `None` if zstd returns a NULL pointer - may happen if allocation fails.
+ pub fn try_create() -> Option<Self> {
+ // Safety: Just FFI
+ Some(CCtx(
+ NonNull::new(unsafe { zstd_sys::ZSTD_createCCtx() })?,
+ PhantomData,
+ ))
+ }
+
+ /// Wrap `ZSTD_createCCtx`
+ ///
+ /// # Panics
+ ///
+ /// If zstd returns a NULL pointer.
+ pub fn create() -> Self {
+ Self::try_create()
+ .expect("zstd returned null pointer when creating new context")
+ }
+
+ /// Wraps the `ZSTD_compressCCtx()` function
+ pub fn compress<C: WriteBuf + ?Sized>(
+ &mut self,
+ dst: &mut C,
+ src: &[u8],
+ compression_level: CompressionLevel,
+ ) -> SafeResult {
+ // Safety: ZSTD_compressCCtx returns how many bytes were written.
+ unsafe {
+ dst.write_from(|buffer, capacity| {
+ parse_code(zstd_sys::ZSTD_compressCCtx(
+ self.0.as_ptr(),
+ buffer,
+ capacity,
+ ptr_void(src),
+ src.len(),
+ compression_level,
+ ))
+ })
+ }
+ }
+
+ /// Wraps the `ZSTD_compress2()` function.
+ pub fn compress2<C: WriteBuf + ?Sized>(
+ &mut self,
+ dst: &mut C,
+ src: &[u8],
+ ) -> SafeResult {
+ // Safety: ZSTD_compress2 returns how many bytes were written.
+ unsafe {
+ dst.write_from(|buffer, capacity| {
+ parse_code(zstd_sys::ZSTD_compress2(
+ self.0.as_ptr(),
+ buffer,
+ capacity,
+ ptr_void(src),
+ src.len(),
+ ))
+ })
+ }
+ }
+
+ /// Wraps the `ZSTD_compress_usingDict()` function.
+ pub fn compress_using_dict<C: WriteBuf + ?Sized>(
+ &mut self,
+ dst: &mut C,
+ src: &[u8],
+ dict: &[u8],
+ compression_level: CompressionLevel,
+ ) -> SafeResult {
+ // Safety: ZSTD_compress_usingDict returns how many bytes were written.
+ unsafe {
+ dst.write_from(|buffer, capacity| {
+ parse_code(zstd_sys::ZSTD_compress_usingDict(
+ self.0.as_ptr(),
+ buffer,
+ capacity,
+ ptr_void(src),
+ src.len(),
+ ptr_void(dict),
+ dict.len(),
+ compression_level,
+ ))
+ })
+ }
+ }
+
+ /// Wraps the `ZSTD_compress_usingCDict()` function.
+ pub fn compress_using_cdict<C: WriteBuf + ?Sized>(
+ &mut self,
+ dst: &mut C,
+ src: &[u8],
+ cdict: &CDict<'_>,
+ ) -> SafeResult {
+ // Safety: ZSTD_compress_usingCDict returns how many bytes were written.
+ unsafe {
+ dst.write_from(|buffer, capacity| {
+ parse_code(zstd_sys::ZSTD_compress_usingCDict(
+ self.0.as_ptr(),
+ buffer,
+ capacity,
+ ptr_void(src),
+ src.len(),
+ cdict.0.as_ptr(),
+ ))
+ })
+ }
+ }
+
+ /// Initializes the context with the given compression level.
+ ///
+ /// This is equivalent to running:
+ /// * `reset()`
+ /// * `set_parameter(CompressionLevel, compression_level)`
+ pub fn init(&mut self, compression_level: CompressionLevel) -> SafeResult {
+ // Safety: Just FFI
+ let code = unsafe {
+ zstd_sys::ZSTD_initCStream(self.0.as_ptr(), compression_level)
+ };
+ parse_code(code)
+ }
+
+ /// Wraps the `ZSTD_initCStream_srcSize()` function.
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ #[deprecated]
+ pub fn init_src_size(
+ &mut self,
+ compression_level: CompressionLevel,
+ pledged_src_size: u64,
+ ) -> SafeResult {
+ // Safety: Just FFI
+ let code = unsafe {
+ zstd_sys::ZSTD_initCStream_srcSize(
+ self.0.as_ptr(),
+ compression_level as c_int,
+ pledged_src_size as c_ulonglong,
+ )
+ };
+ parse_code(code)
+ }
+
+ /// Wraps the `ZSTD_initCStream_usingDict()` function.
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ #[deprecated]
+ pub fn init_using_dict(
+ &mut self,
+ dict: &[u8],
+ compression_level: CompressionLevel,
+ ) -> SafeResult {
+ // Safety: Just FFI
+ let code = unsafe {
+ zstd_sys::ZSTD_initCStream_usingDict(
+ self.0.as_ptr(),
+ ptr_void(dict),
+ dict.len(),
+ compression_level,
+ )
+ };
+ parse_code(code)
+ }
+
+ /// Wraps the `ZSTD_initCStream_usingCDict()` function.
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ #[deprecated]
+ pub fn init_using_cdict<'b>(&mut self, cdict: &CDict<'b>) -> SafeResult
+ where
+ 'b: 'a, // Dictionary outlives the stream.
+ {
+ // Safety: Just FFI
+ let code = unsafe {
+ zstd_sys::ZSTD_initCStream_usingCDict(
+ self.0.as_ptr(),
+ cdict.0.as_ptr(),
+ )
+ };
+ parse_code(code)
+ }
+
+ /// Tries to load a dictionary.
+ ///
+ /// The dictionary content will be copied internally and does not need to be kept alive after
+ /// calling this function.
+ ///
+ /// If you need to use the same dictionary for multiple contexts, it may be more efficient to
+ /// create a `CDict` first, then loads that.
+ ///
+ /// The dictionary will apply to all compressed frames, until a new dictionary is set.
+ pub fn load_dictionary(&mut self, dict: &[u8]) -> SafeResult {
+ // Safety: Just FFI
+ parse_code(unsafe {
+ zstd_sys::ZSTD_CCtx_loadDictionary(
+ self.0.as_ptr(),
+ ptr_void(dict),
+ dict.len(),
+ )
+ })
+ }
+
+ /// Wraps the `ZSTD_CCtx_refCDict()` function.
+ ///
+ /// Dictionary must outlive the context.
+ pub fn ref_cdict<'b>(&mut self, cdict: &CDict<'b>) -> SafeResult
+ where
+ 'b: 'a,
+ {
+ // Safety: Just FFI
+ parse_code(unsafe {
+ zstd_sys::ZSTD_CCtx_refCDict(self.0.as_ptr(), cdict.0.as_ptr())
+ })
+ }
+
+ /// Return to "no-dictionary" mode.
+ ///
+ /// This will disable any dictionary/prefix previously registered for future frames.
+ pub fn disable_dictionary(&mut self) -> SafeResult {
+ // Safety: Just FFI
+ parse_code(unsafe {
+ zstd_sys::ZSTD_CCtx_loadDictionary(
+ self.0.as_ptr(),
+ core::ptr::null(),
+ 0,
+ )
+ })
+ }
+
+ /// Use some prefix as single-use dictionary for the next compressed frame.
+ ///
+ /// Just like a dictionary, decompression will need to be given the same prefix.
+ ///
+ /// This is best used if the "prefix" looks like the data to be compressed.
+ pub fn ref_prefix<'b>(&mut self, prefix: &'b [u8]) -> SafeResult
+ where
+ 'b: 'a,
+ {
+ // Safety: Just FFI
+ parse_code(unsafe {
+ zstd_sys::ZSTD_CCtx_refPrefix(
+ self.0.as_ptr(),
+ ptr_void(prefix),
+ prefix.len(),
+ )
+ })
+ }
+
+ /// Performs a step of a streaming compression operation.
+ ///
+ /// This will read some data from `input` and/or write some data to `output`.
+ ///
+ /// # Returns
+ ///
+ /// A hint for the "ideal" amount of input data to provide in the next call.
+ ///
+ /// This hint is only for performance purposes.
+ ///
+ /// Wraps the `ZSTD_compressStream()` function.
+ pub fn compress_stream<C: WriteBuf + ?Sized>(
+ &mut self,
+ output: &mut OutBuffer<'_, C>,
+ input: &mut InBuffer<'_>,
+ ) -> SafeResult {
+ let mut output = output.wrap();
+ let mut input = input.wrap();
+ // Safety: Just FFI
+ let code = unsafe {
+ zstd_sys::ZSTD_compressStream(
+ self.0.as_ptr(),
+ ptr_mut(&mut output),
+ ptr_mut(&mut input),
+ )
+ };
+ parse_code(code)
+ }
+
+ /// Performs a step of a streaming compression operation.
+ ///
+ /// This will read some data from `input` and/or write some data to `output`.
+ ///
+ /// The `end_op` directive can be used to specify what to do after: nothing special, flush
+ /// internal buffers, or end the frame.
+ ///
+ /// # Returns
+ ///
+ /// An lower bound for the amount of data that still needs to be flushed out.
+ ///
+ /// This is useful when flushing or ending the frame: you need to keep calling this function
+ /// until it returns 0.
+ ///
+ /// Wraps the `ZSTD_compressStream2()` function.
+ pub fn compress_stream2<C: WriteBuf + ?Sized>(
+ &mut self,
+ output: &mut OutBuffer<'_, C>,
+ input: &mut InBuffer<'_>,
+ end_op: zstd_sys::ZSTD_EndDirective,
+ ) -> SafeResult {
+ let mut output = output.wrap();
+ let mut input = input.wrap();
+ // Safety: Just FFI
+ parse_code(unsafe {
+ zstd_sys::ZSTD_compressStream2(
+ self.0.as_ptr(),
+ ptr_mut(&mut output),
+ ptr_mut(&mut input),
+ end_op,
+ )
+ })
+ }
+
+ /// Flush any intermediate buffer.
+ ///
+ /// To fully flush, you should keep calling this function until it returns `Ok(0)`.
+ ///
+ /// Wraps the `ZSTD_flushStream()` function.
+ pub fn flush_stream<C: WriteBuf + ?Sized>(
+ &mut self,
+ output: &mut OutBuffer<'_, C>,
+ ) -> SafeResult {
+ let mut output = output.wrap();
+ // Safety: Just FFI
+ let code = unsafe {
+ zstd_sys::ZSTD_flushStream(self.0.as_ptr(), ptr_mut(&mut output))
+ };
+ parse_code(code)
+ }
+
+ /// Ends the stream.
+ ///
+ /// You should keep calling this function until it returns `Ok(0)`.
+ ///
+ /// Wraps the `ZSTD_endStream()` function.
+ pub fn end_stream<C: WriteBuf + ?Sized>(
+ &mut self,
+ output: &mut OutBuffer<'_, C>,
+ ) -> SafeResult {
+ let mut output = output.wrap();
+ // Safety: Just FFI
+ let code = unsafe {
+ zstd_sys::ZSTD_endStream(self.0.as_ptr(), ptr_mut(&mut output))
+ };
+ parse_code(code)
+ }
+
+ /// Returns the size currently used by this context.
+ ///
+ /// This may change over time.
+ pub fn sizeof(&self) -> usize {
+ // Safety: Just FFI
+ unsafe { zstd_sys::ZSTD_sizeof_CCtx(self.0.as_ptr()) }
+ }
+
+ /// Resets the state of the context.
+ ///
+ /// Depending on the reset mode, it can reset the session, the parameters, or both.
+ ///
+ /// Wraps the `ZSTD_CCtx_reset()` function.
+ pub fn reset(&mut self, reset: ResetDirective) -> SafeResult {
+ // Safety: Just FFI
+ parse_code(unsafe {
+ zstd_sys::ZSTD_CCtx_reset(self.0.as_ptr(), reset.as_sys())
+ })
+ }
+
+ /// Sets a compression parameter.
+ ///
+ /// Some of these parameters need to be set during de-compression as well.
+ pub fn set_parameter(&mut self, param: CParameter) -> SafeResult {
+ // TODO: Until bindgen properly generates a binding for this, we'll need to do it here.
+
+ #[cfg(feature = "experimental")]
+ use zstd_sys::ZSTD_cParameter::{
+ ZSTD_c_experimentalParam1 as ZSTD_c_rsyncable,
+ ZSTD_c_experimentalParam10 as ZSTD_c_stableOutBuffer,
+ ZSTD_c_experimentalParam11 as ZSTD_c_blockDelimiters,
+ ZSTD_c_experimentalParam12 as ZSTD_c_validateSequences,
+ ZSTD_c_experimentalParam13 as ZSTD_c_useBlockSplitter,
+ ZSTD_c_experimentalParam14 as ZSTD_c_useRowMatchFinder,
+ ZSTD_c_experimentalParam15 as ZSTD_c_deterministicRefPrefix,
+ ZSTD_c_experimentalParam16 as ZSTD_c_prefetchCDictTables,
+ ZSTD_c_experimentalParam17 as ZSTD_c_enableSeqProducerFallback,
+ ZSTD_c_experimentalParam18 as ZSTD_c_maxBlockSize,
+ ZSTD_c_experimentalParam19 as ZSTD_c_searchForExternalRepcodes,
+ ZSTD_c_experimentalParam2 as ZSTD_c_format,
+ ZSTD_c_experimentalParam3 as ZSTD_c_forceMaxWindow,
+ ZSTD_c_experimentalParam4 as ZSTD_c_forceAttachDict,
+ ZSTD_c_experimentalParam5 as ZSTD_c_literalCompressionMode,
+ ZSTD_c_experimentalParam6 as ZSTD_c_targetCBlockSize,
+ ZSTD_c_experimentalParam7 as ZSTD_c_srcSizeHint,
+ ZSTD_c_experimentalParam8 as ZSTD_c_enableDedicatedDictSearch,
+ ZSTD_c_experimentalParam9 as ZSTD_c_stableInBuffer,
+ };
+
+ use zstd_sys::ZSTD_cParameter::*;
+ use CParameter::*;
+
+ let (param, value) = match param {
+ #[cfg(feature = "experimental")]
+ RSyncable(rsyncable) => (ZSTD_c_rsyncable, rsyncable as c_int),
+ #[cfg(feature = "experimental")]
+ Format(format) => (ZSTD_c_format, format as c_int),
+ #[cfg(feature = "experimental")]
+ ForceMaxWindow(force) => (ZSTD_c_forceMaxWindow, force as c_int),
+ #[cfg(feature = "experimental")]
+ ForceAttachDict(force) => (ZSTD_c_forceAttachDict, force as c_int),
+ #[cfg(feature = "experimental")]
+ LiteralCompressionMode(mode) => {
+ (ZSTD_c_literalCompressionMode, mode as c_int)
+ }
+ #[cfg(feature = "experimental")]
+ TargetCBlockSize(value) => {
+ (ZSTD_c_targetCBlockSize, value as c_int)
+ }
+ #[cfg(feature = "experimental")]
+ SrcSizeHint(value) => (ZSTD_c_srcSizeHint, value as c_int),
+ #[cfg(feature = "experimental")]
+ EnableDedicatedDictSearch(enable) => {
+ (ZSTD_c_enableDedicatedDictSearch, enable as c_int)
+ }
+ #[cfg(feature = "experimental")]
+ StableInBuffer(stable) => (ZSTD_c_stableInBuffer, stable as c_int),
+ #[cfg(feature = "experimental")]
+ StableOutBuffer(stable) => {
+ (ZSTD_c_stableOutBuffer, stable as c_int)
+ }
+ #[cfg(feature = "experimental")]
+ BlockDelimiters(value) => (ZSTD_c_blockDelimiters, value as c_int),
+ #[cfg(feature = "experimental")]
+ ValidateSequences(validate) => {
+ (ZSTD_c_validateSequences, validate as c_int)
+ }
+ #[cfg(feature = "experimental")]
+ UseBlockSplitter(split) => {
+ (ZSTD_c_useBlockSplitter, split as c_int)
+ }
+ #[cfg(feature = "experimental")]
+ UseRowMatchFinder(mode) => {
+ (ZSTD_c_useRowMatchFinder, mode as c_int)
+ }
+ #[cfg(feature = "experimental")]
+ DeterministicRefPrefix(deterministic) => {
+ (ZSTD_c_deterministicRefPrefix, deterministic as c_int)
+ }
+ #[cfg(feature = "experimental")]
+ PrefetchCDictTables(prefetch) => {
+ (ZSTD_c_prefetchCDictTables, prefetch as c_int)
+ }
+ #[cfg(feature = "experimental")]
+ EnableSeqProducerFallback(enable) => {
+ (ZSTD_c_enableSeqProducerFallback, enable as c_int)
+ }
+ #[cfg(feature = "experimental")]
+ MaxBlockSize(value) => (ZSTD_c_maxBlockSize, value as c_int),
+ #[cfg(feature = "experimental")]
+ SearchForExternalRepcodes(value) => {
+ (ZSTD_c_searchForExternalRepcodes, value as c_int)
+ }
+ CompressionLevel(level) => (ZSTD_c_compressionLevel, level),
+ WindowLog(value) => (ZSTD_c_windowLog, value as c_int),
+ HashLog(value) => (ZSTD_c_hashLog, value as c_int),
+ ChainLog(value) => (ZSTD_c_chainLog, value as c_int),
+ SearchLog(value) => (ZSTD_c_searchLog, value as c_int),
+ MinMatch(value) => (ZSTD_c_minMatch, value as c_int),
+ TargetLength(value) => (ZSTD_c_targetLength, value as c_int),
+ Strategy(strategy) => (ZSTD_c_strategy, strategy as c_int),
+ EnableLongDistanceMatching(flag) => {
+ (ZSTD_c_enableLongDistanceMatching, flag as c_int)
+ }
+ LdmHashLog(value) => (ZSTD_c_ldmHashLog, value as c_int),
+ LdmMinMatch(value) => (ZSTD_c_ldmMinMatch, value as c_int),
+ LdmBucketSizeLog(value) => {
+ (ZSTD_c_ldmBucketSizeLog, value as c_int)
+ }
+ LdmHashRateLog(value) => (ZSTD_c_ldmHashRateLog, value as c_int),
+ ContentSizeFlag(flag) => (ZSTD_c_contentSizeFlag, flag as c_int),
+ ChecksumFlag(flag) => (ZSTD_c_checksumFlag, flag as c_int),
+ DictIdFlag(flag) => (ZSTD_c_dictIDFlag, flag as c_int),
+
+ NbWorkers(value) => (ZSTD_c_nbWorkers, value as c_int),
+
+ JobSize(value) => (ZSTD_c_jobSize, value as c_int),
+
+ OverlapSizeLog(value) => (ZSTD_c_overlapLog, value as c_int),
+ };
+
+ // Safety: Just FFI
+ parse_code(unsafe {
+ zstd_sys::ZSTD_CCtx_setParameter(self.0.as_ptr(), param, value)
+ })
+ }
+
+ /// Guarantee that the input size will be this value.
+ ///
+ /// If given `None`, assumes the size is unknown.
+ ///
+ /// Unless explicitly disabled, this will cause the size to be written in the compressed frame
+ /// header.
+ ///
+ /// If the actual data given to compress has a different size, an error will be returned.
+ pub fn set_pledged_src_size(
+ &mut self,
+ pledged_src_size: Option<u64>,
+ ) -> SafeResult {
+ // Safety: Just FFI
+ parse_code(unsafe {
+ zstd_sys::ZSTD_CCtx_setPledgedSrcSize(
+ self.0.as_ptr(),
+ pledged_src_size.unwrap_or(CONTENTSIZE_UNKNOWN) as c_ulonglong,
+ )
+ })
+ }
+
+ /// Creates a copy of this context.
+ ///
+ /// This only works before any data has been compressed. An error will be
+ /// returned otherwise.
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ pub fn try_clone(
+ &self,
+ pledged_src_size: Option<u64>,
+ ) -> Result<Self, ErrorCode> {
+ // Safety: Just FFI
+ let context = NonNull::new(unsafe { zstd_sys::ZSTD_createCCtx() })
+ .ok_or(0usize)?;
+
+ // Safety: Just FFI
+ parse_code(unsafe {
+ zstd_sys::ZSTD_copyCCtx(
+ context.as_ptr(),
+ self.0.as_ptr(),
+ pledged_src_size.unwrap_or(CONTENTSIZE_UNKNOWN),
+ )
+ })?;
+
+ Ok(CCtx(context, self.1))
+ }
+
+ /// Wraps the `ZSTD_getBlockSize()` function.
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ pub fn get_block_size(&self) -> usize {
+ // Safety: Just FFI
+ unsafe { zstd_sys::ZSTD_getBlockSize(self.0.as_ptr()) }
+ }
+
+ /// Wraps the `ZSTD_compressBlock()` function.
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ pub fn compress_block<C: WriteBuf + ?Sized>(
+ &mut self,
+ dst: &mut C,
+ src: &[u8],
+ ) -> SafeResult {
+ // Safety: ZSTD_compressBlock returns the number of bytes written.
+ unsafe {
+ dst.write_from(|buffer, capacity| {
+ parse_code(zstd_sys::ZSTD_compressBlock(
+ self.0.as_ptr(),
+ buffer,
+ capacity,
+ ptr_void(src),
+ src.len(),
+ ))
+ })
+ }
+ }
+
+ /// Returns the recommended input buffer size.
+ ///
+ /// Using this size may result in minor performance boost.
+ pub fn in_size() -> usize {
+ // Safety: Just FFI
+ unsafe { zstd_sys::ZSTD_CStreamInSize() }
+ }
+
+ /// Returns the recommended output buffer size.
+ ///
+ /// Using this may result in minor performance boost.
+ pub fn out_size() -> usize {
+ // Safety: Just FFI
+ unsafe { zstd_sys::ZSTD_CStreamOutSize() }
+ }
+}
+
+impl<'a> Drop for CCtx<'a> {
+ fn drop(&mut self) {
+ // Safety: Just FFI
+ unsafe {
+ zstd_sys::ZSTD_freeCCtx(self.0.as_ptr());
+ }
+ }
+}
+
+unsafe impl<'a> Send for CCtx<'a> {}
+// CCtx can't be shared across threads, so it does not implement Sync.
+
+unsafe fn c_char_to_str(text: *const c_char) -> &'static str {
+ #[cfg(not(feature = "std"))]
+ {
+ // To be safe, we need to compute right now its length
+ let len = libc::strlen(text);
+ // Cast it to a slice
+ let slice = core::slice::from_raw_parts(text as *mut u8, len);
+ // And hope it's still text.
+ str::from_utf8(slice).expect("bad error message from zstd")
+ }
+
+ #[cfg(feature = "std")]
+ {
+ std::ffi::CStr::from_ptr(text)
+ .to_str()
+ .expect("bad error message from zstd")
+ }
+}
+
+/// Returns the error string associated with an error code.
+pub fn get_error_name(code: usize) -> &'static str {
+ unsafe {
+ // Safety: assumes ZSTD returns a well-formed utf8 string.
+ let name = zstd_sys::ZSTD_getErrorName(code);
+ c_char_to_str(name)
+ }
+}
+
+/// A Decompression Context.
+///
+/// The lifetime references the potential dictionary used for this context.
+///
+/// If no dictionary was used, it will most likely be `'static`.
+///
+/// Same as `DStream`.
+pub struct DCtx<'a>(NonNull<zstd_sys::ZSTD_DCtx>, PhantomData<&'a ()>);
+
+impl Default for DCtx<'_> {
+ fn default() -> Self {
+ DCtx::create()
+ }
+}
+
+impl<'a> DCtx<'a> {
+ /// Try to create a new decompression context.
+ ///
+ /// Returns `None` if the operation failed (for example, not enough memory).
+ pub fn try_create() -> Option<Self> {
+ Some(DCtx(
+ NonNull::new(unsafe { zstd_sys::ZSTD_createDCtx() })?,
+ PhantomData,
+ ))
+ }
+
+ /// Creates a new decoding context.
+ ///
+ /// # Panics
+ ///
+ /// If the context creation fails.
+ pub fn create() -> Self {
+ Self::try_create()
+ .expect("zstd returned null pointer when creating new context")
+ }
+
+ /// Fully decompress the given frame.
+ ///
+ /// This decompress an entire frame in-memory. If you can have enough memory to store both the
+ /// input and output buffer, then it may be faster that streaming decompression.
+ ///
+ /// Wraps the `ZSTD_decompressDCtx()` function.
+ pub fn decompress<C: WriteBuf + ?Sized>(
+ &mut self,
+ dst: &mut C,
+ src: &[u8],
+ ) -> SafeResult {
+ unsafe {
+ dst.write_from(|buffer, capacity| {
+ parse_code(zstd_sys::ZSTD_decompressDCtx(
+ self.0.as_ptr(),
+ buffer,
+ capacity,
+ ptr_void(src),
+ src.len(),
+ ))
+ })
+ }
+ }
+
+ /// Fully decompress the given frame using a dictionary.
+ ///
+ /// Dictionary must be identical to the one used during compression.
+ ///
+ /// If you plan on using the same dictionary multiple times, it is faster to create a `DDict`
+ /// first and use `decompress_using_ddict`.
+ ///
+ /// Wraps `ZSTD_decompress_usingDict`
+ pub fn decompress_using_dict<C: WriteBuf + ?Sized>(
+ &mut self,
+ dst: &mut C,
+ src: &[u8],
+ dict: &[u8],
+ ) -> SafeResult {
+ unsafe {
+ dst.write_from(|buffer, capacity| {
+ parse_code(zstd_sys::ZSTD_decompress_usingDict(
+ self.0.as_ptr(),
+ buffer,
+ capacity,
+ ptr_void(src),
+ src.len(),
+ ptr_void(dict),
+ dict.len(),
+ ))
+ })
+ }
+ }
+
+ /// Fully decompress the given frame using a dictionary.
+ ///
+ /// Dictionary must be identical to the one used during compression.
+ ///
+ /// Wraps the `ZSTD_decompress_usingDDict()` function.
+ pub fn decompress_using_ddict<C: WriteBuf + ?Sized>(
+ &mut self,
+ dst: &mut C,
+ src: &[u8],
+ ddict: &DDict<'_>,
+ ) -> SafeResult {
+ unsafe {
+ dst.write_from(|buffer, capacity| {
+ parse_code(zstd_sys::ZSTD_decompress_usingDDict(
+ self.0.as_ptr(),
+ buffer,
+ capacity,
+ ptr_void(src),
+ src.len(),
+ ddict.0.as_ptr(),
+ ))
+ })
+ }
+ }
+
+ /// Initializes an existing `DStream` for decompression.
+ ///
+ /// This is equivalent to calling:
+ /// * `reset(SessionOnly)`
+ /// * `disable_dictionary()`
+ ///
+ /// Wraps the `ZSTD_initCStream()` function.
+ pub fn init(&mut self) -> SafeResult {
+ let code = unsafe { zstd_sys::ZSTD_initDStream(self.0.as_ptr()) };
+ parse_code(code)
+ }
+
+ /// Wraps the `ZSTD_initDStream_usingDict()` function.
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ #[deprecated]
+ pub fn init_using_dict(&mut self, dict: &[u8]) -> SafeResult {
+ let code = unsafe {
+ zstd_sys::ZSTD_initDStream_usingDict(
+ self.0.as_ptr(),
+ ptr_void(dict),
+ dict.len(),
+ )
+ };
+ parse_code(code)
+ }
+
+ /// Wraps the `ZSTD_initDStream_usingDDict()` function.
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ #[deprecated]
+ pub fn init_using_ddict<'b>(&mut self, ddict: &DDict<'b>) -> SafeResult
+ where
+ 'b: 'a,
+ {
+ let code = unsafe {
+ zstd_sys::ZSTD_initDStream_usingDDict(
+ self.0.as_ptr(),
+ ddict.0.as_ptr(),
+ )
+ };
+ parse_code(code)
+ }
+
+ /// Resets the state of the context.
+ ///
+ /// Depending on the reset mode, it can reset the session, the parameters, or both.
+ ///
+ /// Wraps the `ZSTD_DCtx_reset()` function.
+ pub fn reset(&mut self, reset: ResetDirective) -> SafeResult {
+ parse_code(unsafe {
+ zstd_sys::ZSTD_DCtx_reset(self.0.as_ptr(), reset.as_sys())
+ })
+ }
+
+ /// Loads a dictionary.
+ ///
+ /// This will let this context decompress frames that were compressed using this dictionary.
+ ///
+ /// The dictionary content will be copied internally and does not need to be kept alive after
+ /// calling this function.
+ ///
+ /// If you need to use the same dictionary for multiple contexts, it may be more efficient to
+ /// create a `DDict` first, then loads that.
+ ///
+ /// The dictionary will apply to all future frames, until a new dictionary is set.
+ pub fn load_dictionary(&mut self, dict: &[u8]) -> SafeResult {
+ parse_code(unsafe {
+ zstd_sys::ZSTD_DCtx_loadDictionary(
+ self.0.as_ptr(),
+ ptr_void(dict),
+ dict.len(),
+ )
+ })
+ }
+
+ /// Return to "no-dictionary" mode.
+ ///
+ /// This will disable any dictionary/prefix previously registered for future frames.
+ pub fn disable_dictionary(&mut self) -> SafeResult {
+ parse_code(unsafe {
+ zstd_sys::ZSTD_DCtx_loadDictionary(
+ self.0.as_ptr(),
+ core::ptr::null(),
+ 0,
+ )
+ })
+ }
+
+ /// References a dictionary.
+ ///
+ /// This will let this context decompress frames compressed with the same dictionary.
+ ///
+ /// It will apply to all frames decompressed by this context (until a new dictionary is set).
+ ///
+ /// Wraps the `ZSTD_DCtx_refDDict()` function.
+ pub fn ref_ddict<'b>(&mut self, ddict: &DDict<'b>) -> SafeResult
+ where
+ 'b: 'a,
+ {
+ parse_code(unsafe {
+ zstd_sys::ZSTD_DCtx_refDDict(self.0.as_ptr(), ddict.0.as_ptr())
+ })
+ }
+
+ /// Use some prefix as single-use dictionary for the next frame.
+ ///
+ /// Just like a dictionary, this only works if compression was done with the same prefix.
+ ///
+ /// But unlike a dictionary, this only applies to the next frame.
+ ///
+ /// Wraps the `ZSTD_DCtx_refPrefix()` function.
+ pub fn ref_prefix<'b>(&mut self, prefix: &'b [u8]) -> SafeResult
+ where
+ 'b: 'a,
+ {
+ parse_code(unsafe {
+ zstd_sys::ZSTD_DCtx_refPrefix(
+ self.0.as_ptr(),
+ ptr_void(prefix),
+ prefix.len(),
+ )
+ })
+ }
+
+ /// Sets a decompression parameter.
+ pub fn set_parameter(&mut self, param: DParameter) -> SafeResult {
+ #[cfg(feature = "experimental")]
+ use zstd_sys::ZSTD_dParameter::{
+ ZSTD_d_experimentalParam1 as ZSTD_d_format,
+ ZSTD_d_experimentalParam2 as ZSTD_d_stableOutBuffer,
+ ZSTD_d_experimentalParam3 as ZSTD_d_forceIgnoreChecksum,
+ ZSTD_d_experimentalParam4 as ZSTD_d_refMultipleDDicts,
+ };
+
+ use zstd_sys::ZSTD_dParameter::*;
+ use DParameter::*;
+
+ let (param, value) = match param {
+ #[cfg(feature = "experimental")]
+ Format(format) => (ZSTD_d_format, format as c_int),
+ #[cfg(feature = "experimental")]
+ StableOutBuffer(stable) => {
+ (ZSTD_d_stableOutBuffer, stable as c_int)
+ }
+ #[cfg(feature = "experimental")]
+ ForceIgnoreChecksum(force) => {
+ (ZSTD_d_forceIgnoreChecksum, force as c_int)
+ }
+ #[cfg(feature = "experimental")]
+ RefMultipleDDicts(value) => {
+ (ZSTD_d_refMultipleDDicts, value as c_int)
+ }
+
+ WindowLogMax(value) => (ZSTD_d_windowLogMax, value as c_int),
+ };
+
+ parse_code(unsafe {
+ zstd_sys::ZSTD_DCtx_setParameter(self.0.as_ptr(), param, value)
+ })
+ }
+
+ /// Performs a step of a streaming decompression operation.
+ ///
+ /// This will read some data from `input` and/or write some data to `output`.
+ ///
+ /// # Returns
+ ///
+ /// * `Ok(0)` if the current frame just finished decompressing successfully.
+ /// * `Ok(hint)` with a hint for the "ideal" amount of input data to provide in the next call.
+ /// Can be safely ignored.
+ ///
+ /// Wraps the `ZSTD_decompressStream()` function.
+ pub fn decompress_stream<C: WriteBuf + ?Sized>(
+ &mut self,
+ output: &mut OutBuffer<'_, C>,
+ input: &mut InBuffer<'_>,
+ ) -> SafeResult {
+ let mut output = output.wrap();
+ let mut input = input.wrap();
+ let code = unsafe {
+ zstd_sys::ZSTD_decompressStream(
+ self.0.as_ptr(),
+ ptr_mut(&mut output),
+ ptr_mut(&mut input),
+ )
+ };
+ parse_code(code)
+ }
+
+ /// Wraps the `ZSTD_DStreamInSize()` function.
+ ///
+ /// Returns a hint for the recommended size of the input buffer for decompression.
+ pub fn in_size() -> usize {
+ unsafe { zstd_sys::ZSTD_DStreamInSize() }
+ }
+
+ /// Wraps the `ZSTD_DStreamOutSize()` function.
+ ///
+ /// Returns a hint for the recommended size of the output buffer for decompression.
+ pub fn out_size() -> usize {
+ unsafe { zstd_sys::ZSTD_DStreamOutSize() }
+ }
+
+ /// Wraps the `ZSTD_sizeof_DCtx()` function.
+ pub fn sizeof(&self) -> usize {
+ unsafe { zstd_sys::ZSTD_sizeof_DCtx(self.0.as_ptr()) }
+ }
+
+ /// Wraps the `ZSTD_decompressBlock()` function.
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ pub fn decompress_block<C: WriteBuf + ?Sized>(
+ &mut self,
+ dst: &mut C,
+ src: &[u8],
+ ) -> SafeResult {
+ unsafe {
+ dst.write_from(|buffer, capacity| {
+ parse_code(zstd_sys::ZSTD_decompressBlock(
+ self.0.as_ptr(),
+ buffer,
+ capacity,
+ ptr_void(src),
+ src.len(),
+ ))
+ })
+ }
+ }
+
+ /// Wraps the `ZSTD_insertBlock()` function.
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ pub fn insert_block(&mut self, block: &[u8]) -> usize {
+ unsafe {
+ zstd_sys::ZSTD_insertBlock(
+ self.0.as_ptr(),
+ ptr_void(block),
+ block.len(),
+ )
+ }
+ }
+
+ /// Creates a copy of this context.
+ ///
+ /// This only works before any data has been decompressed. An error will be
+ /// returned otherwise.
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ pub fn try_clone(&self) -> Result<Self, ErrorCode> {
+ let context = NonNull::new(unsafe { zstd_sys::ZSTD_createDCtx() })
+ .ok_or(0usize)?;
+
+ unsafe { zstd_sys::ZSTD_copyDCtx(context.as_ptr(), self.0.as_ptr()) };
+
+ Ok(DCtx(context, self.1))
+ }
+}
+
+impl Drop for DCtx<'_> {
+ fn drop(&mut self) {
+ unsafe {
+ zstd_sys::ZSTD_freeDCtx(self.0.as_ptr());
+ }
+ }
+}
+
+unsafe impl Send for DCtx<'_> {}
+// DCtx can't be shared across threads, so it does not implement Sync.
+
+/// Compression dictionary.
+pub struct CDict<'a>(NonNull<zstd_sys::ZSTD_CDict>, PhantomData<&'a ()>);
+
+impl CDict<'static> {
+ /// Prepare a dictionary to compress data.
+ ///
+ /// This will make it easier for compression contexts to load this dictionary.
+ ///
+ /// The dictionary content will be copied internally, and does not need to be kept around.
+ ///
+ /// # Panics
+ ///
+ /// If loading this dictionary failed.
+ pub fn create(
+ dict_buffer: &[u8],
+ compression_level: CompressionLevel,
+ ) -> Self {
+ Self::try_create(dict_buffer, compression_level)
+ .expect("zstd returned null pointer when creating dict")
+ }
+
+ /// Prepare a dictionary to compress data.
+ ///
+ /// This will make it easier for compression contexts to load this dictionary.
+ ///
+ /// The dictionary content will be copied internally, and does not need to be kept around.
+ pub fn try_create(
+ dict_buffer: &[u8],
+ compression_level: CompressionLevel,
+ ) -> Option<Self> {
+ Some(CDict(
+ NonNull::new(unsafe {
+ zstd_sys::ZSTD_createCDict(
+ ptr_void(dict_buffer),
+ dict_buffer.len(),
+ compression_level,
+ )
+ })?,
+ PhantomData,
+ ))
+ }
+}
+
+impl<'a> CDict<'a> {
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ pub fn create_by_reference(
+ dict_buffer: &'a [u8],
+ compression_level: CompressionLevel,
+ ) -> Self {
+ CDict(
+ NonNull::new(unsafe {
+ zstd_sys::ZSTD_createCDict_byReference(
+ ptr_void(dict_buffer),
+ dict_buffer.len(),
+ compression_level,
+ )
+ })
+ .expect("zstd returned null pointer"),
+ PhantomData,
+ )
+ }
+
+ /// Returns the _current_ memory usage of this dictionary.
+ ///
+ /// Note that this may change over time.
+ pub fn sizeof(&self) -> usize {
+ unsafe { zstd_sys::ZSTD_sizeof_CDict(self.0.as_ptr()) }
+ }
+
+ /// Returns the dictionary ID for this dict.
+ ///
+ /// Returns `None` if this dictionary is empty or invalid.
+ pub fn get_dict_id(&self) -> Option<NonZeroU32> {
+ NonZeroU32::new(unsafe {
+ zstd_sys::ZSTD_getDictID_fromCDict(self.0.as_ptr()) as u32
+ })
+ }
+}
+
+/// Wraps the `ZSTD_createCDict()` function.
+pub fn create_cdict(
+ dict_buffer: &[u8],
+ compression_level: CompressionLevel,
+) -> CDict<'static> {
+ CDict::create(dict_buffer, compression_level)
+}
+
+impl<'a> Drop for CDict<'a> {
+ fn drop(&mut self) {
+ unsafe {
+ zstd_sys::ZSTD_freeCDict(self.0.as_ptr());
+ }
+ }
+}
+
+unsafe impl<'a> Send for CDict<'a> {}
+unsafe impl<'a> Sync for CDict<'a> {}
+
+/// Wraps the `ZSTD_compress_usingCDict()` function.
+pub fn compress_using_cdict(
+ cctx: &mut CCtx<'_>,
+ dst: &mut [u8],
+ src: &[u8],
+ cdict: &CDict<'_>,
+) -> SafeResult {
+ cctx.compress_using_cdict(dst, src, cdict)
+}
+
+/// A digested decompression dictionary.
+pub struct DDict<'a>(NonNull<zstd_sys::ZSTD_DDict>, PhantomData<&'a ()>);
+
+impl DDict<'static> {
+ pub fn create(dict_buffer: &[u8]) -> Self {
+ Self::try_create(dict_buffer)
+ .expect("zstd returned null pointer when creating dict")
+ }
+
+ pub fn try_create(dict_buffer: &[u8]) -> Option<Self> {
+ Some(DDict(
+ NonNull::new(unsafe {
+ zstd_sys::ZSTD_createDDict(
+ ptr_void(dict_buffer),
+ dict_buffer.len(),
+ )
+ })?,
+ PhantomData,
+ ))
+ }
+}
+
+impl<'a> DDict<'a> {
+ pub fn sizeof(&self) -> usize {
+ unsafe { zstd_sys::ZSTD_sizeof_DDict(self.0.as_ptr()) }
+ }
+
+ /// Wraps the `ZSTD_createDDict_byReference()` function.
+ ///
+ /// The dictionary will keep referencing `dict_buffer`.
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ pub fn create_by_reference(dict_buffer: &'a [u8]) -> Self {
+ DDict(
+ NonNull::new(unsafe {
+ zstd_sys::ZSTD_createDDict_byReference(
+ ptr_void(dict_buffer),
+ dict_buffer.len(),
+ )
+ })
+ .expect("zstd returned null pointer"),
+ PhantomData,
+ )
+ }
+
+ /// Returns the dictionary ID for this dict.
+ ///
+ /// Returns `None` if this dictionary is empty or invalid.
+ pub fn get_dict_id(&self) -> Option<NonZeroU32> {
+ NonZeroU32::new(unsafe {
+ zstd_sys::ZSTD_getDictID_fromDDict(self.0.as_ptr()) as u32
+ })
+ }
+}
+
+/// Wraps the `ZSTD_createDDict()` function.
+///
+/// It copies the dictionary internally, so the resulting `DDict` is `'static`.
+pub fn create_ddict(dict_buffer: &[u8]) -> DDict<'static> {
+ DDict::create(dict_buffer)
+}
+
+impl<'a> Drop for DDict<'a> {
+ fn drop(&mut self) {
+ unsafe {
+ zstd_sys::ZSTD_freeDDict(self.0.as_ptr());
+ }
+ }
+}
+
+unsafe impl<'a> Send for DDict<'a> {}
+unsafe impl<'a> Sync for DDict<'a> {}
+
+/// Wraps the `ZSTD_decompress_usingDDict()` function.
+pub fn decompress_using_ddict(
+ dctx: &mut DCtx<'_>,
+ dst: &mut [u8],
+ src: &[u8],
+ ddict: &DDict<'_>,
+) -> SafeResult {
+ dctx.decompress_using_ddict(dst, src, ddict)
+}
+
+/// Compression stream.
+///
+/// Same as `CCtx`.
+pub type CStream<'a> = CCtx<'a>;
+
+// CStream can't be shared across threads, so it does not implement Sync.
+
+/// Allocates a new `CStream`.
+pub fn create_cstream<'a>() -> CStream<'a> {
+ CCtx::create()
+}
+
+/// Prepares an existing `CStream` for compression at the given level.
+pub fn init_cstream(
+ zcs: &mut CStream<'_>,
+ compression_level: CompressionLevel,
+) -> SafeResult {
+ zcs.init(compression_level)
+}
+
+#[derive(Debug)]
+/// Wrapper around an input buffer.
+///
+/// Bytes will be read starting at `src[pos]`.
+///
+/// `pos` will be updated after reading.
+pub struct InBuffer<'a> {
+ pub src: &'a [u8],
+ pub pos: usize,
+}
+
+/// Describe a resizeable bytes container like `Vec<u8>`.
+///
+/// Represents a contiguous segment of memory, a prefix of which is initialized.
+///
+/// It allows starting from an uninitializes chunk of memory and writing to it.
+///
+/// The main implementors are:
+/// * `Vec<u8>` and similar structures. These can start empty with a non-zero capacity, and they
+/// will be resized to cover the data written.
+/// Any existing data will be overwritten.
+/// * `[u8]` and `[u8; N]`. These must start already-initialized, and will not be resized. It will
+/// be up to the caller to only use the part that was written.
+/// * `std::io::Cursor<T: WriteBuf>`. This will ignore data before the cursor's position, and
+/// append data after that.
+pub unsafe trait WriteBuf {
+ /// Returns the valid data part of this container. Should only cover initialized data.
+ fn as_slice(&self) -> &[u8];
+
+ /// Returns the full capacity of this container. May include uninitialized data.
+ fn capacity(&self) -> usize;
+
+ /// Returns a pointer to the start of the data.
+ fn as_mut_ptr(&mut self) -> *mut u8;
+
+ /// Indicates that the first `n` bytes of the container have been written.
+ ///
+ /// Safety: this should only be called if the `n` first bytes of this buffer have actually been
+ /// initialized.
+ unsafe fn filled_until(&mut self, n: usize);
+
+ /// Call the given closure using the pointer and capacity from `self`.
+ ///
+ /// Assumes the given function returns a parseable code, which if valid, represents how many
+ /// bytes were written to `self`.
+ ///
+ /// The given closure must treat its first argument as pointing to potentially uninitialized
+ /// memory, and should not read from it.
+ ///
+ /// In addition, it must have written at least `n` bytes contiguously from this pointer, where
+ /// `n` is the returned value.
+ unsafe fn write_from<F>(&mut self, f: F) -> SafeResult
+ where
+ F: FnOnce(*mut c_void, usize) -> SafeResult,
+ {
+ let res = f(ptr_mut_void(self), self.capacity());
+ if let Ok(n) = res {
+ self.filled_until(n);
+ }
+ res
+ }
+}
+
+#[cfg(feature = "std")]
+#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "std")))]
+unsafe impl<T> WriteBuf for std::io::Cursor<T>
+where
+ T: WriteBuf,
+{
+ fn as_slice(&self) -> &[u8] {
+ &self.get_ref().as_slice()[self.position() as usize..]
+ }
+
+ fn capacity(&self) -> usize {
+ self.get_ref()
+ .capacity()
+ .saturating_sub(self.position() as usize)
+ }
+
+ fn as_mut_ptr(&mut self) -> *mut u8 {
+ let start = self.position() as usize;
+ assert!(start <= self.get_ref().capacity());
+ // Safety: start is still in the same memory allocation
+ unsafe { self.get_mut().as_mut_ptr().add(start) }
+ }
+
+ unsafe fn filled_until(&mut self, n: usize) {
+ // Early exit: `n = 0` does not indicate anything.
+ if n == 0 {
+ return;
+ }
+
+ // Here we assume data _before_ self.position() was already initialized.
+ // Egh it's not actually guaranteed by Cursor? So let's guarantee it ourselves.
+ let position = self.position() as usize;
+ let initialized = self.get_ref().as_slice().len();
+ if let Some(uninitialized) = position.checked_sub(initialized) {
+ // Cursor's solution is to pad with zeroes
+ // From the end of valid data (as_slice().len()) to the position.
+
+ // Safety:
+ // * We know `n > 0`
+ // * This means `self.capacity() > 0` (promise by the caller)
+ // * This means `self.get_ref().capacity() > self.position`
+ // * This means that `position` is within the nested pointer's allocation.
+ // * Finally, `initialized + uninitialized = position`, so the entire byte
+ // range here is within the allocation
+ unsafe {
+ self.get_mut()
+ .as_mut_ptr()
+ .add(initialized)
+ .write_bytes(0u8, uninitialized)
+ };
+ }
+
+ let start = self.position() as usize;
+ assert!(start + n <= self.get_ref().capacity());
+ self.get_mut().filled_until(start + n);
+ }
+}
+
+#[cfg(feature = "std")]
+#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "std")))]
+unsafe impl<'a> WriteBuf for &'a mut std::vec::Vec<u8> {
+ fn as_slice(&self) -> &[u8] {
+ std::vec::Vec::as_slice(self)
+ }
+
+ fn capacity(&self) -> usize {
+ std::vec::Vec::capacity(self)
+ }
+
+ fn as_mut_ptr(&mut self) -> *mut u8 {
+ std::vec::Vec::as_mut_ptr(self)
+ }
+
+ unsafe fn filled_until(&mut self, n: usize) {
+ std::vec::Vec::set_len(self, n)
+ }
+}
+
+#[cfg(feature = "std")]
+#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "std")))]
+unsafe impl WriteBuf for std::vec::Vec<u8> {
+ fn as_slice(&self) -> &[u8] {
+ &self[..]
+ }
+ fn capacity(&self) -> usize {
+ self.capacity()
+ }
+ fn as_mut_ptr(&mut self) -> *mut u8 {
+ self.as_mut_ptr()
+ }
+ unsafe fn filled_until(&mut self, n: usize) {
+ self.set_len(n);
+ }
+}
+
+#[cfg(feature = "arrays")]
+#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "arrays")))]
+unsafe impl<const N: usize> WriteBuf for [u8; N] {
+ fn as_slice(&self) -> &[u8] {
+ self
+ }
+ fn capacity(&self) -> usize {
+ self.len()
+ }
+
+ fn as_mut_ptr(&mut self) -> *mut u8 {
+ (&mut self[..]).as_mut_ptr()
+ }
+
+ unsafe fn filled_until(&mut self, _n: usize) {
+ // Assume the slice is already initialized
+ }
+}
+
+unsafe impl WriteBuf for [u8] {
+ fn as_slice(&self) -> &[u8] {
+ self
+ }
+ fn capacity(&self) -> usize {
+ self.len()
+ }
+
+ fn as_mut_ptr(&mut self) -> *mut u8 {
+ self.as_mut_ptr()
+ }
+
+ unsafe fn filled_until(&mut self, _n: usize) {
+ // Assume the slice is already initialized
+ }
+}
+
+/*
+// This is possible, but... why?
+unsafe impl<'a> WriteBuf for OutBuffer<'a, [u8]> {
+ fn as_slice(&self) -> &[u8] {
+ self.dst
+ }
+ fn capacity(&self) -> usize {
+ self.dst.len()
+ }
+ fn as_mut_ptr(&mut self) -> *mut u8 {
+ self.dst.as_mut_ptr()
+ }
+ unsafe fn filled_until(&mut self, n: usize) {
+ self.pos = n;
+ }
+}
+*/
+
+#[derive(Debug)]
+/// Wrapper around an output buffer.
+///
+/// `C` is usually either `[u8]` or `Vec<u8>`.
+///
+/// Bytes will be written starting at `dst[pos]`.
+///
+/// `pos` will be updated after writing.
+///
+/// # Invariant
+///
+/// `pos <= dst.capacity()`
+pub struct OutBuffer<'a, C: WriteBuf + ?Sized> {
+ pub dst: &'a mut C,
+ pos: usize,
+}
+
+/// Convenience method to get a mut pointer from a mut ref.
+fn ptr_mut<B>(ptr_void: &mut B) -> *mut B {
+ ptr_void as *mut B
+}
+
+/// Interface between a C-level ZSTD_outBuffer and a rust-level `OutBuffer`.
+///
+/// Will update the parent buffer from the C buffer on drop.
+struct OutBufferWrapper<'a, 'b, C: WriteBuf + ?Sized> {
+ buf: zstd_sys::ZSTD_outBuffer,
+ parent: &'a mut OutBuffer<'b, C>,
+}
+
+impl<'a, 'b: 'a, C: WriteBuf + ?Sized> Deref for OutBufferWrapper<'a, 'b, C> {
+ type Target = zstd_sys::ZSTD_outBuffer;
+
+ fn deref(&self) -> &Self::Target {
+ &self.buf
+ }
+}
+
+impl<'a, 'b: 'a, C: WriteBuf + ?Sized> DerefMut
+ for OutBufferWrapper<'a, 'b, C>
+{
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.buf
+ }
+}
+
+impl<'a, C: WriteBuf + ?Sized> OutBuffer<'a, C> {
+ /// Returns a new `OutBuffer` around the given slice.
+ ///
+ /// Starts with `pos = 0`.
+ pub fn around(dst: &'a mut C) -> Self {
+ OutBuffer { dst, pos: 0 }
+ }
+
+ /// Returns a new `OutBuffer` around the given slice, starting at the given position.
+ ///
+ /// # Panics
+ ///
+ /// If `pos >= dst.capacity()`.
+ pub fn around_pos(dst: &'a mut C, pos: usize) -> Self {
+ if pos >= dst.capacity() {
+ panic!("Given position outside of the buffer bounds.");
+ }
+
+ OutBuffer { dst, pos }
+ }
+
+ /// Returns the current cursor position.
+ pub fn pos(&self) -> usize {
+ self.pos
+ }
+
+ /// Sets the new cursor position.
+ ///
+ /// # Panics
+ ///
+ /// If `pos > self.dst.capacity()`.
+ ///
+ /// # Safety
+ ///
+ /// Data up to `pos` must have actually been written to.
+ pub unsafe fn set_pos(&mut self, pos: usize) {
+ if pos > self.dst.capacity() {
+ panic!("Given position outside of the buffer bounds.");
+ }
+
+ self.dst.filled_until(pos);
+
+ self.pos = pos;
+ }
+
+ fn wrap<'b>(&'b mut self) -> OutBufferWrapper<'b, 'a, C> {
+ OutBufferWrapper {
+ buf: zstd_sys::ZSTD_outBuffer {
+ dst: ptr_mut_void(self.dst),
+ size: self.dst.capacity(),
+ pos: self.pos,
+ },
+ parent: self,
+ }
+ }
+
+ /// Returns the part of this buffer that was written to.
+ pub fn as_slice<'b>(&'b self) -> &'a [u8]
+ where
+ 'b: 'a,
+ {
+ let pos = self.pos;
+ &self.dst.as_slice()[..pos]
+ }
+}
+
+impl<'a, 'b, C: WriteBuf + ?Sized> Drop for OutBufferWrapper<'a, 'b, C> {
+ fn drop(&mut self) {
+ // Safe because we guarantee that data until `self.buf.pos` has been written.
+ unsafe { self.parent.set_pos(self.buf.pos) };
+ }
+}
+
+struct InBufferWrapper<'a, 'b> {
+ buf: zstd_sys::ZSTD_inBuffer,
+ parent: &'a mut InBuffer<'b>,
+}
+
+impl<'a, 'b: 'a> Deref for InBufferWrapper<'a, 'b> {
+ type Target = zstd_sys::ZSTD_inBuffer;
+
+ fn deref(&self) -> &Self::Target {
+ &self.buf
+ }
+}
+
+impl<'a, 'b: 'a> DerefMut for InBufferWrapper<'a, 'b> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.buf
+ }
+}
+
+impl<'a> InBuffer<'a> {
+ /// Returns a new `InBuffer` around the given slice.
+ ///
+ /// Starts with `pos = 0`.
+ pub fn around(src: &'a [u8]) -> Self {
+ InBuffer { src, pos: 0 }
+ }
+
+ /// Returns the current cursor position.
+ pub fn pos(&self) -> usize {
+ self.pos
+ }
+
+ /// Sets the new cursor position.
+ ///
+ /// # Panics
+ ///
+ /// If `pos > self.src.len()`.
+ pub fn set_pos(&mut self, pos: usize) {
+ if pos > self.src.len() {
+ panic!("Given position outside of the buffer bounds.");
+ }
+ self.pos = pos;
+ }
+
+ fn wrap<'b>(&'b mut self) -> InBufferWrapper<'b, 'a> {
+ InBufferWrapper {
+ buf: zstd_sys::ZSTD_inBuffer {
+ src: ptr_void(self.src),
+ size: self.src.len(),
+ pos: self.pos,
+ },
+ parent: self,
+ }
+ }
+}
+
+impl<'a, 'b> Drop for InBufferWrapper<'a, 'b> {
+ fn drop(&mut self) {
+ self.parent.set_pos(self.buf.pos);
+ }
+}
+
+/// A Decompression stream.
+///
+/// Same as `DCtx`.
+pub type DStream<'a> = DCtx<'a>;
+
+// Some functions work on a "frame prefix".
+// TODO: Define `struct FramePrefix(&[u8]);` and move these functions to it?
+//
+// Some other functions work on a dictionary (not CDict or DDict).
+// Same thing?
+
+/// Wraps the `ZSTD_findFrameCompressedSize()` function.
+///
+/// `src` should contain at least an entire frame.
+pub fn find_frame_compressed_size(src: &[u8]) -> SafeResult {
+ let code = unsafe {
+ zstd_sys::ZSTD_findFrameCompressedSize(ptr_void(src), src.len())
+ };
+ parse_code(code)
+}
+
+/// Wraps the `ZSTD_getFrameContentSize()` function.
+///
+/// Args:
+/// * `src`: A prefix of the compressed frame. It should at least include the frame header.
+///
+/// Returns:
+/// * `Err(ContentSizeError)` if `src` is too small of a prefix, or if it appears corrupted.
+/// * `Ok(None)` if the frame does not include a content size.
+/// * `Ok(Some(content_size_in_bytes))` otherwise.
+pub fn get_frame_content_size(
+ src: &[u8],
+) -> Result<Option<u64>, ContentSizeError> {
+ parse_content_size(unsafe {
+ zstd_sys::ZSTD_getFrameContentSize(ptr_void(src), src.len())
+ })
+}
+
+/// Wraps the `ZSTD_findDecompressedSize()` function.
+///
+/// `src` should be exactly a sequence of ZSTD frames.
+#[cfg(feature = "experimental")]
+#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+pub fn find_decompressed_size(
+ src: &[u8],
+) -> Result<Option<u64>, ContentSizeError> {
+ parse_content_size(unsafe {
+ zstd_sys::ZSTD_findDecompressedSize(ptr_void(src), src.len())
+ })
+}
+
+/// Wraps the `ZSTD_isFrame()` function.
+#[cfg(feature = "experimental")]
+#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+pub fn is_frame(buffer: &[u8]) -> bool {
+ unsafe { zstd_sys::ZSTD_isFrame(ptr_void(buffer), buffer.len()) > 0 }
+}
+
+/// Wraps the `ZSTD_getDictID_fromDict()` function.
+///
+/// Returns `None` if the dictionary is not a valid zstd dictionary.
+pub fn get_dict_id_from_dict(dict: &[u8]) -> Option<NonZeroU32> {
+ NonZeroU32::new(unsafe {
+ zstd_sys::ZSTD_getDictID_fromDict(ptr_void(dict), dict.len()) as u32
+ })
+}
+
+/// Wraps the `ZSTD_getDictID_fromFrame()` function.
+///
+/// Returns `None` if the dictionary ID could not be decoded. This may happen if:
+/// * The frame was not encoded with a dictionary.
+/// * The frame intentionally did not include dicionary ID.
+/// * The dictionary was non-conformant.
+/// * `src` is too small and does not include the frame header.
+/// * `src` is not a valid zstd frame prefix.
+pub fn get_dict_id_from_frame(src: &[u8]) -> Option<NonZeroU32> {
+ NonZeroU32::new(unsafe {
+ zstd_sys::ZSTD_getDictID_fromFrame(ptr_void(src), src.len()) as u32
+ })
+}
+
+/// What kind of context reset should be applied.
+pub enum ResetDirective {
+ /// Only the session will be reset.
+ ///
+ /// All parameters will be preserved (including the dictionary).
+ /// But any frame being processed will be dropped.
+ ///
+ /// It can be useful to start re-using a context after an error or when an
+ /// ongoing compression is no longer needed.
+ SessionOnly,
+
+ /// Only reset parameters (including dictionary or referenced prefix).
+ ///
+ /// All parameters will be reset to default values.
+ ///
+ /// This can only be done between sessions - no compression or decompression must be ongoing.
+ Parameters,
+
+ /// Reset both the session and parameters.
+ ///
+ /// The result is similar to a newly created context.
+ SessionAndParameters,
+}
+
+impl ResetDirective {
+ fn as_sys(self) -> zstd_sys::ZSTD_ResetDirective {
+ match self {
+ ResetDirective::SessionOnly => zstd_sys::ZSTD_ResetDirective::ZSTD_reset_session_only,
+ ResetDirective::Parameters => zstd_sys::ZSTD_ResetDirective::ZSTD_reset_parameters,
+ ResetDirective::SessionAndParameters => zstd_sys::ZSTD_ResetDirective::ZSTD_reset_session_and_parameters,
+ }
+ }
+}
+
+#[cfg(feature = "experimental")]
+#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[repr(u32)]
+pub enum FrameFormat {
+ /// Regular zstd format.
+ One = zstd_sys::ZSTD_format_e::ZSTD_f_zstd1 as u32,
+
+ /// Skip the 4 bytes identifying the content as zstd-compressed data.
+ Magicless = zstd_sys::ZSTD_format_e::ZSTD_f_zstd1_magicless as u32,
+}
+
+#[cfg(feature = "experimental")]
+#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[repr(u32)]
+pub enum DictAttachPref {
+ DefaultAttach =
+ zstd_sys::ZSTD_dictAttachPref_e::ZSTD_dictDefaultAttach as u32,
+ ForceAttach = zstd_sys::ZSTD_dictAttachPref_e::ZSTD_dictForceAttach as u32,
+ ForceCopy = zstd_sys::ZSTD_dictAttachPref_e::ZSTD_dictForceCopy as u32,
+ ForceLoad = zstd_sys::ZSTD_dictAttachPref_e::ZSTD_dictForceLoad as u32,
+}
+
+#[cfg(feature = "experimental")]
+#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[repr(u32)]
+pub enum ParamSwitch {
+ Auto = zstd_sys::ZSTD_paramSwitch_e::ZSTD_ps_auto as u32,
+ Enable = zstd_sys::ZSTD_paramSwitch_e::ZSTD_ps_enable as u32,
+ Disable = zstd_sys::ZSTD_paramSwitch_e::ZSTD_ps_disable as u32,
+}
+
+/// A compression parameter.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum CParameter {
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ RSyncable(bool),
+
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ Format(FrameFormat),
+
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ ForceMaxWindow(bool),
+
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ ForceAttachDict(DictAttachPref),
+
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ LiteralCompressionMode(ParamSwitch),
+
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ TargetCBlockSize(u32),
+
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ SrcSizeHint(u32),
+
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ EnableDedicatedDictSearch(bool),
+
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ StableInBuffer(bool),
+
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ StableOutBuffer(bool),
+
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ BlockDelimiters(bool),
+
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ ValidateSequences(bool),
+
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ UseBlockSplitter(ParamSwitch),
+
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ UseRowMatchFinder(ParamSwitch),
+
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ DeterministicRefPrefix(bool),
+
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ PrefetchCDictTables(ParamSwitch),
+
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ EnableSeqProducerFallback(bool),
+
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ MaxBlockSize(u32),
+
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ SearchForExternalRepcodes(ParamSwitch),
+
+ /// Compression level to use.
+ ///
+ /// Compression levels are global presets for the other compression parameters.
+ CompressionLevel(CompressionLevel),
+
+ /// Maximum allowed back-reference distance.
+ ///
+ /// The actual distance is 2 power "this value".
+ WindowLog(u32),
+
+ HashLog(u32),
+
+ ChainLog(u32),
+
+ SearchLog(u32),
+
+ MinMatch(u32),
+
+ TargetLength(u32),
+
+ Strategy(Strategy),
+
+ EnableLongDistanceMatching(bool),
+
+ LdmHashLog(u32),
+
+ LdmMinMatch(u32),
+
+ LdmBucketSizeLog(u32),
+
+ LdmHashRateLog(u32),
+
+ ContentSizeFlag(bool),
+
+ ChecksumFlag(bool),
+
+ DictIdFlag(bool),
+
+ /// How many threads will be spawned.
+ ///
+ /// With a default value of `0`, `compress_stream*` functions block until they complete.
+ ///
+ /// With any other value (including 1, a single compressing thread), these methods directly
+ /// return, and the actual compression is done in the background (until a flush is requested).
+ ///
+ /// Note: this will only work if the `zstdmt` feature is activated.
+ NbWorkers(u32),
+
+ /// Size in bytes of a compression job.
+ ///
+ /// Does not have any effect when `NbWorkers` is set to 0.
+ ///
+ /// The default value of 0 finds the best job size based on the compression parameters.
+ ///
+ /// Note: this will only work if the `zstdmt` feature is activated.
+ JobSize(u32),
+
+ /// Specifies how much overlap must be given to each worker.
+ ///
+ /// Possible values:
+ ///
+ /// * `0` (default value): automatic overlap based on compression strategy.
+ /// * `1`: No overlap
+ /// * `1 < n < 9`: Overlap a fraction of the window size, defined as `1/(2 ^ 9-n)`.
+ /// * `9`: Full overlap (as long as the window)
+ /// * `9 < m`: Will return an error.
+ ///
+ /// Note: this will only work if the `zstdmt` feature is activated.
+ OverlapSizeLog(u32),
+}
+
+/// A decompression parameter.
+pub enum DParameter {
+ WindowLogMax(u32),
+
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ /// See `FrameFormat`.
+ Format(FrameFormat),
+
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ StableOutBuffer(bool),
+
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ ForceIgnoreChecksum(bool),
+
+ #[cfg(feature = "experimental")]
+ #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+ RefMultipleDDicts(bool),
+}
+
+/// Wraps the `ZDICT_trainFromBuffer()` function.
+#[cfg(feature = "zdict_builder")]
+#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "zdict_builder")))]
+pub fn train_from_buffer<C: WriteBuf + ?Sized>(
+ dict_buffer: &mut C,
+ samples_buffer: &[u8],
+ samples_sizes: &[usize],
+) -> SafeResult {
+ assert_eq!(samples_buffer.len(), samples_sizes.iter().sum());
+
+ unsafe {
+ dict_buffer.write_from(|buffer, capacity| {
+ parse_code(zstd_sys::ZDICT_trainFromBuffer(
+ buffer,
+ capacity,
+ ptr_void(samples_buffer),
+ samples_sizes.as_ptr(),
+ samples_sizes.len() as u32,
+ ))
+ })
+ }
+}
+
+/// Wraps the `ZDICT_getDictID()` function.
+#[cfg(feature = "zdict_builder")]
+#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "zdict_builder")))]
+pub fn get_dict_id(dict_buffer: &[u8]) -> Option<NonZeroU32> {
+ NonZeroU32::new(unsafe {
+ zstd_sys::ZDICT_getDictID(ptr_void(dict_buffer), dict_buffer.len())
+ })
+}
+
+/// Wraps the `ZSTD_getBlockSize()` function.
+#[cfg(feature = "experimental")]
+#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+pub fn get_block_size(cctx: &CCtx) -> usize {
+ unsafe { zstd_sys::ZSTD_getBlockSize(cctx.0.as_ptr()) }
+}
+
+/// Wraps the `ZSTD_decompressBound` function
+#[cfg(feature = "experimental")]
+#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+pub fn decompress_bound(data: &[u8]) -> Result<u64, ErrorCode> {
+ let bound =
+ unsafe { zstd_sys::ZSTD_decompressBound(ptr_void(data), data.len()) };
+ if is_error(bound as usize) {
+ Err(bound as usize)
+ } else {
+ Ok(bound)
+ }
+}
+
+/// Given a buffer of size `src_size`, returns the maximum number of sequences that can ge
+/// generated.
+#[cfg(feature = "experimental")]
+#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+pub fn sequence_bound(src_size: usize) -> usize {
+ // Safety: Just FFI.
+ unsafe { zstd_sys::ZSTD_sequenceBound(src_size) }
+}
+
+/// Returns the minimum extra space when output and input buffer overlap.
+///
+/// When using in-place decompression, the output buffer must be at least this much bigger (in
+/// bytes) than the input buffer. The extra space must be at the front of the output buffer (the
+/// input buffer must be at the end of the output buffer).
+#[cfg(feature = "experimental")]
+#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "experimental")))]
+pub fn decompression_margin(
+ compressed_data: &[u8],
+) -> Result<usize, ErrorCode> {
+ parse_code(unsafe {
+ zstd_sys::ZSTD_decompressionMargin(
+ ptr_void(compressed_data),
+ compressed_data.len(),
+ )
+ })
+}