feat(compress): port the CCtx parameter object API to Rust

Move CCtx parameter reset/init, parameter set/get, and external sequence
producer registration to Rust with a private C-layout mirror. Keep allocation,
advanced initialization, bounds policy, and context-dependent APIs in C, and
retain C layout assertions and helper bridges for the shared object.

Test Plan:
- rustfmt +nightly --check --edition 2021 rust/src/zstd_compress_params_api.rs rust/src/lib.rs
- RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo test --manifest-path rust/Cargo.toml --no-default-features --features compression zstd_compress_params_api
- RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo clippy --manifest-path rust/Cargo.toml --all-targets --no-default-features --features compression -- -D warnings
- make -B -C lib libzstd.a V=1
- git diff --cached --check
This commit is contained in:
2026-07-12 10:48:40 +02:00
parent 49454dada9
commit 0d3dae8fd8
3 changed files with 1107 additions and 424 deletions
+2
View File
@@ -42,6 +42,8 @@ pub mod zstd_compress_literals;
#[cfg(feature = "compression")]
pub mod zstd_compress_params;
#[cfg(feature = "compression")]
pub mod zstd_compress_params_api;
#[cfg(feature = "compression")]
pub mod zstd_compress_sequences;
#[cfg(feature = "compression")]
pub mod zstd_compress_stats;
+1000
View File
@@ -0,0 +1,1000 @@
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(clippy::missing_safety_doc)]
#![allow(clippy::too_many_arguments)]
//! Rust implementation of the narrow `ZSTD_CCtx_params` object API.
//!
//! Allocation, advanced initialization, bounds calculation, and context
//! policy remain in `zstd_compress.c`. This module owns only the object
//! reset/initialization, parameter set/get, and sequence-producer registration
//! entry points.
use crate::errors::{ZstdErrorCode, ERROR};
#[cfg(test)]
use crate::zstd_compress_params::ZSTD_bounds;
use crate::zstd_compress_params::{ZSTD_compressionParameters, ZSTD_frameParameters};
use std::mem::size_of;
use std::os::raw::{c_int, c_void};
use std::ptr;
type ZstdAllocFunction = unsafe extern "C" fn(*mut c_void, usize) -> *mut c_void;
type ZstdFreeFunction = unsafe extern "C" fn(*mut c_void, *mut c_void);
/// ABI-compatible representation of `ZSTD_customMem`.
#[repr(C)]
#[derive(Clone, Copy)]
struct ZSTD_customMem {
customAlloc: Option<ZstdAllocFunction>,
customFree: Option<ZstdFreeFunction>,
opaque: *mut c_void,
}
/// ABI-compatible private LDM parameter object.
#[repr(C)]
#[derive(Clone, Copy, Default)]
struct ldmParams_t {
enableLdm: c_int,
hashLog: u32,
bucketSizeLog: u32,
minMatchLength: u32,
hashRateLog: u32,
windowLog: u32,
}
/// ABI-compatible callback parameter type from `zstd.h`.
#[repr(C)]
#[derive(Clone, Copy, Default)]
pub struct ZSTD_Sequence {
offset: u32,
litLength: u32,
matchLength: u32,
rep: u32,
}
pub type ZSTD_sequenceProducer_F = unsafe extern "C" fn(
sequenceProducerState: *mut c_void,
outSeqs: *mut ZSTD_Sequence,
outSeqsCapacity: usize,
src: *const c_void,
srcSize: usize,
dict: *const c_void,
dictSize: usize,
compressionLevel: c_int,
windowSize: usize,
) -> usize;
/// Layout mirror for `struct ZSTD_CCtx_params_s`.
///
/// The type is public only because exported Rust ABI functions cannot expose
/// a private type. Its fields and all helper types remain private to this
/// module; C remains the owner of the actual parameter object contract.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct ZSTD_CCtx_params {
format: c_int,
cParams: ZSTD_compressionParameters,
fParams: ZSTD_frameParameters,
compressionLevel: c_int,
forceWindow: c_int,
targetCBlockSize: usize,
srcSizeHint: c_int,
attachDictPref: c_int,
literalCompressionMode: c_int,
nbWorkers: c_int,
jobSize: usize,
overlapLog: c_int,
rsyncable: c_int,
ldmParams: ldmParams_t,
enableDedicatedDictSearch: c_int,
inBufferMode: c_int,
outBufferMode: c_int,
blockDelimiters: c_int,
validateSequences: c_int,
postBlockSplitter: c_int,
preBlockSplitter_level: c_int,
maxBlockSize: usize,
useRowMatchFinder: c_int,
deterministicRefPrefix: c_int,
customMem: ZSTD_customMem,
prefetchCDictTables: c_int,
enableMatchFinderFallback: c_int,
extSeqProdState: *mut c_void,
extSeqProdFunc: Option<ZSTD_sequenceProducer_F>,
searchForExternalRepcodes: c_int,
}
const DEFAULT_CLEVEL: c_int = 3;
#[cfg(test)]
const PS_AUTO: c_int = 0;
#[cfg(test)]
const PS_DISABLE: c_int = 2;
#[cfg(test)]
const BLOCKSIZE_MAX: usize = 1 << 17;
#[cfg(test)]
const BLOCKSIZE_MAX_MIN: c_int = 1 << 10;
const TARGET_C_BLOCK_SIZE_MIN: c_int = 1340;
const C_COMPRESSION_LEVEL: c_int = 100;
const C_WINDOW_LOG: c_int = 101;
const C_HASH_LOG: c_int = 102;
const C_CHAIN_LOG: c_int = 103;
const C_SEARCH_LOG: c_int = 104;
const C_MIN_MATCH: c_int = 105;
const C_TARGET_LENGTH: c_int = 106;
const C_STRATEGY: c_int = 107;
const C_TARGET_C_BLOCK_SIZE: c_int = 130;
const C_ENABLE_LDM: c_int = 160;
const C_LDM_HASH_LOG: c_int = 161;
const C_LDM_MIN_MATCH: c_int = 162;
const C_LDM_BUCKET_SIZE_LOG: c_int = 163;
const C_LDM_HASH_RATE_LOG: c_int = 164;
const C_CONTENT_SIZE_FLAG: c_int = 200;
const C_CHECKSUM_FLAG: c_int = 201;
const C_DICT_ID_FLAG: c_int = 202;
const C_NB_WORKERS: c_int = 400;
const C_JOB_SIZE: c_int = 401;
const C_OVERLAP_LOG: c_int = 402;
const C_RSYNCABLE: c_int = 500;
const C_FORMAT: c_int = 10;
const C_FORCE_MAX_WINDOW: c_int = 1000;
const C_FORCE_ATTACH_DICT: c_int = 1001;
const C_LITERAL_COMPRESSION_MODE: c_int = 1002;
const C_SRC_SIZE_HINT: c_int = 1004;
const C_ENABLE_DEDICATED_DICT_SEARCH: c_int = 1005;
const C_STABLE_IN_BUFFER: c_int = 1006;
const C_STABLE_OUT_BUFFER: c_int = 1007;
const C_BLOCK_DELIMITERS: c_int = 1008;
const C_VALIDATE_SEQUENCES: c_int = 1009;
const C_SPLIT_AFTER_SEQUENCES: c_int = 1010;
const C_USE_ROW_MATCH_FINDER: c_int = 1011;
const C_DETERMINISTIC_REF_PREFIX: c_int = 1012;
const C_PREFETCH_CDICT_TABLES: c_int = 1013;
const C_ENABLE_SEQ_PRODUCER_FALLBACK: c_int = 1014;
const C_MAX_BLOCK_SIZE: c_int = 1015;
const C_REPCODE_RESOLUTION: c_int = 1016;
const C_BLOCK_SPLITTER_LEVEL: c_int = 1017;
#[cfg(not(test))]
unsafe extern "C" {
fn ZSTD_rust_cctx_params_clamp_bounds(param: c_int, value: *mut c_int) -> usize;
fn ZSTD_rust_cctx_params_within_bounds(param: c_int, value: c_int) -> c_int;
fn ZSTD_rust_cctx_params_is_multithreaded() -> c_int;
fn ZSTD_rust_cctx_params_job_size_min() -> c_int;
}
#[cfg(test)]
fn fallback_bounds(param: c_int) -> ZSTD_bounds {
let (lower, upper) = match param {
C_COMPRESSION_LEVEL | C_WINDOW_LOG | C_HASH_LOG | C_CHAIN_LOG | C_SEARCH_LOG
| C_MIN_MATCH | C_TARGET_LENGTH | C_STRATEGY => {
return crate::zstd_compress_params::ZSTD_rust_params_getBounds(param)
}
C_FORMAT => (0, 1),
C_FORCE_ATTACH_DICT => (0, 3),
C_LITERAL_COMPRESSION_MODE => (0, 2),
C_CONTENT_SIZE_FLAG | C_CHECKSUM_FLAG | C_DICT_ID_FLAG => (0, 1),
C_NB_WORKERS | C_JOB_SIZE | C_OVERLAP_LOG => (0, 0),
C_ENABLE_DEDICATED_DICT_SEARCH => (0, 1),
C_ENABLE_LDM
| C_SPLIT_AFTER_SEQUENCES
| C_USE_ROW_MATCH_FINDER
| C_PREFETCH_CDICT_TABLES
| C_REPCODE_RESOLUTION => (PS_AUTO, PS_DISABLE),
C_LDM_HASH_LOG => (6, 30),
C_LDM_MIN_MATCH => (4, 4096),
C_LDM_BUCKET_SIZE_LOG => (1, 8),
C_LDM_HASH_RATE_LOG => (0, if size_of::<usize>() == 4 { 24 } else { 25 }),
C_RSYNCABLE
| C_FORCE_MAX_WINDOW
| C_VALIDATE_SEQUENCES
| C_DETERMINISTIC_REF_PREFIX
| C_ENABLE_SEQ_PRODUCER_FALLBACK => (0, 1),
C_TARGET_C_BLOCK_SIZE => (TARGET_C_BLOCK_SIZE_MIN, BLOCKSIZE_MAX as c_int),
C_SRC_SIZE_HINT => (0, c_int::MAX),
C_STABLE_IN_BUFFER | C_STABLE_OUT_BUFFER | C_BLOCK_DELIMITERS => (0, 1),
C_BLOCK_SPLITTER_LEVEL => (0, 6),
C_MAX_BLOCK_SIZE => (BLOCKSIZE_MAX_MIN, BLOCKSIZE_MAX as c_int),
_ => {
return ZSTD_bounds {
error: ERROR(ZstdErrorCode::ParameterUnsupported),
lowerBound: 0,
upperBound: 0,
};
}
};
ZSTD_bounds {
error: 0,
lowerBound: lower,
upperBound: upper,
}
}
#[inline]
unsafe fn within_bounds(param: c_int, value: c_int) -> bool {
#[cfg(not(test))]
{
unsafe { ZSTD_rust_cctx_params_within_bounds(param, value) != 0 }
}
#[cfg(test)]
{
let bounds = fallback_bounds(param);
bounds.error == 0 && value >= bounds.lowerBound && value <= bounds.upperBound
}
}
#[inline]
unsafe fn clamp_bounds(param: c_int, value: &mut c_int) -> usize {
#[cfg(not(test))]
{
unsafe { ZSTD_rust_cctx_params_clamp_bounds(param, value) }
}
#[cfg(test)]
{
let bounds = fallback_bounds(param);
if bounds.error != 0 {
return bounds.error;
}
*value = (*value).clamp(bounds.lowerBound, bounds.upperBound);
0
}
}
#[inline]
unsafe fn multithreading_enabled() -> bool {
#[cfg(not(test))]
{
unsafe { ZSTD_rust_cctx_params_is_multithreaded() != 0 }
}
#[cfg(test)]
{
false
}
}
#[inline]
unsafe fn job_size_min() -> c_int {
#[cfg(not(test))]
{
unsafe { ZSTD_rust_cctx_params_job_size_min() }
}
#[cfg(test)]
{
512 << 10
}
}
#[inline]
unsafe fn require_bounds(param: c_int, value: c_int) -> Result<(), usize> {
if unsafe { within_bounds(param, value) } {
Ok(())
} else {
Err(ERROR(ZstdErrorCode::ParameterOutOfBound))
}
}
unsafe fn init_impl(params: *mut ZSTD_CCtx_params, compression_level: c_int) -> usize {
if params.is_null() {
return ERROR(ZstdErrorCode::Generic);
}
unsafe {
ptr::write_bytes(params.cast::<u8>(), 0, size_of::<ZSTD_CCtx_params>());
(*params).compressionLevel = compression_level;
(*params).fParams.contentSizeFlag = 1;
}
0
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_CCtxParams_reset(params: *mut ZSTD_CCtx_params) -> usize {
unsafe { init_impl(params, DEFAULT_CLEVEL) }
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_CCtxParams_init(
params: *mut ZSTD_CCtx_params,
compression_level: c_int,
) -> usize {
unsafe { init_impl(params, compression_level) }
}
#[inline]
unsafe fn set_parameter(params: *mut ZSTD_CCtx_params, param: c_int, mut value: c_int) -> usize {
let params = unsafe { &mut *params };
match param {
C_FORMAT => {
if let Err(error) = unsafe { require_bounds(C_FORMAT, value) } {
return error;
}
params.format = value;
value as usize
}
C_COMPRESSION_LEVEL => {
let error = unsafe { clamp_bounds(C_COMPRESSION_LEVEL, &mut value) };
if error != 0 {
return error;
}
params.compressionLevel = if value == 0 { DEFAULT_CLEVEL } else { value };
if params.compressionLevel >= 0 {
params.compressionLevel as usize
} else {
0
}
}
C_WINDOW_LOG => {
if value != 0 {
if let Err(error) = unsafe { require_bounds(C_WINDOW_LOG, value) } {
return error;
}
}
params.cParams.windowLog = value as u32;
params.cParams.windowLog as usize
}
C_HASH_LOG => {
if value != 0 {
if let Err(error) = unsafe { require_bounds(C_HASH_LOG, value) } {
return error;
}
}
params.cParams.hashLog = value as u32;
params.cParams.hashLog as usize
}
C_CHAIN_LOG => {
if value != 0 {
if let Err(error) = unsafe { require_bounds(C_CHAIN_LOG, value) } {
return error;
}
}
params.cParams.chainLog = value as u32;
params.cParams.chainLog as usize
}
C_SEARCH_LOG => {
if value != 0 {
if let Err(error) = unsafe { require_bounds(C_SEARCH_LOG, value) } {
return error;
}
}
params.cParams.searchLog = value as u32;
value as usize
}
C_MIN_MATCH => {
if value != 0 {
if let Err(error) = unsafe { require_bounds(C_MIN_MATCH, value) } {
return error;
}
}
params.cParams.minMatch = value as u32;
params.cParams.minMatch as usize
}
C_TARGET_LENGTH => {
if let Err(error) = unsafe { require_bounds(C_TARGET_LENGTH, value) } {
return error;
}
params.cParams.targetLength = value as u32;
params.cParams.targetLength as usize
}
C_STRATEGY => {
if value != 0 {
if let Err(error) = unsafe { require_bounds(C_STRATEGY, value) } {
return error;
}
}
params.cParams.strategy = value;
value as usize
}
C_CONTENT_SIZE_FLAG => {
params.fParams.contentSizeFlag = (value != 0) as c_int;
params.fParams.contentSizeFlag as usize
}
C_CHECKSUM_FLAG => {
params.fParams.checksumFlag = (value != 0) as c_int;
params.fParams.checksumFlag as usize
}
C_DICT_ID_FLAG => {
params.fParams.noDictIDFlag = (value == 0) as c_int;
(params.fParams.noDictIDFlag == 0) as usize
}
C_FORCE_MAX_WINDOW => {
params.forceWindow = (value != 0) as c_int;
params.forceWindow as usize
}
C_FORCE_ATTACH_DICT => {
if let Err(error) = unsafe { require_bounds(C_FORCE_ATTACH_DICT, value) } {
return error;
}
params.attachDictPref = value;
value as usize
}
C_LITERAL_COMPRESSION_MODE => {
if let Err(error) = unsafe { require_bounds(C_LITERAL_COMPRESSION_MODE, value) } {
return error;
}
params.literalCompressionMode = value;
value as usize
}
C_NB_WORKERS => {
if !unsafe { multithreading_enabled() } {
if value != 0 {
return ERROR(ZstdErrorCode::ParameterUnsupported);
}
return 0;
}
let error = unsafe { clamp_bounds(C_NB_WORKERS, &mut value) };
if error != 0 {
return error;
}
params.nbWorkers = value;
params.nbWorkers as usize
}
C_JOB_SIZE => {
if !unsafe { multithreading_enabled() } {
if value != 0 {
return ERROR(ZstdErrorCode::ParameterUnsupported);
}
return 0;
}
let minimum = unsafe { job_size_min() };
if value != 0 && value < minimum {
value = minimum;
}
let error = unsafe { clamp_bounds(C_JOB_SIZE, &mut value) };
if error != 0 {
return error;
}
params.jobSize = value as usize;
params.jobSize
}
C_OVERLAP_LOG => {
if !unsafe { multithreading_enabled() } {
if value != 0 {
return ERROR(ZstdErrorCode::ParameterUnsupported);
}
return 0;
}
let error = unsafe { clamp_bounds(C_OVERLAP_LOG, &mut value) };
if error != 0 {
return error;
}
params.overlapLog = value;
params.overlapLog as usize
}
C_RSYNCABLE => {
if !unsafe { multithreading_enabled() } {
if value != 0 {
return ERROR(ZstdErrorCode::ParameterUnsupported);
}
return 0;
}
/* The C implementation intentionally clamps this with overlapLog. */
let error = unsafe { clamp_bounds(C_OVERLAP_LOG, &mut value) };
if error != 0 {
return error;
}
params.rsyncable = value;
params.rsyncable as usize
}
C_ENABLE_DEDICATED_DICT_SEARCH => {
params.enableDedicatedDictSearch = (value != 0) as c_int;
params.enableDedicatedDictSearch as usize
}
C_ENABLE_LDM => {
if let Err(error) = unsafe { require_bounds(C_ENABLE_LDM, value) } {
return error;
}
params.ldmParams.enableLdm = value;
value as usize
}
C_LDM_HASH_LOG => {
if value != 0 {
if let Err(error) = unsafe { require_bounds(C_LDM_HASH_LOG, value) } {
return error;
}
}
params.ldmParams.hashLog = value as u32;
params.ldmParams.hashLog as usize
}
C_LDM_MIN_MATCH => {
if value != 0 {
if let Err(error) = unsafe { require_bounds(C_LDM_MIN_MATCH, value) } {
return error;
}
}
params.ldmParams.minMatchLength = value as u32;
params.ldmParams.minMatchLength as usize
}
C_LDM_BUCKET_SIZE_LOG => {
if value != 0 {
if let Err(error) = unsafe { require_bounds(C_LDM_BUCKET_SIZE_LOG, value) } {
return error;
}
}
params.ldmParams.bucketSizeLog = value as u32;
params.ldmParams.bucketSizeLog as usize
}
C_LDM_HASH_RATE_LOG => {
if value != 0 {
if let Err(error) = unsafe { require_bounds(C_LDM_HASH_RATE_LOG, value) } {
return error;
}
}
params.ldmParams.hashRateLog = value as u32;
params.ldmParams.hashRateLog as usize
}
C_TARGET_C_BLOCK_SIZE => {
if value != 0 {
value = value.max(TARGET_C_BLOCK_SIZE_MIN);
if let Err(error) = unsafe { require_bounds(C_TARGET_C_BLOCK_SIZE, value) } {
return error;
}
}
params.targetCBlockSize = value as u32 as usize;
params.targetCBlockSize
}
C_SRC_SIZE_HINT => {
if value != 0 {
if let Err(error) = unsafe { require_bounds(C_SRC_SIZE_HINT, value) } {
return error;
}
}
params.srcSizeHint = value;
params.srcSizeHint as usize
}
C_STABLE_IN_BUFFER => {
if let Err(error) = unsafe { require_bounds(C_STABLE_IN_BUFFER, value) } {
return error;
}
params.inBufferMode = value;
value as usize
}
C_STABLE_OUT_BUFFER => {
if let Err(error) = unsafe { require_bounds(C_STABLE_OUT_BUFFER, value) } {
return error;
}
params.outBufferMode = value;
value as usize
}
C_BLOCK_DELIMITERS => {
if let Err(error) = unsafe { require_bounds(C_BLOCK_DELIMITERS, value) } {
return error;
}
params.blockDelimiters = value;
value as usize
}
C_VALIDATE_SEQUENCES => {
if let Err(error) = unsafe { require_bounds(C_VALIDATE_SEQUENCES, value) } {
return error;
}
params.validateSequences = value;
value as usize
}
C_SPLIT_AFTER_SEQUENCES => {
if let Err(error) = unsafe { require_bounds(C_SPLIT_AFTER_SEQUENCES, value) } {
return error;
}
params.postBlockSplitter = value;
value as usize
}
C_BLOCK_SPLITTER_LEVEL => {
if let Err(error) = unsafe { require_bounds(C_BLOCK_SPLITTER_LEVEL, value) } {
return error;
}
params.preBlockSplitter_level = value;
value as usize
}
C_USE_ROW_MATCH_FINDER => {
if let Err(error) = unsafe { require_bounds(C_USE_ROW_MATCH_FINDER, value) } {
return error;
}
params.useRowMatchFinder = value;
value as usize
}
C_DETERMINISTIC_REF_PREFIX => {
if let Err(error) = unsafe { require_bounds(C_DETERMINISTIC_REF_PREFIX, value) } {
return error;
}
params.deterministicRefPrefix = (value != 0) as c_int;
params.deterministicRefPrefix as usize
}
C_PREFETCH_CDICT_TABLES => {
if let Err(error) = unsafe { require_bounds(C_PREFETCH_CDICT_TABLES, value) } {
return error;
}
params.prefetchCDictTables = value;
value as usize
}
C_ENABLE_SEQ_PRODUCER_FALLBACK => {
if let Err(error) = unsafe { require_bounds(C_ENABLE_SEQ_PRODUCER_FALLBACK, value) } {
return error;
}
params.enableMatchFinderFallback = value;
value as usize
}
C_MAX_BLOCK_SIZE => {
if value != 0 {
if let Err(error) = unsafe { require_bounds(C_MAX_BLOCK_SIZE, value) } {
return error;
}
}
params.maxBlockSize = value as usize;
params.maxBlockSize
}
C_REPCODE_RESOLUTION => {
if let Err(error) = unsafe { require_bounds(C_REPCODE_RESOLUTION, value) } {
return error;
}
params.searchForExternalRepcodes = value;
value as usize
}
_ => ERROR(ZstdErrorCode::ParameterUnsupported),
}
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_CCtxParams_setParameter(
params: *mut ZSTD_CCtx_params,
param: c_int,
value: c_int,
) -> usize {
unsafe { set_parameter(params, param, value) }
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_CCtxParams_getParameter(
params: *const ZSTD_CCtx_params,
param: c_int,
value: *mut c_int,
) -> usize {
let params = unsafe { &*params };
let result = match param {
C_FORMAT => params.format,
C_COMPRESSION_LEVEL => params.compressionLevel,
C_WINDOW_LOG => params.cParams.windowLog as c_int,
C_HASH_LOG => params.cParams.hashLog as c_int,
C_CHAIN_LOG => params.cParams.chainLog as c_int,
C_SEARCH_LOG => params.cParams.searchLog as c_int,
C_MIN_MATCH => params.cParams.minMatch as c_int,
C_TARGET_LENGTH => params.cParams.targetLength as c_int,
C_STRATEGY => params.cParams.strategy,
C_CONTENT_SIZE_FLAG => params.fParams.contentSizeFlag,
C_CHECKSUM_FLAG => params.fParams.checksumFlag,
C_DICT_ID_FLAG => (params.fParams.noDictIDFlag == 0) as c_int,
C_FORCE_MAX_WINDOW => params.forceWindow,
C_FORCE_ATTACH_DICT => params.attachDictPref,
C_LITERAL_COMPRESSION_MODE => params.literalCompressionMode,
C_NB_WORKERS => {
if !unsafe { multithreading_enabled() } {
debug_assert_eq!(params.nbWorkers, 0);
}
params.nbWorkers
}
C_JOB_SIZE => {
if !unsafe { multithreading_enabled() } {
return ERROR(ZstdErrorCode::ParameterUnsupported);
}
debug_assert!(params.jobSize <= c_int::MAX as usize);
params.jobSize as c_int
}
C_OVERLAP_LOG => {
if !unsafe { multithreading_enabled() } {
return ERROR(ZstdErrorCode::ParameterUnsupported);
}
params.overlapLog
}
C_RSYNCABLE => {
if !unsafe { multithreading_enabled() } {
return ERROR(ZstdErrorCode::ParameterUnsupported);
}
params.rsyncable
}
C_ENABLE_DEDICATED_DICT_SEARCH => params.enableDedicatedDictSearch,
C_ENABLE_LDM => params.ldmParams.enableLdm,
C_LDM_HASH_LOG => params.ldmParams.hashLog as c_int,
C_LDM_MIN_MATCH => params.ldmParams.minMatchLength as c_int,
C_LDM_BUCKET_SIZE_LOG => params.ldmParams.bucketSizeLog as c_int,
C_LDM_HASH_RATE_LOG => params.ldmParams.hashRateLog as c_int,
C_TARGET_C_BLOCK_SIZE => params.targetCBlockSize as c_int,
C_SRC_SIZE_HINT => params.srcSizeHint,
C_STABLE_IN_BUFFER => params.inBufferMode,
C_STABLE_OUT_BUFFER => params.outBufferMode,
C_BLOCK_DELIMITERS => params.blockDelimiters,
C_VALIDATE_SEQUENCES => params.validateSequences,
C_SPLIT_AFTER_SEQUENCES => params.postBlockSplitter,
C_BLOCK_SPLITTER_LEVEL => params.preBlockSplitter_level,
C_USE_ROW_MATCH_FINDER => params.useRowMatchFinder,
C_DETERMINISTIC_REF_PREFIX => params.deterministicRefPrefix,
C_PREFETCH_CDICT_TABLES => params.prefetchCDictTables,
C_ENABLE_SEQ_PRODUCER_FALLBACK => params.enableMatchFinderFallback,
C_MAX_BLOCK_SIZE => params.maxBlockSize as c_int,
C_REPCODE_RESOLUTION => params.searchForExternalRepcodes,
_ => return ERROR(ZstdErrorCode::ParameterUnsupported),
};
unsafe { *value = result };
0
}
#[no_mangle]
pub unsafe extern "C" fn ZSTD_CCtxParams_registerSequenceProducer(
params: *mut ZSTD_CCtx_params,
ext_seq_prod_state: *mut c_void,
ext_seq_prod_func: Option<ZSTD_sequenceProducer_F>,
) {
debug_assert!(!params.is_null());
let params = unsafe { &mut *params };
if ext_seq_prod_func.is_some() {
params.extSeqProdFunc = ext_seq_prod_func;
params.extSeqProdState = ext_seq_prod_state;
} else {
params.extSeqProdFunc = None;
params.extSeqProdState = ptr::null_mut();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::errors::{ERR_getErrorCode, ERR_isError};
use std::mem::{align_of, offset_of, size_of, MaybeUninit};
#[test]
fn private_parameter_mirror_matches_the_c_layout() {
let pointer_size = size_of::<usize>();
assert_eq!(align_of::<ZSTD_CCtx_params>(), pointer_size);
assert_eq!(size_of::<ZSTD_compressionParameters>(), 28);
assert_eq!(size_of::<ZSTD_frameParameters>(), 12);
assert_eq!(size_of::<ldmParams_t>(), 24);
assert_eq!(
size_of::<ZSTD_customMem>(),
if pointer_size == 8 { 24 } else { 12 }
);
assert_eq!(offset_of!(ZSTD_CCtx_params, format), 0);
assert_eq!(offset_of!(ZSTD_CCtx_params, cParams), 4);
assert_eq!(offset_of!(ZSTD_CCtx_params, fParams), 32);
assert_eq!(offset_of!(ZSTD_CCtx_params, compressionLevel), 44);
assert_eq!(offset_of!(ZSTD_CCtx_params, forceWindow), 48);
assert_eq!(
offset_of!(ZSTD_CCtx_params, targetCBlockSize),
if pointer_size == 8 { 56 } else { 52 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, srcSizeHint),
if pointer_size == 8 { 64 } else { 56 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, attachDictPref),
if pointer_size == 8 { 68 } else { 60 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, literalCompressionMode),
if pointer_size == 8 { 72 } else { 64 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, nbWorkers),
if pointer_size == 8 { 76 } else { 68 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, jobSize),
if pointer_size == 8 { 80 } else { 72 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, overlapLog),
if pointer_size == 8 { 88 } else { 76 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, rsyncable),
if pointer_size == 8 { 92 } else { 80 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, ldmParams),
if pointer_size == 8 { 96 } else { 84 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, enableDedicatedDictSearch),
if pointer_size == 8 { 120 } else { 108 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, inBufferMode),
if pointer_size == 8 { 124 } else { 112 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, outBufferMode),
if pointer_size == 8 { 128 } else { 116 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, blockDelimiters),
if pointer_size == 8 { 132 } else { 120 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, validateSequences),
if pointer_size == 8 { 136 } else { 124 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, postBlockSplitter),
if pointer_size == 8 { 140 } else { 128 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, preBlockSplitter_level),
if pointer_size == 8 { 144 } else { 132 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, maxBlockSize),
if pointer_size == 8 { 152 } else { 136 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, useRowMatchFinder),
if pointer_size == 8 { 160 } else { 140 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, deterministicRefPrefix),
if pointer_size == 8 { 164 } else { 144 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, customMem),
if pointer_size == 8 { 168 } else { 148 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, prefetchCDictTables),
if pointer_size == 8 { 192 } else { 160 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, enableMatchFinderFallback),
if pointer_size == 8 { 196 } else { 164 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, extSeqProdState),
if pointer_size == 8 { 200 } else { 168 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, extSeqProdFunc),
if pointer_size == 8 { 208 } else { 172 }
);
assert_eq!(
offset_of!(ZSTD_CCtx_params, searchForExternalRepcodes),
if pointer_size == 8 { 216 } else { 176 }
);
assert_eq!(
size_of::<ZSTD_CCtx_params>(),
if pointer_size == 8 { 224 } else { 180 }
);
}
#[test]
fn init_reset_and_set_get_preserve_public_semantics() {
let mut storage = MaybeUninit::<ZSTD_CCtx_params>::uninit();
let params = storage.as_mut_ptr();
unsafe {
assert_eq!(
ZSTD_CCtxParams_init(ptr::null_mut(), DEFAULT_CLEVEL),
ERROR(ZstdErrorCode::Generic)
);
assert_eq!(
ZSTD_CCtxParams_reset(ptr::null_mut()),
ERROR(ZstdErrorCode::Generic)
);
assert_eq!(ZSTD_CCtxParams_init(params, -7), 0);
assert_eq!(ZSTD_CCtxParams_setParameter(params, C_FORMAT, 1), 1);
assert_eq!(
ZSTD_CCtxParams_setParameter(params, C_COMPRESSION_LEVEL, 0),
3
);
assert_eq!(
ZSTD_CCtxParams_setParameter(params, C_COMPRESSION_LEVEL, -7),
0
);
assert_eq!(
ZSTD_CCtxParams_setParameter(params, C_TARGET_C_BLOCK_SIZE, 1),
1340
);
assert_eq!(
ZSTD_CCtxParams_setParameter(params, C_CONTENT_SIZE_FLAG, -1),
1
);
assert_eq!(ZSTD_CCtxParams_setParameter(params, C_DICT_ID_FLAG, 0), 0);
assert_eq!(
ZSTD_CCtxParams_setParameter(params, C_WINDOW_LOG, 9),
ERROR(ZstdErrorCode::ParameterOutOfBound)
);
let mut value = -1;
assert_eq!(
ZSTD_CCtxParams_getParameter(params, C_COMPRESSION_LEVEL, &mut value),
0
);
assert_eq!(value, -7);
assert_eq!(
ZSTD_CCtxParams_getParameter(params, C_FORMAT, &mut value),
0
);
assert_eq!(value, 1);
assert_eq!(
ZSTD_CCtxParams_getParameter(params, C_DICT_ID_FLAG, &mut value),
0
);
assert_eq!(value, 0);
assert_eq!(ZSTD_CCtxParams_reset(params), 0);
assert_eq!(
ZSTD_CCtxParams_getParameter(params, C_COMPRESSION_LEVEL, &mut value),
0
);
assert_eq!(value, DEFAULT_CLEVEL);
assert_eq!(
ZSTD_CCtxParams_getParameter(params, C_CHECKSUM_FLAG, &mut value),
0
);
assert_eq!(value, 0);
}
}
#[test]
fn unsupported_and_non_multithreaded_parameters_keep_error_codes() {
let mut storage = MaybeUninit::<ZSTD_CCtx_params>::uninit();
let params = storage.as_mut_ptr();
unsafe {
assert_eq!(ZSTD_CCtxParams_init(params, DEFAULT_CLEVEL), 0);
assert_eq!(
ZSTD_CCtxParams_setParameter(params, C_NB_WORKERS, 1),
ERROR(ZstdErrorCode::ParameterUnsupported)
);
let mut value = -1;
assert_eq!(
ZSTD_CCtxParams_getParameter(params, C_JOB_SIZE, &mut value),
ERROR(ZstdErrorCode::ParameterUnsupported)
);
assert_eq!(
ZSTD_CCtxParams_setParameter(params, 12345, 0),
ERROR(ZstdErrorCode::ParameterUnsupported)
);
assert!(ERR_isError(ERROR(ZstdErrorCode::ParameterUnsupported)));
assert_eq!(
ERR_getErrorCode(ERROR(ZstdErrorCode::ParameterUnsupported)),
ZstdErrorCode::ParameterUnsupported as i32
);
}
}
#[test]
fn sequence_producer_registration_sets_and_clears_both_fields() {
unsafe extern "C" fn producer(
_state: *mut c_void,
_out: *mut ZSTD_Sequence,
_capacity: usize,
_src: *const c_void,
_src_size: usize,
_dict: *const c_void,
_dict_size: usize,
_level: c_int,
_window: usize,
) -> usize {
0
}
let mut storage = MaybeUninit::<ZSTD_CCtx_params>::uninit();
let params = storage.as_mut_ptr();
let state = std::ptr::dangling_mut::<c_void>();
unsafe {
assert_eq!(ZSTD_CCtxParams_init(params, DEFAULT_CLEVEL), 0);
ZSTD_CCtxParams_registerSequenceProducer(params, state, Some(producer));
assert_eq!((*params).extSeqProdState, state);
assert!((*params).extSeqProdFunc.is_some());
ZSTD_CCtxParams_registerSequenceProducer(params, state, None);
assert!((*params).extSeqProdState.is_null());
assert!((*params).extSeqProdFunc.is_none());
}
}
}